Using JavaFX in a NetBeans Application – Part 2

Part 1 discussed how to use JavaFX to visualize data and other content in a NetBeans Platform application window. We showed you how to create JavaFX content from a TopComponent using FXML and a FXML controller class.

In this post we describe the communication strategies you can use when integrating JavaFX content within a TopComponent (NetBeans Platform window).

Communication Strategies

The overriding goal in integrating JavaFX content in a TopComponent is to keep the JavaFX scene graph and its controller class encapsulated. Figure 1 shows the TopComponent, which is managed by the NetBeans Platform window system and communicates directly with the NetBeans Platform framework.

Figure 1. Embedding JavaFX in a TopComponent

Figure 1. Embedding JavaFX in a TopComponent

The TopComponent invokes the FXMLLoader, which reads the FXML, builds the JavaFX scene graph, and instantiates the controller class. Any further manipulation of the scene graph from the TopComponent should go through public methods you provide in the controller class. The crooked line in the diagram separating the TopComponent from the JavaFX controller class emphasizes that these methods must be invoked from the JavaFX Application Thread. These strategies keep the JavaFX content isolated and separate from the TopComponent code. Here are the communication strategies you will use.

  • Self-contained events—All events that occur in the JavaFX scene graph are self contained and no communication with the TopComponent is required.

For example, when the user changes the value of the slider in the left window of the rotating 3D box (see Figure 2 in the previous post), the changes are all localized within the JavaFX scene. In the FXML controller class, you bind the box’s rotate property to the slider’s value property. This makes the box rotate when the slider changes.

blueBox.rotateProperty().bind(s.valueProperty());
       
  • One-way communication—The TopComponent initiates a change in the JavaFX scene graph (perhaps due to a user-selected top-level menu item). Any action that affects the JavaFX scene graph must occur on the JavaFX Application Thread. Thus, the TopComponent calls Platform.runLater() to invoke a JavaFX controller method.

For example, here is part of a property change listener that detects modification to a Person object displayed in a JavaFX scene. After some checks, Platform.runLater() updates the JavaFX scene on the JavaFX Application Thread by invoking the controller public method updateEvents() with the new Person object.

    // TopComponent event listener code that updates JavaFX scene
    if (. . .) {
        Platform.runLater(() -> {
            controller.updateEvents(person);
        });
    }

If the controller method returns a value or reference, the TopComponent must wait for the controller method to complete before using it. The Concurrent Library’s CountDownLatch provides a safe and straightforward way to wait for the method to complete. Suppose TopComponent method getImage() provides a snapshot of the JavaFX scene by calling the FXML controller method getImage(). The TopComponent must wait for the completion of the controller’s getImage() method invoked on the JavaFX Application Thread.

        // TopComponent method to get a snapshot of its embedded scene graph
        private BufferedImage getImage() {
            if (controller == null) {
                return null;
            }
            final CountDownLatch latch = new CountDownLatch(1);
            Platform.runLater(() -> {
                // get the JavaFX image from the controller
                // must be in JavaFX Application Thread
                try {
                    image = controller.getImage();
                } finally {
                    latch.countDown();
                }
            });
            try {
                latch.await();
                return image;
            } catch (InterruptedException ex) {
                Exceptions.printStackTrace(ex);
                return null;
            }
        }
  • Two-way communication—Not only can the TopComponent manipulate the JavaFX scene graph, but the JavaFX environment may need to communicate state change back to the TopComponent. In this situation, PropertyChange events fired to interested listeners provide a workable approach that keeps the JavaFX environment isolated from its TopComponent. Listeners may need to be aware when they receive notification events from different threads.

A good example of this two-way communication strategy is a JavaFX form editor. When the user modifies the form, the JavaFX controller class sends a property change event to the listening TopComponent.

@FXML
private void handleKeyEvent(KeyEvent event) {
    if (inSync) {
        inSync = false;
        this.propChangeSupport.firePropertyChange(
                   PROP_PERSONEDITOR_MODIFIED, event, null);
    }
}

In the TopComponent, a property change listener can respond to the above PROP_PERSONEDITOR_MODIFIED property change event. Here, the TopComponent invokes a method modify(), which records that a modification has occurred.

@Override
public void propertyChange(PropertyChangeEvent pce) {
    if (pce.getPropertyName().equals(
         PersonFXEditorController.PROP_PERSONEDITOR_MODIFIED)) {
            if (readyToListen) {
                modify();
            }
    }
}

These communication strategies are discussed in more detail in our book JavaFX Rich Client Programming on the NetBeans Platform.