Статьи

Swing и JavaFX: работа с JFXPanel

Мне скоро придется иметь дело с JavaFX в толстом клиенте на основе Swing — о, извините, конечно, я имел в виду «многослойный богатый клиент»!

Так что это заставляет меня взглянуть на JFXPanel . JFXPanel является компонентом javax.swing.J для встраивания содержимого JavaFX в Swing-UI. JFXPanel может использоваться аналогично JPanel, и доступ к нему можно получить через EDT как общий компонент Swing, за исключением того, что работа с компонентом JavaFX должна выполняться через поток приложения JavaFX .

Чтобы поиграть с этим, я создал две одинаковые панели (Swing + JavaFX), каждая из которых имеет кнопку, текстовое поле и метку, и поместил их в JSplitPane и JFrame:

Присмотритесь внутрь

Чтобы попробовать взаимодействие Swing <-> JavaFX, действия кнопок устанавливают текст из TextField в JLabel и наоборот.
В JPanel нет ничего особенного, так как он обрабатывает общие вещи Swing, но с JFXPanel, содержащей элементы управления JavaFX:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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;
    }
}

Важно здесь: добавить сцену в JFXPanel в потоке приложений JavaFX:

1
Platform.runLater(this::createScene);

Если вы позвоните:

1
createScene()

из другого потока вы получаете Runtime-Exception:

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

Кроме того, каждое взаимодействие с JavaFX должно быть размещено в потоке приложений JavaFX:

Например:

1
2
3
Platform.runLater(() -> {
   swingFXPanel.getTestLabel().setText(swingPanel.getTestTextField().getText());
});
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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);
    }
}

Кроме того, работа с FXML довольно проста:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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;
    }
 
}

Для меня очень важно получить максимально возможное признание моих коллег использовать JavaFX в Swing.

Поэтому я хочу упростить конкретную обработку потоков приложения FX. Так что, возможно, этого можно достичь, если основное отличие от использования JPanel состоит в том, чтобы просто добавить:

1
2
3
4
private void createScene() {
        Scene scene = new Scene(rootPane);
        setScene(scene);
    }

и позвонить:

1
Platform.runLater(this::createScene);

внутри JFXPanel .

  • Вы можете найти полный пример кода здесь .