Donnerstag, 11. Dezember 2014

JavaFX Swing Bridging

Introduction


In this study the possibility to brigde Swing components in JavaFX is investigated. Java Swing is a Java framework for the development of RCP ( Rich Client Platform ) applications. In the light of Oracle having announced, that Java FX is the new common UI framework*, it should be examined, how existing UI components of Swing can be reused in an JavaFX environment.

Current Java Version: JDK1.8.0_25

JavaFX



JavaFX is a framework for platform independend rich client applications. Since JDK 7u6 JavaFX is part of the Java runtime and Java development kit. It is meant to supersede Swing as Javas UI technology. 



Scene Graph*



The elements of a JavaFX UI are organized in a tree structure, also called scene graph. The JavaFX Scene Graph API makes graphical user interfaces easier to create, especially when complex visual effects and transformations are involved. A scene graph is a tree data structure, most commonly found in graphical applications and libraries such as vector editing tools, 3D libraries, and video games. The JavaFX scene graph is a retained mode API, meaning that it maintains an internal model of all graphical objects in your application. At any given time, it knows what objects to display, what areas of the screen need repainting, and how to render it all in the most efficient manner. Instead of invoking primitive drawing methods directly, you instead use the scene graph API and let the system automatically handle the rendering details. This approach significantly reduces the amount of code that is needed in your application.

The individual items held within the JavaFX scene graph are known as nodes. Each node is classified as either a branch node (meaning that it can have children), or a leaf node (meaning that it cannot have children). The first node in the tree is always called the root node, and it never has a parent. See a general inheritance diagram in Figure 1.
Figure 1: Tree Structure


The JavaFX API defines a number of classes that can act as root, branch or leaf nodes. When substituted with actual class names, this same figure might resemble that shown in Figure 2 in a real application.
Figure 2: JavaFX Scene Graph
 
In Figure 2, a Group object acts as the root node. The Circle and Rectangle objects are leaf nodes, because they do not (and cannot) have children. The Region object (which defines an area of the screen with children than can be styled using CSS) is a branch node that contains two more leaf nodes (Text and ImageView). Scene graphs can become much larger than this, but the basic organization — that is, the way in which parent nodes contain child nodes — is a pattern that repeats in all applications.



JavaFX Threading



The elements of a JavaFX scene graph are managed in the so called JavaFX Application Thread. This means, that changes, e.g. displaying a text in a text field, are processed by the JavaFX Application Thread. If a long running calculation is being processed in another thread and the result should be displayed at the JavaFX UI, is has to be delivered back to the JavaFX Application Thread. For this, JavaFX provides the static method javax.application.Platform.runLater( Runnable ).  The commands within the run() method of the Runnable argument are being processed at the JavaFX Application Thread.
Example 1 shows a background thread delivering a calculated result back to the JavaFX UI. If the background thread calles the TextField directly, JavaFX will throw an  java.lang.IllegalStateException: Not on FX application thread.



public class JavaFxBackgroundCalculator extends Application{

    private TextField textField;
   
    public static void main(String[] arg0){
         Application.launch(arg0);
    }
   
    @Override
    public void start(Stage stage) throws Exception {
        
         Button button = new Button("Calculate a while");
         button.setOnAction(new EventHandler<ActionEvent>() {
               public void handle(ActionEvent evt) {
                     calculate();
               }
         });
        
         textField = new TextField();
        
         HBox hBox = new HBox();
         hBox.setPadding(new Insets(10));
         hBox.getChildren().add(textField);
         hBox.getChildren().add(button);
        
         Scene scene = new Scene(hBox);
         stage.setScene(scene);
         stage.show();
    }
   
    private void calculate(){
        
         Runnable calculation = new Runnable() {
               public void run() {
                    
                     int duration = 1000; // 10 sec
                     for(int i = 0; i <= 10; i++){
                           String message = duration / 100.0 * i + "%";
                          
                           //back to JavaFX Application Thread
                           Platform.runLater(new Runnable() {
                                 public void run() {
                                       textField.setText(message);
                                 }
                           });
                          
                           try {
                                 Thread.sleep(100);
                           } catch (InterruptedException e) {
                                 throw new RuntimeException(e);
                           }
                     }
               }
         };
        
         new Thread(calculation).start();
    }
}

     Example 1


For a convenient usage of background processes, JavaFX provides the package javafx.concurrent. It contains implementations of the known Worker, FutureTask und Services*, which are bridging to the JavaFX Application Thread internally.


Embedding Swing in JavaFX*



JavaFX 8 introduces the SwingNode class, which is located in the javafx.embed.swing package. This class is a JavaFX Node implementation and can be added to the Scene Graph as any other Node object. SwingNode enables the embedding of Swing content in a JavaFX application. To specify the content of the SwingNode object, the setContent method must be called, which accepts an instance of the javax.swing.JComponent class. The setContent method can be called on either the JavaFX application thread or event dispatch thread (EDT). However, to access the Swing content, it must be ensured that the code runs on EDT, because the standard Swing threading restrictions apply.

The code shown in Example 2 illustrates the general pattern of using the SwingNode class.

import javafx.application.Application;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javax.swing.JButton;
import javax.swing.SwingUtilities;

public class SwingFx extends Application {

    @Override
    public void start (Stage stage) {
        final SwingNode swingNode = new SwingNode();

        createSwingContent(swingNode);

        StackPane pane = new StackPane();
        pane.getChildren().add(swingNode);

        stage.setTitle("Swing in JavaFX");
        stage.setScene(new Scene(pane, 250, 150));
        stage.show();
        }

    private void createSwingContent(final SwingNode swingNode) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                swingNode.setContent(new JButton("Click me!"));
            }
        });
    }
}
     Example 2

When executed, this code produces the output shown in Figure 3. 

Figure 3: SwingButton In JavaFx


Interoperability between JavaFX and Swing


When combining JavaFX and Swing in one Application, it has to be minded, that both components run in their own thread. Should a Swing component be called from JavaFX it has to happen in the Swing thread. The other way round a JavaFX has to be called from Swing withing the JavaFX Application Thread. Example 3 shows the mutual call of JavaFX to Swing and vice versa using javafx.application.Platform.runLater ( Runnable ) and javax.swing.SwingUtilities.invokeLater ( Runnable ).

public class SwingInFx extends Application {

    private TextField fxTextField;
    private JTextField swingTextField;

    public static void main(String[] args) {
         launch(args);
    }

    @Override
    public void start(Stage stage) {

         HBox box = new HBox();
         createFXContent(box);

         SwingNode swingNode = new SwingNode();
         createSwingContent(swingNode);

         GridPane pane = new GridPane();
         pane.add(box, 1, 1);
         pane.add(swingNode, 1, 2);

         stage.setTitle("Swing in JavaFX");
         stage.setScene(new Scene(pane, 250, 150));
         stage.show();
    }

    private void createFXContent(HBox box) {
         fxTextField = new TextField("FX text");

         Button fxButton = new Button("Click FX");
         fxButton.setOnAction(new EventHandler<javafx.event.ActionEvent>() {
               public void handle(javafx.event.ActionEvent evt) {
                     setSwingText("FX clicked");
               }
         });

         box.getChildren().add(fxTextField);
         box.getChildren().add(fxButton);
    }

    private void createSwingContent(final SwingNode swingNode) {

         SwingUtilities.invokeLater(new Runnable() {
               @Override
               public void run() {

                     swingTextField = new JTextField("Swing Text");

                     JButton swingButton = new JButton("Click Swing");
                     swingButton.addActionListener(new ActionListener() {
                           public void actionPerformed(java.awt.event.ActionEvent e) {
                                 setFXText("Swing clicked");
                           }
                     });

                     JPanel panel = new JPanel(new GridLayout(1, 2));
                     panel.add(swingTextField, BorderLayout.NORTH);
                     panel.add(swingButton, BorderLayout.NORTH);
                     swingNode.setContent(panel);
               }
         });
    }

    private void setFXText(String text) {
         Platform.runLater(new Runnable() {
               public void run() {
                     fxTextField.setText(text);
               }
         });
    }

    private void setSwingText(String text) {
         SwingUtilities.invokeLater(new Runnable() {
               public void run() {
                     swingTextField.setText(text);
               }
         });
    }
}
     Example 3 

When run, this code produces the output shown in Figure 4.

Figure 4: JavaFX Swing Interoperability

This application consists of two rows. The upper one consist of JavaFX components, the lower one consists of Swing components. Clicking on the JavaFX button will adjust the Swing textfield and vice versa .

However, since two threads are running in this application, the rendering of the UI works not properly.
Usually, right after the start the swing components are not yet rendered, as shown in Figure 5

 
Figure 5: Swing in JavaFX - Thread Problems

There has to be some event first, which initiates the swing grafical renderer, like a mouse move over the swing button or a mouse click on the swing text field. To avoid such behaviour, a single thread option was build into JavaFX since version 8*. Starting the application with vm argument -Djavafx.embed.singleThread=true should execute all swing command on the JavaFX application thread. This feature is still experimental and not yet officially documentent. Also it seems not do what it promises. Starting the same sample application with that option: java -Djavafx.embed.singleThread=true de.psi.pen.java8.test.SwingInFx will cause no rendering at all as shown in the next figures. After dragging the application window, causing a rerendering, only the swing components come to light.

Figure 6: Swing in JavaFX - SingleThread 1

Figure 7: Swing in JavaFX - SingleThread 2


Embedding JavaFX in Swing


JavaFX provides also a way of being integrated into Swing applications. The javafx.embed.swing.JFXPanel is a javax.swing.JComponent implementation, which can be added any swing/awt java.awt.Container, like Panels, Windows, Frames or JDialogs. For JavaFX the JFXPanel acts as a Stage, at which the Scene, containing the Scene Graph, must be set. Example 4 shows how JavaFX content can be embedded into a Swing application. Also in this case the different threads must be kept in mind. Any adjustments to JavaFX components must happen within the JavaFX Application Thread.



public class FxInSwing {

    private static void initAndShowGUI() {
        
         // This method is invoked on the EDT thread
         JLabel swingLabel = new JLabel("This is Swing");
        
         JFXPanel fxPanel = new JFXPanel();
         Platform.runLater(new Runnable() {
               public void run() {
                     Scene scene = createScene();
                     fxPanel.setScene(scene);
               }
         });
        
         Panel panel = new Panel(new BorderLayout());
         panel.add(swingLabel, BorderLayout.NORTH);
         panel.add(fxPanel, BorderLayout.CENTER);
        
         JFrame frame = new JFrame("Swing Application");
         frame.add(panel);
         frame.setSize(300, 200);
         frame.setVisible(true);
         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    private static Scene createScene() {
         Group root = new Group();
         Scene scene = new Scene(root, Color.ALICEBLUE);
         Text text = new Text();

         text.setX(40);
         text.setY(100);
         text.setFont(new Font(25));
         text.setText("Welcome JavaFX!");

         root.getChildren().add(text);

         return (scene);
    }

    public static void main(String[] args) {
         SwingUtilities.invokeLater(new Runnable() {
               @Override
               public void run() {
                     initAndShowGUI();
               }
         });
    }
}

     Example 4


When executed, this code produces the output shown in Figure 8.

 
Figure 8: JavaFX in Swing


Conclusion

 

Swing components can be easily integrated into JavaFX applications with javafx.embed.swing.SwingNode. Like in any other Java application, the Swing component must be called within the Swing thread through SwingUtilities.invokeLater. The SwingNode, as part of JavaFXs Scene Graph, is responsible for rendering its contained Swing component and forwardes all input and focus events from the JavaFX UI to the contained JComponent. However, the documentain states, that no heavyweight components should be contained in a SwingNode, because rendering problems might occur*. How much weight a Swing component must gain until display issues can be expected is shown in Example 3: It can happen right from the beginning! Besides the official Oracle documentation, not much feasible experience can be found on the web at the moment, and few blogs rather tend to advise, not to use this feature at the moment*. The fact, that Oracle is still dealing with threading issues ( SingleThread experiment) between the two worlds should be minded when more effort into Swing/JavaFX bridging   is being considered.