Swing and JavaFX: working with JFXPanel

I soon will have to deal with JavaFX in a Swing based fat client
– oh sorry, of course I meant “multi-layered rich-client” ;-)!

So this brings me to have a look at the JFXPanel.
The JFXPanel is a javax.swing.JComponent to embed JavaFX content into Swing-UIs. A JFXPanel can be used similar to a JPanel and can be accessed via the EDT as a generic Swing component except dealing with JavaFX component must be done via the JavaFX application thread.

To play around with that stuff I created two similar Panels (Swing + JavaFX) each with a Button, a TextField and a Label and placed them in a JSplitPane and a JFrame:

A closer look inside

To try Swing <-> JavaFX interoperability the buttons actions are setting the text from TextField to JLabel and vice versa.
There is nothing special with the JPanel as its handles generic Swing stuff, but with the JFXPanel containing the JavaFX controls:

public class SwingFXPanel extends JFXPanel {

    private Button testButton;
    private TextField testTextField;
    private Label testLabel;
    private VBox pane;

    public SwingFXPanel() {
        init();
    }

    private void init() {
        testButton = new Button("I am a JavaFX Button");
        testTextField = new TextField();
        testLabel = new Label("empty");
        pane = new VBox();
        pane.setAlignment(Pos.CENTER);
        pane.getChildren().addAll(testTextField, testButton, testLabel);
        Platform.runLater(this::createScene);
    }

    private void createScene() {
        Scene scene = new Scene(pane);
        setScene(scene);
    }

    public Button getTestButton() {
        return testButton;
    }

    public TextField getTestTextField() {
        return testTextField;
    }

    public Label getTestLabel() {
        return testLabel;
    }
}

Important here: to add the Scene to the JFXPanel within the JavaFX Application Thread:

Platform.runLater(this::createScene);

If you call

createScene()

from another thread you get a Runtime-Exception:

java.lang.IllegalStateException: Not on FX application thread; currentThread = AWT-EventQueue-0

Also each interaction with JavaFX related stuff must be placed on the JavaFX Application Thread:

E.g.:

Platform.runLater(() -> {
   swingFXPanel.getTestLabel().setText(swingPanel.getTestTextField().getText());
});
public class InteropFrame extends JFrame {

    private JSplitPane centralSplitPane;
    private SwingPanel swingPanel;
    private SwingFXPanel swingFXPanel;

    public InteropFrame(){
        init();
    }

    private void init() {
        setTitle("Swing <-> JavaFX Interoperatbiliy");
        setSize(500, 500);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new BorderLayout());

        centralSplitPane = new JSplitPane();
        centralSplitPane.setDividerLocation(0.5);
        centralSplitPane.setResizeWeight(0.3);
        
        swingPanel = new SwingPanel();
        swingFXPanel = new SwingFXPanel();

        swingPanel.getTestButton().addActionListener((ActionEvent e) -> {
            Platform.runLater(() -> {
                swingFXPanel.getTestLabel().setText(swingPanel.getTestTextField().getText());
            });
        });


        swingFXPanel.getTestButton().setOnAction((javafx.event.ActionEvent t) -> {
            swingPanel.getTestLabel().setText(swingFXPanel.getTestTextField().getText());
        });

        centralSplitPane.setLeftComponent(swingPanel);
        centralSplitPane.setRightComponent(swingFXPanel);

        add(centralSplitPane, BorderLayout.CENTER);
    }
}

Alternatively also dealing with FXML is quite simple:

public class SwingFXMLPanel extends JFXPanel {

    @FXML
    private Button testButton;
    @FXML
    private TextField testTextField;
    @FXML
    private Label testLabel;

    private VBox rootPane;

    private URL fxmlResource;
    
    public SwingFXMLPanel(URL fxmlResource){
        this.fxmlResource = fxmlResource;
        init();
    }

    private void init(){
        rootPane = new VBox();
        FXMLLoader loader = new FXMLLoader(fxmlResource);
        loader.setController(this);
        loader.setRoot(rootPane);
        try {
            loader.load();
        } catch (IOException ex) {
            Logger.getLogger(SwingFXMLPanel.class.getName()).log(Level.SEVERE, null, ex);
        }

        testButton.setText("I am a JavaFX Button");
        testLabel.setText("empty");

        Platform.runLater(this::createScene);
    }

    private void createScene() {
        Scene scene = new Scene(rootPane);
        setScene(scene);
    }

    public Button getTestButton() {
        return testButton;
    }

    public TextField getTestTextField() {
        return testTextField;
    }

    public Label getTestLabel() {
        return testLabel;
    }

}

For me its very important to get the most possible acceptance of my co-workers to use JavaFX in Swing.
Therefore I want to simplify the specific FX application thread handling.
So maybe this can be achieved if the major difference to using a JPanel is just to add

    private void createScene() {
        Scene scene = new Scene(rootPane);
        setScene(scene);
    }

and to call

  Platform.runLater(this::createScene);

inside the JFXPanel.

You can find the complete example code here.

Recommended Reading

Comments Icon2 comments found on “Swing and JavaFX: working with JFXPanel

  1. Hi Jens, great artical, exactly what i was looking for. One question, is this double colon between this and createScene a syntax error, if so what should it be.

    Platform.runLater(this::createScene);

Discuss

Your email address will not be published. Required fields are marked *