JavaFX integration in Swing

2020-02-16 01:56发布

问题:

I'm new to using JavaFX and I'm having some issues figuring out how to integrate an fxml file, in my swing application. Reading online I've found a way to load the fxml file,and displaying it on screen following this guide: Integrating JavaFX into Swing Applications

what my code is supposed to do is to load the fxml file, that I've started to design with JavaFX Scene Builder, add the JFXPanel to a JPanel, and return it through a method call. this then gets added to a tabbedPanel in the main application (so there might be multiple distinct JFXPanel open at the same time).

I've kind of figured out some of the problems, but I'm not sure if how to continue. I was able to run one panel, but as in the tutorial I posted, static methods are used, and so I was unable to have multiple distinct panels. I made some changes, but now I'm completely stuck.

This is my current FXML:

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.Pane?>

<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="GUI.fcmModeler.fcmPanel">
   <children>
      <AnchorPane prefHeight="25.0" prefWidth="1000.0">
         <children>
            <Button fx:id="addComponent" layoutX="161.0" mnemonicParsing="false" onMouseClicked="#addComponent" prefHeight="25.0" prefWidth="600.0" text="+ Add Component" textAlignment="CENTER" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
         </children>
      </AnchorPane>
      <SplitPane dividerPositions="0.1713426853707415" layoutY="25.0" prefHeight="577.0" prefWidth="1000.0">
        <items>
          <AnchorPane fx:id="leftPane" maxWidth="160.0" minHeight="0.0" minWidth="160.0" prefHeight="575.0" prefWidth="160.0" />
          <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="546.0" prefWidth="683.0" />
        </items>
      </SplitPane>
   </children>
</Pane>

And this is my controller class:

package GUI.fcmModeler;

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;

import javax.swing.*;
import java.io.IOException;
import java.util.Random;

public class fcmPanel extends JPanel {

    private JPanel frame;
    private static Group root=new Group();
    private boolean firstRun=true;
    Scene scene=null;

    private void initAndShowGUI() {
        // This method is invoked on the EDT thread
        final JFXPanel fxPanel = new JFXPanel();
        frame.add(fxPanel);
        frame.setSize(1200, 1200);
        frame.setVisible(true);

        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                initFX(fxPanel);
            }
        });

        frame.add(fxPanel);
        frame.setVisible(true);
    }

    private void initFX(JFXPanel fxPanel) {
        // This method is invoked on the JavaFX thread
        Scene scene = createScene();
        fxPanel.setScene(scene);
    }

    private Scene createScene() {
        //if(firstRun){
            //root = new Group();
            if(scene==null) {
                scene = new Scene(root, Color.ALICEBLUE);
            }
            try {
                Node newLoadedPane = FXMLLoader.load(fcmPanel.class.getResource("/fxml/fcmPanel.fxml"));
                root.getChildren().add(newLoadedPane);
            } catch (IOException e) {
                System.out.print("the file doesn'e exist.\n");
                e.printStackTrace();
            }
        //}

        return (scene);
    }

    public JPanel returnPanel() {
        System.out.println("returnpanel");
//        if(frame==null){
//            frame = new JPanel();
//        }
        //initFcmPanel();
        return frame;
    }

    public fcmPanel(){
        frame = new JPanel();
        //root=g;
        initFcmPanel();
    }


    public void initFcmPanel() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                initAndShowGUI();
            }
        });
    }

    public void addComponent(){
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                System.out.println("added component2");
                Circle circle = new Circle(new Random().nextInt(50),Color.BLUE);
                root.getChildren().add(circle);
                circle.relocate(new Random().nextInt(600),new Random().nextInt(600));
            }
        });

    }

}

The JFXPanel get added to the tabbed panel through this call:

tabbedPanel.addTab(newTabName , null,new fcmPanel().returnPanel(), filePath  );

As it is right now, I'm facing the following problem

Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Group@3e0f3500[styleClass=root]is already set as root of another scene

I understand the error message,but I don't know how to solve it.

Thank you in advance.

回答1:

I am not sure what you are trying to achieve. The code posted can not be run as posted, so I created a quick and dirty mcve version of your code which runs with no errors, in hope it will help you see what's wrong or clarify your need :

import java.awt.Dimension;
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.paint.Color;

public class FcmPanel {

    private static Group root=new Group();
    Scene scene=null;

    private void initAndShowGUI() {

        // This method is invoked on the EDT thread
        final JFXPanel fxPanel = new JFXPanel();
        fxPanel.setPreferredSize(new Dimension(600, 400));
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Platform.runLater(()-> initFX(fxPanel));

        JTabbedPane tabbedPanel = new JTabbedPane();
        tabbedPanel.addTab("Just an emty tab", new JPanel() );
        tabbedPanel.addTab("FcmPanel", fxPanel );
        frame.add(tabbedPanel);
        frame.pack();
        frame.setVisible(true);
    }

    private void initFX(JFXPanel fxPanel) {
        Scene scene = createScene();
        fxPanel.setScene(scene);
    }

    private Scene createScene() {

        if(scene==null) {
            scene = new Scene(root, Color.ALICEBLUE);
        }
        try {
            Node newLoadedPane = FXMLLoader.load(getClass().
                    getResource("FcmPanel.fxml"));
            root.getChildren().add(newLoadedPane);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return (scene);
    }

    public static void main(String[] args) {
        FcmPanel p = new FcmPanel();
        p.initAndShowGUI();
    }
} 

The fxml is essentially the same. Just changed the Button to a Label which requires no controller :

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

<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.Pane?>

<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="1000.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" >
   <children>
      <AnchorPane prefHeight="25.0" prefWidth="1000.0">
         <children>
            <Label fx:id="addComponent" layoutX="161.0" mnemonicParsing="false"  prefHeight="25.0" prefWidth="600.0" text="+ Add Component" textAlignment="CENTER" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
         </children>
      </AnchorPane>
      <SplitPane dividerPositions="0.1713426853707415" layoutY="25.0" prefHeight="577.0" prefWidth="1000.0">
        <items>
          <AnchorPane fx:id="leftPane" maxWidth="160.0" minHeight="0.0" minWidth="160.0" prefHeight="575.0" prefWidth="160.0" />
          <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="546.0" prefWidth="683.0" />
        </items>
      </SplitPane>
   </children>
</Pane>