Practical JavaFX 2, Part 2: Refactoring Swing JPad's basic UI features to JavaFX

Migrate JPad's content pane, menu system, and event handling to JavaFX

Upgrading a Swing-based app to JavaFX gives you access to the modern UI features of a RIA framework, as well as the ability to easily deploy your application in multiple environments. In this article, you'll see for yourself how a Swing-based notepad application's basic UI architecture maps to JavaFX. There are some adjustments involved, but the trade-off in rich UI features and flexible deployment could be worth it.

The first part of this article introduced a Swing-based notepad application. Now I'll introduce its JavaFX counterpart, JPadFX. We'll start with the basic UI architecture of JPadFX, which maps to the features developed for the Swing JPad: border pane, menu system, scene, and stage. I'll also discuss observable lists, which don't map to a Swing counterpart. Finally, I'll introduce JPadFX's event-handling architecture and explain how it differentiates from the approach used in the Swing-based app.

Note that Part 2 focuses on the basic, and essential, features of a JavaFX notepad application. Part 3 introduces the more advanced features of dialog boxes, the clipboard, and drag-and-drop support.

Introducing JPadFX

JPadFX is the JavaFX equivalent of the Swing-based JPad application that I introduced in the first part. JPadFX is more or less identical to the Swing-based notepad, as you can see in Figure 1, a screenshot of JPadFX's user interface.

Figure 1. A screenshot of JPadFX's UI (click to enlarge)

Note that unlike the Swing-based notepad, JPadFX shows no undo function on the UI. JavaFX doesn't support an undo function, and I chose not to introduce a legacy UI dependency into the application. As JavaFX matures the need for such dependencies (or doing without them) will decrease.

Compiling and running JPadFX

You can download the source files for JPadFX anytime: JPad.java, About.java, Alert.java, AreYouSure.java, and the resource file icon.png. After extracting these files to your current directory, execute the following commands to compile JPadFX.java and the other source files, and run JPadFX. The third command tells JPadFX to open and display the contents of JPadFX.java:

javac -cp "C:\Program Files\Oracle\Javafx 2.0 SDK\rt\lib\jfxrt.jar";. JPadFX.java
java -cp "C:\Program Files\Oracle\Javafx 2.0 SDK\rt\lib\jfxrt.jar";. JPadFX
java -cp "C:\Program Files\Oracle\Javafx 2.0 SDK\rt\lib\jfxrt.jar";. JPadFX JPadFX.java

Note that the above commands assume a Windows platform and that you have installed the JavaFX 2.0.2 SDK to C:\Program Files\Oracle\JavaFX 2.0 SDK. This home directory's rt subdirectory contains a lib subdirectory. That subdirectory contains jfxrt.jar, which in turn contains JavaFX API class files. I've appended a dot (representing the current directory) to javac's classpath so that this tool can locate the other source files.

Architecture of JPadFX

JPadFX consists of About, Alert, AreYouSure, and JPadFX classes. JPadFX is the main class, as shown in Listing 1, and the other classes implement custom dialog boxes.

Listing 1. The JPadFX class drives the JPadFX application

// ...
public class JPadFX extends Application
{
   // ...
   @Override
   public void start(final Stage primaryStage)
   {
      // ...
      Application.Parameters params = getParameters();
      List<String> uparams = params.getUnnamed();
      if (uparams.size() != 0)
         doOpen(new File(uparams.get(0)));
   }
   private void doExit(Event e)
   {
      // ...
   }
   private void doNew()
   {
      // ...
   }
   private void doOpen()
   {
      doOpen(null);
   }
   private void doOpen(File file)
   {
      // ...
   }
   private boolean doSave()
   {
      // ...
   }
   private boolean doSaveAs()
   {
      // ...
   }
   private String read(File f) throws IOException
   {
      // ...
   }
   private void write(File f, String text) throws IOException
   {
      // ...
   }
   public static void main(String[] args)
   {
      launch(args);
   }
}

Listing 1 reveals a skeletal framework that is very similar to the original Swing JPad architecture, but there are some differences. Unlike JPad, which extends Swing's JFrame class, JPadFX extends the javafx.application package's abstract Application class. Application declares methods for launching JavaFX applications, for defining the application's lifecycle, and more.

In Listing 1, Application declares the void launch(String... args) class method to launch the application. This method is passed the array of command-line arguments and then instantiates the application class, JPadFX.

Next, Application's void init() method is called on a thread known as the launcher thread. If you wanted to, you could override this method to perform initialization before the application started, but JPadFX doesn't do that.

Sometime after init() returns, Application's void start(Stage primaryStage) method is invoked on the JavaFX application thread. This method is invoked with a javafx.stage.Stage instance and is used to construct and display the UI.

A Stage instance is a container for an application's UI. Because Stage extends the javafx.stage.Window class, Stage instances are also Window instances that can be shown and managed. Applications can have multiple Stages; additional instances can serve as dialog boxes.

The application runs until its last stage window is closed, either by the user or via the javafx.application.Platform class's void exit() class method. JavaFX responds by calling Application's void stop() method on the JavaFX application thread. This method gives an application a chance to release any previously acquired resources. Because JPadFX doesn't need to release any resources, JPadFX does not override stop().

stop() and resource disposal

Use stop() to release resources acquired in init() and/or start(). Because init() runs on the launcher thread and stop() runs on the JavaFX application thread, fields referring to init()-acquired resources must be declared volatile.

After creating JPadFX's UI, start() opens the file identified by the first command-line argument when at least one argument is specified. However, its approach to obtaining this argument differs from the simple approach taken by JPad's constructor.

JavaFX applications can access parameters specified as command-line arguments, unnamed parameters, name/value pairs in JNLP (Java Network Launching Protocol) files, and so on. Application declares an Application.Parameters getParameters() method for accessing all of these possibilities.

Parameters declares Map<String, String> getNamed(), List<String> getRaw(), and List<String> getUnnamed() methods to access all, named, and unnamed parameters. start() calls the latter method because command-line arguments are unnamed.

Creating the UI

JPadFX's start() method creates JPadFX's UI by constructing a scene graph of nodes. A scene graph is a tree of nodes, which represent a UI's visual and nonvisual elements (such as controls and layouts). Collectively, these elements describe a scene. Listing 2 shows how JavaFX 2 organizes the JPadFX UI into a scene.

Listing 2. JPadFX's UI is created in the start() method

// ...
private TextArea ta;
private Label lbl;
// ...
public void start(final Stage primaryStage)
{
   // ...
   BorderPane root = new BorderPane();
   MenuBar mb = new MenuBar();
   Menu mFile = new Menu("File");
   MenuItem miNew = new MenuItem("New");
   KeyCharacterCombination kccNew;
   kccNew = new KeyCharacterCombination("N", KeyCombination.CONTROL_DOWN);
   miNew.setAccelerator(kccNew);
   // ...
   mFile.getItems().add(miNew);
   // ...
   mb.getMenus().add(mFile);
   // ...
   Menu mFormat = new Menu("Format");
   final CheckMenuItem cmiWordWrap = new CheckMenuItem("Word Wrap");
   // ...
   mFormat.getItems().add(cmiWordWrap);
   // ...
   mb.getMenus().add(mFormat);
   // ...
   root.setTop(mb);
   root.setCenter(ta = new TextArea());
   // ...
   root.setBottom(lbl = new Label("JPadFX 1.0"));
   Scene scene = new Scene(root, 400, 400, Color.WHITE);
   primaryStage.setScene(scene);
   primaryStage.setTitle(DEFAULT_TITLE);
   // ...
   primaryStage.show();
   // ...
}

In Listing 2 we see JPadFX's start() method first instantiate javafx.scene.layout.BorderPane, a container managed by a java.awt.BorderLayout-style layout manager. JPadFX uses BorderPane to organize its controls and to serve as the root node of its scene graph.

BorderPane reveals a significant difference between JavaFX and Swing. Unlike Swing's BorderLayout, which focuses on layout management only, BorderPane combines layout management with containment. JavaFX blends these concepts to simplify UI development. Another blending example is javafx.scene.layout.GridPane, which is JavaFX's counterpart to java.awt.GridLayout.

Menus in JavaFX versus Swing

After instantiating BorderPane, start() creates the menu bar and menus by working with the javafx.scene.control.MenuBar, javafx.scene.control.Menu, javafx.scene.control.MenuItem, and javafx.scene.control.CheckMenuItem classes.

Although the JavaFX code in Listing 2 shows some similarities to a Swing-based menu structure, you've surely noted some differences. For one, we would typically use a javax.swing.KeyStroke instance to set a menu item's accelerator in Swing, as shown in Listing 3.

Listing 3. Setting a menu item's accelerator in Swing

miNew.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.CTRL_MASK));

But in JavaFX we use javafx.scene.input's KeyCharacterCombination and KeyCombination classes to acquire the accelerator key, as shown in Listing 4.

Listing 4. Using KeyCharacterCombination as a menu item accelerator in JavaFX

KeyCharacterCombination kccNew;
kccNew = new KeyCharacterCombination("N", KeyCombination.CONTROL_DOWN);
miNew.setAccelerator(kccNew);

More differences between JavaFX and Swing emerge when we start adding menu items to a menu, and menus to a menu bar. Whereas Swing's JMenu and JMenuBar classes provide various add() methods for this task, JavaFX's Menu and MenuBar classes are not as direct.

Observable lists

JavaFX's Menu and MenuBar store their respective MenuItem and Menu instances in observable lists, which are java.util.List<E> implementations that let listeners track changes as they occur, such as an item being added to or removed from a List.

Observable lists are returned by calling Menu's ObservableList<MenuItem> getItems() and MenuBar's ObservableList<Menu> getMenus() methods. After obtaining the observable list, the Menu or MenuItem instance will be added by calling the list's add() method.

Observable lists are used with JavaFX's binding feature for keeping parts of a UI in sync with other parts of the UI or a data model. For example, Menu stores its menu items in an observable list so that it can dynamically update the menu whenever it's notified that the list has changed at runtime.

JavaFX Scene

After creating and configuring the menu bar, start() adds this control to the top portion of the borderpane by calling BorderPane's void setTop(Node n) method. Similar void setCenter(Node n) and void setBottom(Node n) methods are called to add newly created javafx.scene.control.TextArea and javafx.scene.control.Label instances to the borderpane.

JavaFX provides the javafx.scene.Scene class to serve as a container for a scene graph. Each of this class's constructors requires that its first argument be a reference to the node that serves as the scene graph's root, which happens to be the BorderPane instance. start() invokes the Scene(Parent root, double width, double height, Paint fill) constructor class to store this root node along with scene dimensions and a background color:

  • javafx.scene.Parent is the base class for those nodes that have child nodes.
  • javafx.scene.paint.Paint is the base class for a color or gradient used to fill shapes and backgrounds when rendering a scene.

JavaFX Stage

In JavaFX, scenes are managed by stages. start() assigns the Scene instance to the primaryStage instance by invoking Stage's void setScene(Scene scene) method. It then assigns a title to the stage by invoking Stage's void setTitle(String title) method.

Finally, start() displays the primary stage along with its scene by invoking Stage's void show() method.

You can think of the primary stage as JavaFX's counterpart to a Swing application's frame window. Additional stages can serve as dialog boxes, which I discuss in Part 3. The scene that is added to the primary stage is JavaFX's counterpart to Swing's content pane.

Event handling in JavaFX

We've looked at some of the differences between JavaFX and Swing, primarily in a menu system context. Now we'll conclude Part 2 by considering something more significant, how JavaFX handles events. This will be an overview based on the limited features of the JPad demo application. See Resources for a more in-depth discussion of JavaFX 2's event handling framework.

1 2 Page 1
Page 1 of 2