JavaFX - 在其中包含带有事件的 fxml

JavaFX - include fxml with an event in it

我目前正在尝试使用 JavaFX 实现一个非常基本的应用程序,只是为了做一些测试。这里的最终目标是实现一个分成几个部分的接口,每个部分都有自己的.fxm 和控制器。

一开始,我尝试用这种架构开发一个基本的应用程序:

Project explorer

我有一个主 VueGlobale.fxml 文件,其中包含另一个 .fxml 文件 clavier.fxml :

VueGlobale.fxml

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

<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>

<VBox
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="529.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
   <children>
        <fx:include source="clavier.fxml" fx:id="clavier" />
   </children>
</VBox>

clavier.fxml

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controllers.ClavierController">
   <children>
      <Button layoutX="274.0" layoutY="188.0" mnemonicParsing="false" onAction="#ajouterDo" text="Button" />
   </children>
</AnchorPane>

这是 ClavierController.fxml : 包控制器;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import model.Model;

public class ClavierController  {

    private Model model;

    public ClavierController(Model m) {
        this.model = m;
    }

    @FXML
    public void ajouterDo(ActionEvent e){
        System.out.println("Click !");
        this.model.doSomething();
    }
}

型号 封装模型; public class 型号 {

public void doSomething() {
    System.out.println("Model !");
}

}

主要

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{
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("views/VueGlobale.fxml"));

        Parent root = loader.load();
        primaryStage.setTitle("Test");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }

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

问题是:我不知道在我的 main 中该做什么,以便我可以给我的 clavier.fxml 一个 ClavierController(Model m)。 (它与没有参数的控制器一起工作,但是如果我需要精确的参数怎么办?)

这里是 StackTrace,如果它能帮到你的话:

Exception in Application start method
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
    at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at sun.launcher.LauncherHelper$FXHelper.main(Unknown Source)
Caused by: java.lang.RuntimeException: Exception in Application start method
    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication5(LauncherImpl.java:182)
    at java.lang.Thread.run(Unknown Source)
Caused by: javafx.fxml.LoadException: 
/J:/Programming/Telecom%20Nancy/S3/Test/bin/views/clavier.fxml:6
/J:/Programming/Telecom%20Nancy/S3/Test/bin/views/VueGlobale.fxml:9

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
    at javafx.fxml.FXMLLoader.access0(FXMLLoader.java:103)
    at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:932)
    at javafx.fxml.FXMLLoader$InstanceDeclarationElement.processAttribute(FXMLLoader.java:971)
    at javafx.fxml.FXMLLoader$Element.processStartElement(FXMLLoader.java:220)
    at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:744)
    at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
    at javafx.fxml.FXMLLoader.access00(FXMLLoader.java:103)
    at javafx.fxml.FXMLLoader$IncludeElement.constructValue(FXMLLoader.java:1143)
    at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:746)
    at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2707)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2527)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2409)
    at Main.start(Main.java:14)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication12(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait5(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null3(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater4(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null8(WinApplication.java:191)
    ... 1 more
Caused by: java.lang.InstantiationException: controllers.ClavierController
    at java.lang.Class.newInstance(Unknown Source)
    at sun.reflect.misc.ReflectUtil.newInstance(Unknown Source)
    at javafx.fxml.FXMLLoader$ValueElement.processAttribute(FXMLLoader.java:927)
    ... 23 more
Caused by: java.lang.NoSuchMethodException: controllers.ClavierController.<init>()
    at java.lang.Class.getConstructor0(Unknown Source)
    ... 26 more
Exception running application Main

提前感谢您的帮助和抽出时间,祝您愉快。

编辑:

通过包含几个 fxml,我的意思是:

VueGlobale.fxml

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="529.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
   <children>
        <fx:include source="viewtop.fxml" fx:id="top" />
   </children>
<children>
        <fx:include source="clavier.fxml" fx:id="clavier" />
   </children>
<children>
        <fx:include source="viewbottom.fxml" fx:id="bottom" />
   </children>
</VBox>

所以我的 VueGlobale.fxml 中包含三个 .fxml 文件。让我们假设所有这些 .fxml 都有自己的控制器(ClavierController.java、TopController.java、BottomController.java)。所有这些控制器都需要模型。我应该在我的主要工厂做什么?这样的事情不起作用:

import controllers.ClavierController;
import controllers.TopController;
import controllers.BottomController;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import model.Model;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Model m = new Model();

        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("views/VueGlobale.fxml"));
        loader.setControllerFactory(ic -> new ClavierController(m));
        loader.setControllerFactory(ic -> new TopController(m));
        loader.setControllerFactory(ic -> new BottomController(m));

        Parent root = loader.load();
        primaryStage.setTitle("Test");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }

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

controllerFactory 和其他 属性 一样 属性。如果你设置它的值,它就有那个值,所以在连续三行代码中将它的值设置为三个不同的东西是没有意义的。

您可以创建一个控制器工厂来简单地检查参数和 returns 合适的控制器:

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Model m = new Model();

        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("views/VueGlobale.fxml"));
        loader.setControllerFactory(ic -> {
            if (ic == ClavierController.class) {
                return new ClavierController(m);
            } else if (ic == TopController.class) {
                return new TopController(m);
            } else if (ic == BottomController.class) {
                return new BottomController(m) ;
            }
            throw new IllegalArgumentException("Unexpected controller type: "+ic.getName());
        });

        Parent root = loader.load();
        primaryStage.setTitle("Test");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }

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

你也可以使用反射来实现像"If the supplied controller class has a constructor taking a model, call that constructor and pass the model in, otherwise call the default constructor":

这样的逻辑
loader.setControllerFactory(ic -> {
    try {
        for (Constructor<?> c : ic.getConstructors()) {
            if (c.getParameterCount() == 1 && c.getParameterTypes()[0]==Model.class) {
                return c.newInstance(m);
            }
        }
        return ic.newInstance();
    } catch (Exception e) {
        // fatal...
        throw new RuntimeException(e);
    }
});

尽管如此,对于您要实现的目标来说,这有点过分了。您只是试图将模型传递给嵌套控制器(通过 <fx:include> 加载的 FXML 文件的控制器)。 documentation 明确为此提供了一种基于注入的方法。

具体来说,如果您的 "main" FXML 将 fx:id 添加到 <fx:include>,则可以将控制器注入到主控制器中。所以你可以这样做:

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="529.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
   <children>
        <fx:include source="viewtop.fxml" fx:id="top" />
   </children>
<children>
        <fx:include source="clavier.fxml" fx:id="clavier" />
   </children>
<children>
        <fx:include source="viewbottom.fxml" fx:id="bottom" />
   </children>
</VBox>

现在为此 FXML 文件定义一个 "main" 控制器:

public class MainController {

    @FXML
    private Parent top ;
    @FXML
    private Parent clavier ;
    @FXML
    private Parent bottom ;

    @FXML
    private TopController topController ;
    @FXML
    private ClavierController clavierController ;
    @FXML
    private BottomController bottomController ;

    private final Model model ;

    public MainController(Model model) {
        this.model = model ;
    }

    @FXML
    public void initialize() {
        topController.setModel(model);
        clavierController.setModel(model);
        bottomController.setModel(model);
    }

    // ...
}

只需使用适当的模型设置方法定义 "nested" 控制器:

public class TopController {

    private Model model ;

    public void setModel(Model model) {
        this.model = model ;
    }

    @FXML
    private void someHandlerMethod(ActionEvent event) {
        model.doSomething();
    }
}

最后加载您的主要 fxml:

Model model = new Model();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/path/to/fxml"));
loader.setController(new MainController(model));
Parent root = loader.load();

一切顺利。

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

    <?import com.jfoenix.controls.*?>
    <?import java.lang.*?>
    <?import javafx.scene.layout.*?>
    <?import javafx.scene.layout.AnchorPane?>

    <AnchorPane fx:id="buttonPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="30.0" prefWidth="700.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.everest.amcu.ButtonPaneController">
        <children>
            <HBox fx:id="hboxButton" alignment="CENTER_LEFT" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" spacing="5.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
                <children>
                    <JFXButton fx:id="btnAdd" maxHeight="1.7976931348623157E308" maxWidth="100.0" prefWidth="50.0" text="Add" HBox.hgrow="ALWAYS" />
                    <JFXButton fx:id="btnEdit" layoutX="10.0" layoutY="10.0" maxHeight="1.7976931348623157E308" maxWidth="100.0" prefWidth="50.0" text="Edit" HBox.hgrow="ALWAYS" />
                    <JFXButton fx:id="btnDelete" layoutX="62.0" layoutY="10.0" maxHeight="1.7976931348623157E308" maxWidth="100.0" prefWidth="50.0" text="Delete" HBox.hgrow="ALWAYS" />
                    <JFXButton fx:id="btnClose" layoutX="114.0" layoutY="10.0" maxHeight="1.7976931348623157E308" maxWidth="100.0" prefWidth="50.0" text="Close" HBox.hgrow="ALWAYS" />
                </children>
            </HBox>
        </children>
    </AnchorPane>



    PaneOne.fxml
    ---------------    
            <?xml version="1.0" encoding="UTF-8"?>
                      <?import javafx.scene.layout.AnchorPane?>
                      <AnchorPane xmlns:fx="http://javafx.com/fxml/1"       fx:controller="com.everest.amcu.PaneOneController">     
             <fx:include source="ButtonPane.fxml" fx:id="buttonPane" /> </AnchorPane>

ButtonPaneController
--------------------

import java.net.URL;
import java.util.ResourceBundle;
import com.jfoenix.controls.JFXButton;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;

public class ButtonPaneController implements Initializable {

    @FXML
    public JFXButton btnClose;

    @FXML
    public JFXButton btnDelete;

    @FXML
    public JFXButton btnAdd;

    @FXML
    public JFXButton btnEdit;

    @FXML
    private AnchorPane buttonPane;

    @FXML
    private HBox hboxButton;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        btnClose.setOnAction(event -> {
            System.out.println("In Button Pane");
        });
        btnAdd.setOnAction(event -> {
            System.out.println("In Button Pane");
        });
    }

    public void hideButton(JFXButton... jfxButton) {
        for (int i = 0; i < jfxButton.length; i++) {
            hboxButton.getChildren().remove(jfxButton[i]);
        }
    }

}

PaneOneController
------------------
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;

public class PaneOneController implements Initializable {

    @FXML
    ButtonPaneController buttonPaneController;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        buttonPaneController.btnAdd.setOnAction(event -> {
            System.out.println("In Pane One");
        });
    }
}

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

public class Main extends Application {

    public static Stage primaryStage;
    public static Label lblTitle;

    @Override
    public void start(Stage primaryStage) {
        try {
            Main.primaryStage = primaryStage;
            Parent root = FXMLLoader.load(Main.class.getResource("view/PaneOne.fxml"));
            Scene scene = new Scene(root);
            scene.getStylesheets().add(Main.class.getResource("view/css/application.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.initStyle(StageStyle.UNDECORATED);
            primaryStage.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

执行此操作的步骤:

  1. 创建 ButtonPane.fxml 并声明锚窗格的 id 和所有 按钮。
  2. 创建 PaneOne.fxml 并使用
    包含 Button.fxml ,重要的是当你包含 fxml 时必须是 声明 id 与 ButtonPane.fxml.
  3. 的锚窗格 id 相同
  4. 为初始化变量创建 ButtonPaneController。
  5. 创建 PaneOneController 并使用声明 ButtonPaneController @FXML注解.
  6. 运行程序,它与按钮的动作事件配合得很好。