Friday, January 16, 2015

Creating a minimal JavaFX user interface in Java 8

The functional programming features of Java 8 represent a very important improvement to the language. Another important improvement is the replacement of Swing by JavaFX as the primary graphical user interface framework.  I've been using Swing for about a decade, and I've now started looking at JavaFX.

There are some great tutorials on the web about the JavaFX user interface library.  But they often run too long for my attention span.  I prefer enough detail to get a minimal program working, from which I can then tinker on my own.  So in this post I will demonstrate a minimal user interface that shows the basics of how to use Scene Builder, and how to connect the interface built in Scene Builder to some Java code.

To set up Eclipse:
  • Get the all-in-one Eclipse download that has JavaFX already set up.  
  • Make sure JDK 1.8 is installed and set up as the default.  
    • Check Preferences > Java > Installed JREs to be sure.  
  • Also make sure that Preferences > Java > Compiler > JDK Compliance is also set to 1.8.
To set up Scene Builder in Eclipse:
  • Download Scene Builder
  • In Eclipse, go to Preferences > JavaFX and specify the path to the Scene Builder executable.
Once Eclipse is set up, create a new JavaFX project:
  • Go to File > New > Other > JavaFX > JavaFX Project and click "Next"
  • Call the project "minimalfx" and click "Finish"
If you check out the project on the left, it should look something like this:

The Main.java program will contain the following code.  It will run as is, producing an empty window.  The line in red is the code we will need to change in order to incorporate our own interface:

package application;
 
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;


public class Main extends Application {
 @Override
 public void start(Stage primaryStage) {
  try {
   BorderPane root = new BorderPane();
   Scene scene = new Scene(root,400,400);
   primaryStage.setScene(scene);
   primaryStage.show();
  } catch(Exception e) {
   e.printStackTrace();
  }
 }
 
 public static void main(String[] args) {
  launch(args);
 }
}

Creating an interface with interesting behavior requires two things:
  • Creating an XML file describing the interface components.
  • Modifying the above code to interact with the components in the XML file.
To this end, then, we proceed by creating the XML file as follows:
  • Go to File > New > Other > Create FXML Document.
  • Give it the name "Gui".
  • Right-click on it and select "Open with SceneBuilder"
The Scene Builder window will look something like this:


The current interface is specified in the lower left corner, under the "Document" header.  Available components are given in the upper left corner, under the "Library" header, but separated into categories.  Click on "Controls" and drag a Button into the "CENTER" of the BorderPane.  It should then look like this:


Note that the middle area now contains a representation of our interface.  Select Preview > Show Preview in Window to see what it would look like as a running program:


Now let's modify our Main.java code so that it will run this interface: 
  • Save the interface in SceneBuilder.  It is a separate program from Eclipse, so although you can run it from Eclipse its updates are not automatically reflected therein.
  • Refresh the entire project in Eclipse.  
  • Go to Main.java.  As we can see in the code above, it creates an empty BorderPane that does nothing.  We want our newly created BorderPane to be in there instead.  So replace the original BorderPane creation code (in red) with the following:
  •             
    FXMLLoader loader = new FXMLLoader();
    loader.setLocation(Main.class.getResource("Gui.fxml"));
    BorderPane root = (BorderPane) loader.load();
    
  • Run Main. It should now look like this:

Note the 400x400 size.  That's the size set in Main.java, which we can change as we see fit.

Having created the interface using SceneBuilder, and having linked it up with our code, the last thing we will do is add behavior to the Button.  Create a new Java class called Controller.  Then enter the following code:

package application;

import javafx.fxml.FXML;
import javafx.scene.control.Button;

public class Controller {
 @FXML
 private Button clickMe;
 
 private int numClicks;
 
 public Controller() {}
 
 @FXML
 private void initialize() {
  numClicks = 0;
 }
 
 @FXML
 private void clickHandler() {
  numClicks += 1;
  clickMe.setText(numClicks % 2 == 0 ? "Even" : "Odd");
 }
}

Some notes about the code:
  • The @FXML annotation allows these otherwise private elements to be accessed by SceneBuilder.  These items become options we can select within SceneBuilder for our interface.
  • The initialize() method is called when the interface is created.  Typical constructor tasks go there.
  • The JavaFX components often have the same names as the original Java Abstract Windowing Toolkit.  It's important to get the imports right to make sure we have the right components.
Now we're ready to connect the interface to its handler, the Controller class:
  • Back in SceneBuilder, select "Controller" under the Document menu.
  • In the Controller Class box, enter "application.Controller".
  • Now click on the Button we added earlier.  On the right, select the "Code" item.
  • Look for the fx:id field under the Identity header.  This will be near the top of the Code region.
  • There will be a drop-down menu with a single option: clickMe.  
    • Select it.  
    • This binds the object in SceneBuilder to the named object in the Controller class.
  • Under the Main header, go to the "On Action" field.  In the drop-down menu, select clickHandler.  
  • Save the interface in SceneBuilder and refresh the project in Eclipse.
  • SceneBuilder should look like the following at this point:

From within Eclipse, run Main once again.  Once clicking begins, the button should now respond as programmed.

And that's all there is to it.  Happy tinkering!

1 comment: