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.

Call to verify responsiveness of your JavaFX UIs

Recently Jim Weaver installed my little picture indexing app “picmodo” for a demo on his Surface Pro and the GUI became a pice of junk.

Obviously the basic font size of JavaFX on a Windows Tablet is to high:

picmodo-main-crap

user-editor-crap

picmodo-options-crap

I assume, too absolute sizes and positions are not always the best idea, even if the resize behaviour works like expected and the UI it looks quite ok on the developers machine… 😉

So I suppose to frequently give it a try by simply adding this to your CSS to increase default font size a bit and change the button sizes to verify the responsiveness of your layouts:


.root{
-fx-font-size: 16pt;
}

.button, .toggle-button, .choice-box {
-fx-padding: 10px 20px 10px 20px;
}

Somehow that forced me to modify some things. So basically I now prefer to use a combination of VBox, HBox and Region(s) (as a kind of spring-delimiter) for the major layout and to give a bit more respect to “USE_COMPUTED_SIZE” of controls and panes …

picmodo-main-clean

user-editor-clean

picmodo-options-clean