Search code examples
swingjavafxfxmlscenebuilderfxmlloader

Embedding a Swing content into a JavaFX application with FXML file and scene builder


I'm trying to put one component written in Swing (it's JPanel) into a JavaFX application. I managed to do it using code showed on this website: click here

But I'm building my application using FXML file and scene builder. I was trying to put my Swing component on a Pane container with a SwingNode (like it's showed on the picture below):

scene builder

But unfortunately, I didn't manage to do it. I was looking everywhere and I didn't find anything useful in my case.

I'm quite new to JavaFX and it's really important for me to use scene builder. Is that what I'm trying to do possible at all?

My trials:

Main.JAVA

package application;


import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("Letter Recognition with Neural Networks");
        primaryStage.setScene(new Scene(root, 1150, 650));
        primaryStage.setResizable(false);
        primaryStage.show();
    }

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

}

Controller.JAVA

package application;

import application.gui.DrawingPanel;
import javafx.embed.swing.SwingNode;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;

import javax.swing.*;
import java.net.URL;
import java.util.ResourceBundle;

public class Controller implements Initializable {

    @FXML
    private Pane pane;

    @FXML
    private SwingNode swingNode;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        swingNode = new SwingNode();
        pane = new Pane();

        createAndSetSwingDrawingPanel(swingNode);
        pane.getChildren().add(swingNode);
    }

    public void createAndSetSwingDrawingPanel(final SwingNode swingNode) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                swingNode.setContent(new DrawingPanel(400, 400 , 20));
            }
        });
    }
}

FXML file:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.embed.swing.SwingNode?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.text.Font?>

<GridPane gridLinesVisible="true" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.60">
  <columnConstraints>
    <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
    <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
  </columnConstraints>
  <rowConstraints>
    <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
    <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
    <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
  </rowConstraints>
   <children>
      <Pane fx:id="pane" prefHeight="200.0" prefWidth="200.0">
         <children>
            <SwingNode fx:id="swingNode" />
         </children>
      </Pane>
      <Button mnemonicParsing="false" prefHeight="39.0" prefWidth="80.0" text="Click" GridPane.rowIndex="2">
         <font>
            <Font size="18.0" />
         </font>
      </Button>
   </children>
</GridPane>

Solution

  • The swing node is already a child of the pane, because in the FXML you have the <SwingNode> element inside the <children> element, inside the <Pane> element. So you should not add it to the child nodes of the pane a second time in the controller.

    Furthermore, the FXMLLoader is going to initialize the pane and swingNode fields in the controller to the values created corresponding to the FXML file (that is the whole point of annotating them @FXML). It is always a mistake to initialize fields that are annotated @FXML.

    So all you need in the initialize method is

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        createAndSetSwingDrawingPanel(swingNode);
    }
    

    Finally, you seem to be missing the <fx:controller> attribute from your FXML file. In Scene Builder, expand the "controller" panel in the far bottom left, and enter application.Controller in the "Controller Class" field.