Mapping Directions with JavaFX using the GMapsFX Directions API

Mapping directions in a JavaFX application is easy with the Directions API that was recently introduced in GMapsFX.  In this blog post I’ll walk through an example of setting up an application with a map and a couple of text fields, one which will be used for the trip origin and the second which will be used for the trip destination.  When the user hits ‘Enter’ in the destination text field, the map will display the directions.

Starting off with the FXML file, we have an AnchorPane which contains the GoogleMapView and 2 TextFields.  The AnchorPane has a controller assigned to it named FXMLController, and both components have an FX ID associated with them so they will be accessible from the FXMLController class.  Also, the destination TextField has an action, “toTextFieldAction” associated with it, so this method will be called when the user hits the ‘Enter’ key in the TextField.


<?xml version="1.0" encoding="UTF-8"?>
<?import com.lynden.gmapsfx.GoogleMapView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" prefHeight="443.0" prefWidth="711.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.65" fx:controller="com.lynden.gmaps.directions.FXMLController">
<children>
<GoogleMapView fx:id="mapView" layoutX="-311.0" layoutY="-244.0" prefWidth="490.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
<TextField fx:id="fromTextField" prefHeight="27.0" prefWidth="222.0" promptText="From:" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="10.0" />
<TextField fx:id="toTextField" layoutX="10.0" layoutY="10.0" onAction="#toTextFieldAction" prefHeight="27.0" prefWidth="222.0" promptText="To:" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="50.0" />
</children>
</AnchorPane>

view raw

Scene.fxml

hosted with ❤ by GitHub

 

The result should look as follows:

Screen Shot 2016-08-19 at 2.56.27 PM

 

Next, I’ve cut up the relevant parts of the FXMLController class.  The MapComponentInitializedListener interface needs to be implemented by the controller since the underlying GoogleMap doesn’t get initialized immediately.  The DirectionsServiceCallback interface also needs to be implemented, although in this example I won’t be doing anything with it.

The GoogleMapView and the TextFields components from the FXML file are defined below and annotated with @FXML.

There is also a reference to the Directions Service as well as StringProperties to represent the ‘to’ and ‘from’ endpoints that the user will enter.

 


public class FXMLController implements Initializable, MapComponentInitializedListener, DirectionsServiceCallback {
protected StringProperty from = new SimpleStringProperty();
protected StringProperty to = new SimpleStringProperty();
@FXML
protected GoogleMapView mapView;
@FXML
protected TextField fromTextField;
@FXML
protected TextField toTextField;

 

After the controller is created, its initialize method is called which will set the MapView’s initialization listener to the FXMLController as well as bind the ‘to’ and ‘from’ String properties to the TextProperties of their respective TextFields.


@Override
public void initialize(URL url, ResourceBundle rb) {
mapView.addMapInializedListener(this);
to.bindBidirectional(toTextField.textProperty());
from.bindBidirectional(fromTextField.textProperty());
}

 

Once the map has been initialized, the DirectionService can be instantiated as well as a MapOptions object to set various attributes about the map.  The options are then configured and a GoogleMap object can be instantiated from the map view.  The directionsPane is a component which can be used to render the step by step direction text, in this example however, it won’t be displayed.


@Override
public void mapInitialized() {
MapOptions options = new MapOptions();
options.center(new LatLong(47.606189, –122.335842))
.zoomControl(true)
.zoom(12)
.overviewMapControl(false)
.mapType(MapTypeIdEnum.ROADMAP);
GoogleMap map = mapView.createMap(options);
directionsService = new DirectionsService();
directionsPane = mapView.getDirec();
}

Finally, the action method defined in the FXML file when the user hits ‘Enter’ in the TextField is below.  The method will call the getRoute() method on the DirectionsService class, passing in a boolean value which will define whether the route can be modified by dragging it, the map object, and the DirectionsRequest object.


@FXML
private void toTextFieldAction(ActionEvent event) {
DirectionsRequest request = new DirectionsRequest(from.get(), to.get(), TravelModes.DRIVING);
directionsService.getRoute(request, this, new DirectionsRenderer(true, mapView.getMap(), directionsPane));
}

 

Below is an example when the user enters directions from Seattle to Redmond

 

Screen Shot 2016-08-19 at 3.37.38 PM

That’s it!  For completeness I’ll include the full source code of the example below.

 

 

Scene.fxml


<?xml version="1.0" encoding="UTF-8"?>
<?import com.lynden.gmapsfx.GoogleMapView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" prefHeight="443.0" prefWidth="711.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.65" fx:controller="com.lynden.gmaps.directions.FXMLController">
<children>
<GoogleMapView fx:id="mapView" layoutX="-311.0" layoutY="-244.0" prefWidth="490.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
<TextField fx:id="fromTextField" prefHeight="27.0" prefWidth="222.0" promptText="From:" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="10.0" />
<TextField fx:id="toTextField" layoutX="10.0" layoutY="10.0" onAction="#toTextFieldAction" prefHeight="27.0" prefWidth="222.0" promptText="To:" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="50.0" />
</children>
</AnchorPane>

view raw

Scene.fxml

hosted with ❤ by GitHub

 

MainApp.java


package com.lynden.gmaps.directions;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class MainApp extends Application {
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/fxml/Scene.fxml"));
Scene scene = new Scene(root);
scene.getStylesheets().add("/styles/Styles.css");
stage.setTitle("JavaFX and Maven");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

 

FXMLController.java


package com.lynden.gmaps.directions;
import com.lynden.gmapsfx.GoogleMapView;
import com.lynden.gmapsfx.MapComponentInitializedListener;
import com.lynden.gmapsfx.javascript.object.*;
import com.lynden.gmapsfx.service.directions.*;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
public class FXMLController implements Initializable, MapComponentInitializedListener, DirectionsServiceCallback {
protected DirectionsService directionsService;
protected DirectionsPane directionsPane;
protected StringProperty from = new SimpleStringProperty();
protected StringProperty to = new SimpleStringProperty();
@FXML
protected GoogleMapView mapView;
@FXML
protected TextField fromTextField;
@FXML
protected TextField toTextField;
@FXML
private void toTextFieldAction(ActionEvent event) {
DirectionsRequest request = new DirectionsRequest(from.get(), to.get(), TravelModes.DRIVING);
directionsService.getRoute(request, this, new DirectionsRenderer(true, mapView.getMap(), directionsPane));
}
@Override
public void directionsReceived(DirectionsResult results, DirectionStatus status) {
}
@Override
public void initialize(URL url, ResourceBundle rb) {
mapView.addMapInializedListener(this);
to.bindBidirectional(toTextField.textProperty());
from.bindBidirectional(fromTextField.textProperty());
}
@Override
public void mapInitialized() {
MapOptions options = new MapOptions();
options.center(new LatLong(47.606189, –122.335842))
.zoomControl(true)
.zoom(12)
.overviewMapControl(false)
.mapType(MapTypeIdEnum.ROADMAP);
GoogleMap map = mapView.createMap(options);
directionsService = new DirectionsService();
directionsPane = mapView.getDirec();
}
}

GMapsFX 2.0.9 Released

The latest version of GMapsFX has been released which contains a fix for a bug that was preventing the GoogleMapView component from being added as a custom component to SceneBuilder.

The fix will allow the MapView to be added as a custom component.  In a future blog post I will detail how to do this.

 

Screen Shot 2016-08-05 at 4.26.39 PM.png

Mapping an Address with JavaFX using the GMapsFX Geocoding API

Mapping an address in a JavaFX application is extremely easy with the Geocoding API that was recently introduced in GMapsFX.  In this blog post I’ll walk through an example of setting up an application with a map and a text field.  The map will recenter itself at whatever address or place the user types in the text field.

Starting off with the FXML file, we have an AnchorPane which contains the GoogleMapView and a TextField.  The AnchorPane has a controller assigned to it named FXMLController, and both components have an FX ID associated with them so they will be accessible from the FXMLController class.  Also, the TextField has an action, “addressTextFieldAction” associated with it, so this method will be called when the user hits the ‘Enter’ key in the TextField.


<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.*?>
<?import com.lynden.gmapsfx.GoogleMapView?>
<AnchorPane id="AnchorPane" prefHeight="500" prefWidth="750" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.65" fx:controller="com.mycompany.gmapstestproject.FXMLController">
<children>
<GoogleMapView fx:id="mapView" prefHeight="500.0" prefWidth="700.0" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0.0"/>
<TextField fx:id="addressTextField" onAction="#addressTextFieldAction" prefHeight="27.0" prefWidth="274.0" promptText="Address" AnchorPane.leftAnchor="10" AnchorPane.topAnchor="10" />
</children>
</AnchorPane>

view raw

Scene.fxml

hosted with ❤ by GitHub

 

The result should look as follows:

Screen Shot 2016-07-29 at 3.12.18 PM.png

 

Next, I’ve cut up the relevant parts of the FXMLController class.  The MapComponentInitializedListener interface needs to be implemented by the controller since the underlying GoogleMap doesn’t get initialized immediately.  The GoogleMapView and TextField components from the FXML file are defined below and annotated with @FXML.

There is also a reference to the GeocodingService as well as a StringProperty to represent the address the user enters.

 


public class FXMLController implements Initializable, MapComponentInitializedListener {
@FXML
private GoogleMapView mapView;
@FXML
private TextField addressTextField;
private GoogleMap map;
private GeocodingService geocodingService;
private StringProperty address = new SimpleStringProperty();

 

After the controller is created its initialize method is called which will set the MapView’s initialization listener to the FXMLController as well as bind the address property to the address TextField’s text property.


@Override
public void initialize(URL url, ResourceBundle rb) {
mapView.addMapInializedListener(this);
address.bind(addressTextField.textProperty());
}

 

Once the map has been initialized, the GeocodingService can be instantiated as well as a MapOptions object to set various attributes about the map.  Once the options are configured, a GoogleMap object can be instantiated from the map view.


@Override
public void mapInitialized() {
geocodingService = new GeocodingService();
MapOptions mapOptions = new MapOptions();
mapOptions.center(new LatLong(47.6097, –122.3331))
.mapType(MapTypeIdEnum.ROADMAP)
.overviewMapControl(false)
.panControl(false)
.rotateControl(false)
.scaleControl(false)
.streetViewControl(false)
.zoomControl(false)
.zoom(12);
map = mapView.createMap(mapOptions);
}

 

Finally, the action method defined in the FXML file when the user hits ‘Enter’ in the TextField is below.  The method will call the geocode() method on the GeocodeService class, passing in the value of the Address property as well as a callback method.

The callback will check the status of the results, and based on the outcome, will recenter the map at the latitude/longitude the user had entered.


@FXML
public void addressTextFieldAction(ActionEvent event) {
geocodingService.geocode(address.get(), (GeocodingResult[] results, GeocoderStatus status) -> {
LatLong latLong = null;
if( status == GeocoderStatus.ZERO_RESULTS) {
Alert alert = new Alert(Alert.AlertType.ERROR, "No matching address found");
alert.show();
return;
} else if( results.length > 1 ) {
Alert alert = new Alert(Alert.AlertType.WARNING, "Multiple results found, showing the first one.");
alert.show();
latLong = new LatLong(results[0].getGeometry().getLocation().getLatitude(), results[0].getGeometry().getLocation().getLongitude());
} else {
latLong = new LatLong(results[0].getGeometry().getLocation().getLatitude(), results[0].getGeometry().getLocation().getLongitude());
}
map.setCenter(latLong);
});
}

 

Below is an example when the user enters New York City as the address.

Screen Shot 2016-07-29 at 4.27.56 PM

 

That’s it!  For completeness I’ll include the full source code of the example below.

 

 

Scene.fxml


<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.*?>
<?import com.lynden.gmapsfx.GoogleMapView?>
<AnchorPane id="AnchorPane" prefHeight="500" prefWidth="750" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.65" fx:controller="com.mycompany.gmapstestproject.FXMLController">
<children>
<GoogleMapView fx:id="mapView" prefHeight="500.0" prefWidth="700.0" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0.0"/>
<TextField fx:id="addressTextField" onAction="#addressTextFieldAction" prefHeight="27.0" prefWidth="274.0" promptText="Address" AnchorPane.leftAnchor="10" AnchorPane.topAnchor="10" />
</children>
</AnchorPane>

view raw

Scene.fxml

hosted with ❤ by GitHub

 

MainApp.java


package com.mycompany.gmapstestproject;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class MainApp extends Application {
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/fxml/Scene.fxml"));
Scene scene = new Scene(root);
stage.setTitle("JavaFX Geocode Example");
stage.setScene(scene);
stage.show();
}
/**
* The main() method is ignored in correctly deployed JavaFX application.
* main() serves only as fallback in case the application can not be
* launched through deployment artifacts, e.g., in IDEs with limited FX
* support. NetBeans ignores main().
*
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

view raw

MainApp.java

hosted with ❤ by GitHub

 

FXMLController.java


package com.mycompany.gmapstestproject;
import com.lynden.gmapsfx.GoogleMapView;
import com.lynden.gmapsfx.MapComponentInitializedListener;
import com.lynden.gmapsfx.javascript.object.*;
import com.lynden.gmapsfx.service.geocoding.GeocoderStatus;
import com.lynden.gmapsfx.service.geocoding.GeocodingResult;
import com.lynden.gmapsfx.service.geocoding.GeocodingService;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Alert;
import javafx.scene.control.TextField;
public class FXMLController implements Initializable, MapComponentInitializedListener {
@FXML
private GoogleMapView mapView;
@FXML
private TextField addressTextField;
private GoogleMap map;
private GeocodingService geocodingService;
private StringProperty address = new SimpleStringProperty();
@Override
public void initialize(URL url, ResourceBundle rb) {
mapView.addMapInializedListener(this);
address.bind(addressTextField.textProperty());
}
@Override
public void mapInitialized() {
geocodingService = new GeocodingService();
MapOptions mapOptions = new MapOptions();
mapOptions.center(new LatLong(47.6097, –122.3331))
.mapType(MapTypeIdEnum.ROADMAP)
.overviewMapControl(false)
.panControl(false)
.rotateControl(false)
.scaleControl(false)
.streetViewControl(false)
.zoomControl(false)
.zoom(12);
map = mapView.createMap(mapOptions);
}
@FXML
public void addressTextFieldAction(ActionEvent event) {
geocodingService.geocode(address.get(), (GeocodingResult[] results, GeocoderStatus status) -> {
LatLong latLong = null;
if( status == GeocoderStatus.ZERO_RESULTS) {
Alert alert = new Alert(Alert.AlertType.ERROR, "No matching address found");
alert.show();
return;
} else if( results.length > 1 ) {
Alert alert = new Alert(Alert.AlertType.WARNING, "Multiple results found, showing the first one.");
alert.show();
latLong = new LatLong(results[0].getGeometry().getLocation().getLatitude(), results[0].getGeometry().getLocation().getLongitude());
} else {
latLong = new LatLong(results[0].getGeometry().getLocation().getLatitude(), results[0].getGeometry().getLocation().getLongitude());
}
map.setCenter(latLong);
});
}
}

GMapsFX 2.0.7 Released

A new version of GMapsFX has been released to bintray and Maven Central.  The main feature in this version is to allow the use of custom marker/pin images, rather than relying on the default Google images.

A future blog post will demonstrate how to add custom markers to your GMapsFX application.

 

Screen Shot 2016-05-20 at 2.25.33 PM

 

GMapsFX 2.0.4 Released

A minor update of GMapsFX has been released this week which provides a bugfix for an issue that was preventing the GMapsFX GoogleMapView component from being loaded into Scene Builder under certain circumstances.

The release number is 2.0.4 and is currently available from BinTray, and hopefully should be available via Maven Central in the next couple of days.

twitter: @RobTerpilowski
LinkedIn: RobTerpilowski

 

Screen Shot 2016-03-18 at 3.40.46 PM

 

 

GMapsFX 2.0.3 Released With Support for Directions, Elevation, and Geocoding APIs

I have just released version 2.0.3 which includes support for the following major new features:

Directions API
Elevation API
Geocoding API

http://rterp.github.io/GMapsFX/

Below is an example of a JavaFX application displaying a Google map with directions, utilizing the Directions API.

Screen Shot 2016-03-18 at 3.40.46 PM.png

This release has been uploaded to bintray, and should hopefully be available on maven central by next week.

Blog posts to follow this one with tutorials on how to use each one of these new APIs.

https://bintray.com/artifact/download/rterp/maven/com/lynden/GMapsFX/2.0.3/GMapsFX-2.0.3.jar

twitter: @RobTerpilowski
LinkedIn: RobTerpilowski

 

JavaFX vs. Swing vs. HTML5, Who Wins? – My Interview with Alexander Casall

In November 2015 Dirk Lemmermann (Freelancer) and Alexander Casall of Saxonia Systems had a JavaOne session about JavaFX Real World Applications. The article 20 JavaFX real-world applications summarizes the presentation by showing the applications that they’ve talked about. In addition to providing example applications for the article, I was interviewed by Alexander to get my thoughts on JavaFX and desktop development in general. The interview appears below.

Can you tell us about the highlights when you used JavaFX?

The animated transitions and effects such as blurring or drop shadows make a huge difference in the user experience of the application when implemented properly. These are small details that sometimes get glossed over, but when introduced to an application can create a very polished UI.  The effects and transitions were something that were possible to do with Swing, but it was so painful.  I don’t remember how many times I had to override the paintComponent() method to customize the look of a particular component, but all of this is baked into the JavaFX framework, allowing you to do these things in literally a few lines of code.

What is your general opinion about JavaFX?

Overall I am pleased with JavaFX as a successor to Swing.  The addition of property bindings, which eliminate the need for event listeners in many circumstances helps cut down on some of the code complexity.  I also like the fact that there is a very clear seperation between the model, view, and controller, where the view is FXML, the model can be a collection of JavaFX properties, and the controller utilizes dependency injection to have the UI components passed in.  There are some nice tools for doing JavaFX development, including NetBeans for coding, SceneBuilder as a WYSIWYG design tool and ScenicView to help visual provide information about the application while it is running.

JavaFX, Swing, SWT, HTML5 – Who wins – or better, when to use what?

For a new application I would not consider Swing or SWT, which leaves either JavaFX or HTML5 as the remaining options.  In this case there is not a clear winner, but a set of tradeoffs one needs to consider when making a decision.  With HTML5 you have the advantage of being able to deploy your application across many different platforms (phones, tablets, and Desktops), as well as multiple OSs (Windows, Mac, Linux).  There is also the benefit of a huge development community and large selection of open source tools and frameworks.  The ease of deployment across platforms comes at a cost however, in that you must operate within the constraints that are placed on you by the browser. The amount of time debugging issues across different browsers or OSs is often overlooked or underestimated by teams when deciding whether or not to go the desktop or web app route.  We recently worked on a project where a very large chunk of time had been consumed in order to get a piece of functionality working correctly in IE 9 on Windows.With JavaFX the drawback is that the user has to download and install something to their desktop, which is becoming very old fashioned.  But if this is not an issue, then you are free to develop outside the constraints of the browser and use the full power of the Java language and the eco system that backs it.For applications that are used internally within the company I feel that it makes a lot of sense to deploy these at desktop applications for this reason.  Deployments are not an issue in this case as we can automatically push out new installations or updates to PCs in our network automatically.  We also bundle a private JRE with the application so we don’t need to worry about which version(s) of Java the user has installed on their PC.

How satisfied are you with the work of Oracle on JavaFX?

Jonathan Giles and his team have been doing great work at Oracle adding improving and enhancing the JavaFX libraries.  That being said, it would be nice if Oracle officially stated what their long term plans are with JavaFX.  When Oracle let go of some of their evangelists (who were big proponents of JavaFX), just before JavaOne it started a rumor mill of what may have been behind the move.  The uncertainty this has created, and lack of official communication from Oracle will likely deter some development teams who may be on the fence about whether they should port legacy Swing application to JavaFX or HTML5. Over time this will potentially affect how large the JavaFX community eventually becomes.

What do you miss in the work with JavaFX?

The amount of 3rd party component libraries (both open source and commercial) that are available for JavaFX is still somewhat limited at this point, but that should change as the JavaFX community continues to grow.

 

twitter: @RobTerpilowski
LinkedIn: Rob Terpilowski

Handy Tools for JavaFX Development

If you are building an application with JavaFx there are a few tools that will make your life a whole lot easier and save you a lot of time.


Scene Builder

The WYSIWYG drag-n-drop design tool for JavaFx will build an FXML representation of the UI which can then be loaded by a JavaFx application.  Scene Builder helps to enforce the MVC pattern, keeping business logic out of the code that describes the UI. (More on this below).

SceneBuilder1

Scene builder also has a nice “CSS Analyzer” feature which will show you the full CSS path to a component that is selected in the tool.  This has come in handy when attempting to figure out what the CSS path for a component is when attempting to determine where to apply a CSS style .  I wish I had known about this when I first began using JavaFx, as it was previously a trial-and-error process for me to get a component styled correctly.

SceneBuilder2

Gluon, Inc. has taken over the build and release of the Scene Builder installer packages which can be obtained at the following link: http://gluonhq.com/open-source/scene-builder/


NetBeans

Of course you will need a good IDE for developing the remaining, and NetBeans provides a number of features which helps out with developing with JavaFX.

The first is the autocomplete feature when working with the FXML files that are generated by Scene Builder.  The IDE will generate a list of possible values when inserting or modifying nodes.

NetbeansFx

There is also support when working with the JavaFX CSS files that will be loaded by the application.  In the example below, NetBeans generates a list of possible colors for the -fx-background-color property.

NetbeansFxCss

Finally, there is a code generator plugin which will generate getter and setter methods for JavaFX properties in a POJO class.  The normal Getter/Setter functionality would return a StringProperty object for the name variable (below), when ideally I would want the class me to return the actual String that the property represents.  A screen shot below shows the “Generate” popup menu item with the “JavaFx Props Getters & Setters”.  Once selected, the following screenshot illustrates the code that is generated.

The plugin is available at the link below.

https://github.com/rterp/JavaFxPropertyHelperNBPlugin/releases/download/1.0.0/JavaFxPropertyHelperNBPlugin-1.0.0.nbm

Ubuntu1

NetbeansFx2


Scenic View

Scenic View is another helpful tool when developing and debugging JavaFX applications.  It is a stand alone GUI application which will attach to a JavaFX application, and display all the nodes in the scene graph in a collapsable tree.  On the right side of the UI it will display various properties about the selected node.  ie min height, max height, location, css styles, etc.  In the screenshot below Scenic View is in front on the right side of the screen, and the application being debugged is behind Scenic View on the left side of the screen.  The selected button on the app is highlighted yellow, and Scenic View has selected this button in its tree view and is displaying its properties in the table on the right.

Scenic View can be downloaded at the FxExperience website.

ScenicView


MvvMFxLogo

Finally, MvvmFX is a framework for implementing the Model-View-ViewModel (MVVM) pattern in JavaFX.  This pattern allows you to separate any business logic related to the UI from the UI data and state, meaning that the business logic behind the UI can be unit tested without creating any UI components.  Since the view model doesn’t know about the view, it makes it possible to replace the view without requiring modifications to the view model.

An instance of the view model is injected into the view, which is just a JavaFx controller for a component.  An example view/controller class is shown below.

NetbeansFxMvvM

twitter: @RobTerpilowski
LinkedIn: Profile

JavaFX Toolkit Not Initialized (Solved)

I am attempting to utilize the NetBeans Rich Client Platform (RCP) as a shell to manage plugins for an automated trading application which uses JavaFX for the UI, but no Swing components. As such, I have a module installer class which creates the JavaFX scene, but no TopComponent class which RCP developers are accustom to using. The module class I have created is below.

import org.openide.modules.ModuleInstall;

public class Installer extends ModuleInstall {

@Override
public void restored() {
    Platform.setImplicitExit(false);

    // create JavaFX scene
    Platform.runLater(() -> {
        Parent root;
        try {
            FXMLLoader fxmlLoader = new FXMLLoader(MainScreen.class.getResource("/com/zoicapital/qtrader/plugin/BasePanel.fxml"));
            root = (Parent) fxmlLoader.load();
            Scene scene = new Scene(root);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    });
}

The problem is that when launching the application the following exception is being thrown.

java.lang.IllegalStateException: Toolkit not initialized
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:273)
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:268)
at javafx.application.Platform.runLater(Platform.java:83)
at com.zoicapital.qtrader.plugin.Installer.restored(Installer.java:22)

It turns out that even though Platform.runLater() is being invoked, it does not initialize the JavaFX runtime if it hasn’t already been started.

There are a couple of ways around the issue. The first is to invoke the Application.launch() method, or simply instantiate a new JFXPanel() class (even if it isn’t used for anything). Either action will cause the JavaFX Runtime to start and avoid initialization exception when the application is started.

Animations with JavaFX: So Easy a Chimp Can Do It

Ok, well maybe its slightly more complicated than that, but not much. Things such as animations which were do-able with Swing, but were so painful, are baked into the JavaFX API and are relatively straightforward to implement.

In last month’s blog post, I demoed a JavaFX application which had 2 logos slowly scrolling across the app’s background. The code which was needed in order to acheive this effect was suprisingly minimal. The relevant code snippet from the application’s controller class is below.

public class AppController {

@FXML
protected ImageView logoImage;

@FXML
protected AnchorPane rootPane;

public void animateLogo() {
    TranslateTransition tt = 
    new TranslateTransition(Duration.seconds(30), logoImage);

    tt.setFromX( -(logoImage.getFitWidth()) );
    tt.setToX( rootPane.getPrefWidth() );
    tt.setCycleCount( Timeline.INDEFINITE );
    tt.play();
}
}

In the snippet above, we use a TranslationTransition which will move a node along a specified path over a specified time interval. The easiest way to do this is to give it the start point, end point, and the amount of time to run the animation for.

The TranslationTransition is created with a duration of 30 seconds, using the logoImage ImageView object that was injected by JavaFX into this controller class. Next, the starting point is set by specifying the fromX value on the transition. In this case, we want the animation to start the logo just off of the screen, so we specify a negative X value that is the width of the logo node. The toX value is set to the width of the entire UI so that the logo will scroll completely off the screen.

Finally, before playing the animation, the cycle count is set to INDEFINITE which means that as soon as the logo animation is complete, it will be automatically started again from the beginning, and keep doing so indefinitely.

That’s it, other transitions are equally as easy such as fading and scaling. Animations can also be chained together to run in sequence or set up to be run in parallel with other transitions.

Once again, from last month’s post, here’s a video showing animations in action. In particular, there are 2 logos which are animated on the Scene’s background that are scrolling in opposite directions across the screen.

 

Have fun!

twitter: @RobTerpilowski

Written with StackEdit.