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.Practical JavaFX 2: Read all three partsPart 1: Architecture of a Swing-based notepadPart 2: Refactoring Swing JPad’s basic UI features to JavaFXPart 3: Refactoring Swing JPad’s advanced UI features to JavaFXNote 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 JPadFXJPadFX 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 JPadFXYou 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 FilesOracleJavafx 2.0 SDKrtlibjfxrt.jar";. JPadFX.java java -cp "C:Program FilesOracleJavafx 2.0 SDKrtlibjfxrt.jar";. JPadFX java -cp "C:Program FilesOracleJavafx 2.0 SDKrtlibjfxrt.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 FilesOracleJavaFX 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 JPadFXJPadFX 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 disposalUse 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 UIJPadFX‘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 SwingAfter 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 SwingmiNew.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 JavaFXKeyCharacterCombination 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 listsJavaFX’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 SceneAfter 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 StageIn 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 JavaFXWe’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. Swing presents an event handling framework that’s based on event classes, listener interfaces, and registration methods in component classes. In contrast, JavaFX presents an event handling framework that’s largely based on the javafx.event.Event class and subclasses, the EventHandler<T extends Event> interface, and event-handler properties and property setter methods for setting them.Event is the base class for JavaFX event types. Subclasses describe specific types of events. For example, the javafx.event package contains an ActionEvent class that subclasses Event and describes action events such as button presses. Additional Event subclasses are located in other packages.The EventHandler<T extends Event> interface describes JavaFX’s universal event handler. This interface declares a single handle(T event) method that is invoked to handle an event whose type is the actual type argument passed to formal type parameter T. In contrast, Swing provides many different event listener interfaces (e.g., java.awt.event.ActionListener) with diverse methods for handling events. javafx.scene.Node (the base class for scene graph nodes) and its subclasses declare event-oriented properties with setters and getters. For example, Node declares onKeyTyped and a void setOnKeyTyped(EventHandler<? super KeyEvent> value) setter for registering an event handler with this property. When an event pertaining to this property occurs, it is sent to the registered event handler’s handle() method.Action eventsThe ActionEvent class describes some kind of action event that has occurred. Action events can arise from a button press, an animation sequence ending, or another source. Nodes that support action events provide an onAction property and a void setOnAction(EventHandler<? super ActionEvent> value) setter.JPadFX’s menu system is one source of action events. For example, an action event is fired when the user selects a menu item to which an event handler that responds to this event has been registered. Listing 5 shows you how JPadFX registers an action event handler with the New menu item.Listing 5. An action event handler is registered with the New menu itemEventHandler<ActionEvent> ehae; ehae = new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent ae) { // ... } }; miNew.setOnAction(ehae); Listing 5 first instantiates the anonymous class that implements EventHandler<ActionEvent> and then assigns this instance to the New menu item by invoking MenuItem‘s void setOnAction(EventHandler<ActionEvent> value) method. When the user selects New, the event handler’s handle() method will be called with an ActionEvent object that describes the action event. Menu eventsJPadFX registers menu listeners with its menus that respond to menu events occurring when the menu is about to be shown and when it is being hidden. A menu-specific message is displayed on the status bar when that menu is about to be shown, and JPadFX’s generic message is displayed just as the menu is being hidden.The former case is handled by registering an event handler with Menu‘s onShowing property via the void setOnShowing(EventHandler<Event> value) setter. Similarly, the latter case is handled by registering an event handler with Menu‘s onHiding property via the void setOnHiding(EventHandler<Event> value) setter. All of this is shown in Listing 6.Listing 6. The File menu’s help text is customized for its environmentEventHandler<Event> ehe; ehe = new EventHandler<Event>() { @Override public void handle(Event e) { if (getHostServices().getWebContext() == null) lbl.setText("New doc|Open existing doc|Save changes|Exit"); else lbl.setText("New doc|Open existing doc|Save changes"); } }; mFile.setOnShowing(ehe); ehe = new EventHandler<Event>() { @Override public void handle(Event e) { lbl.setText(DEFAULT_STATUS); } }; mFile.setOnHiding(ehe); The first handle() method determines the environment in which JPadFX is running before displaying the help text. If JPadFX is running within a web page, Exit is not included on the status bar because the File menu does not have an Exit menu item in this context: it does not make sense to exit JPadFX in a web context.JPadFX learns about its host environment by calling Application‘s HostServices getHostServices() method. This method returns a javafx.application.HostServices instance, whose methods let JavaFX learn about and interact with the environment. For example, a call to netscape.javascript.JSObject getWebContext() would return the JavaScript handle of the enclosing Document Object Model window of the web page containing your application. If the application isn’t embedded in a web page, the return value will be null.Window eventsThe javafx.stage.WindowEvent class describes events related to showing, hiding, or attempting to close a window. JPadFX registers with the primary stage an event handler that responds to a close request by calling Window‘s void setOnCloseRequest(EventHandler<WindowEvent> value) method, as shown in Listing 7.Listing 7. doExit(Event) consumes window eventsEventHandler<WindowEvent> ehwe; ehwe = new EventHandler<WindowEvent>() { @Override public void handle(WindowEvent we) { doExit(we); } }; primaryStage.setOnCloseRequest(ehwe); // ... private void doExit(Event e) { // ... e.consume(); } The handle() method calls doExit(Event), which factors out common event handling code: the close request can originate from the Exit menu item (when this item is present) or from closing the application via the title bar. doExit(Event) invokes Event‘s void consume() method to prevent the event from propagating to the JavaFX runtime, which would close the window without giving the user an opportunity to save changes.Change listenersAlthough not technically events, change listeners are similar to them. Recall that Swing JPad detects document changes by registering a document listener with the text-area component’s document. In contrast, JPadFX registers a change listener with the text-area control’s length property (see below), assigning true to fDirty whenever the length changes.Listing 8. A change listener detects when a document is dirtyChangeListener<Number> cln; cln = new ChangeListener<Number>() { @Override public void changed(ObservableValue ov, Number oldval, Number newval) { fDirty = true; } }; ta.lengthProperty().addListener(cln); Like JPad, JPadFX only enables the Edit menu’s Cut and Copy menu items when the text area contains selected text. Unlike Swing, which uses a caret listener for this purpose, JavaFX accomplishes this task by registering a change listener with the text-area control’s selected text property, as shown in Listing 9.Listing 9. A change listener detects when a document contains selected textChangeListener<String> cls; cls = new ChangeListener<String>() { @Override public void changed(ObservableValue ov, String oldval, String newval) { if (ta.getSelectedText().length() != 0) { miCopy.setDisable(false); miCut.setDisable(false); } else { miCopy.setDisable(true); miCut.setDisable(true); } } }; ta.selectedTextProperty().addListener(cls); Conclusion to Part 2Refactoring a Swing application to its JavaFX 2.0 equivalent involves migrating basic and advanced UI architectures. So far you’ve learned how JPadFX differs from its Swing JPad counterpart in terms of the use of a border pane for managing controls, menu system differences (including observable lists), the scene and stage, and event handling (including change listeners). In Part 3 we’ll wrap up this tour of JPadFX by looking at how it interprets Swing JPad’s original dialog boxes, clipboard, and drag-and-drop support.Jeff Friesen is a freelance tutor and software developer with an emphasis on Java and Android. In addition to writing Java and Android books for Apress, Jeff has written numerous articles on Java and other technologies for JavaWorld, informIT, Java.net, and DevSource. Jeff can be contacted via his website at TutorTutor.ca. JavaConcurrencySoftware DevelopmentOpen Source