如何使用不同的控制器将一个 FXML 文件加载到另一个 FXML 文件上

问题描述 投票:0回答:1

我目前正在开发我的第一个 FXML 项目,并尝试实现 MVC 格式。我的项目是构建一个计算器,我的处理方法是使用带有菜单的基本 fxml 文件,当调用 menuItems 的 onAction 时,它们将 FXML 文件加载到我的基本 FXML 文件中的anchorPane 上。 base.fxml 文件现在有控制器,我只包含了一个 MenuBar 及其各自的控制器。我最终想要的是一种加载 fxml 文件并在另一个 fxml 文件(base.fxml)中使用它的函数的方法。当我单击任何菜单项时,它会在 FXMLLoader 行处给出一个以 NullPointerException 结尾的非常长的错误。

这是按下菜单项时调用的 onAction(唯一改变的是根据用户想要的计算器类型调用的文件):

public void modeStandard() throws IOException {
        Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("standard.fxml")));
        anchorPane.getChildren().clear();
        anchorPane.getChildren().add(root);
    }

anchorPane有一个在base.fxml中使用的id(显示在最后)。我所有的 onActions 都在各自的位置调用,并且控制器也被正确分配

作为参考,这是我的base.fxml:

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

<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>

<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" >
    <children>
        <fx:include source="MenuBar.fxml" fx:id="menuBar"/>
        <SplitPane dividerPositions="0.6546822742474916" prefHeight="429.0" prefWidth="600.0">
            <items>
                <AnchorPane fx:id="anchorPane" />
                <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
                    <children>
                        <TableView prefHeight="371.0" prefWidth="200.0">
                            <columns>
                                <TableColumn prefWidth="198.0" text="Résultats précédents" />
                            </columns>
                        </TableView>
                    </children>
                </AnchorPane>
            </items>
        </SplitPane>
    </children>
</VBox>

这是包含在内的menuBar.fxml :

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

<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>

<MenuBar prefHeight="0.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.demo.modele.MenuBarController">
<menus>
    <Menu mnemonicParsing="false" text="Standard">
        <items>
            <MenuItem  onAction="#modeStandard" mnemonicParsing="false" text="Calculatrice standard" />
        </items>
    </Menu>
    <Menu mnemonicParsing="false" text="Scientifique">
        <items>
            <MenuItem onAction="#modeScientifique" mnemonicParsing="false" text="Calculatrice scientifique" />
        </items>
    </Menu>
    <Menu mnemonicParsing="false" text="Graphique">
        <items>
            <MenuItem onAction="#modeGraphique" mnemonicParsing="false" text="Calculatrice graphique" />
        </items>
    </Menu>
    <Menu mnemonicParsing="false" text="Programmeur">
        <items>
            <MenuItem onAction="#modeProgrammeur" mnemonicParsing="false" text="Calculatrice programmeur" />
        </items>
    </Menu>
    <Menu mnemonicParsing="false" text="Conversion">
         <items>
            <MenuItem onAction="#appelerConvertisseur" mnemonicParsing="false" text="Appeller convertisseur" />
         </items>
    </Menu>
</menus>
</MenuBar>

MenuBarController 基本上是我展示的第一个函数 ( modeStandard() ),针对不同的模式以及anchorPane 和 menuBar 乘以 4 倍,这样我就可以识别它们 :

@FXML
    private AnchorPane anchorPane;
@FXML
    private MenuBar menuBar;

最后,这是我尝试加载到 base.fxml 中的anchorPane 上的示例:

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

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane fx:controller="com.example.demo.modele.Standard" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1">
    <children>
        <VBox alignment="CENTER" layoutX="6.0" prefHeight="371.0" prefWidth="405.0">
            <children>
                <Label alignment="CENTER" text="0" translateX="80.0" />
                <GridPane alignment="CENTER">
                    <columnConstraints>
                        <ColumnConstraints hgrow="SOMETIMES" maxWidth="66.0" minWidth="10.0" prefWidth="68.0" />
                        <ColumnConstraints hgrow="SOMETIMES" maxWidth="66.0" minWidth="10.0" prefWidth="68.0" />
                        <ColumnConstraints hgrow="SOMETIMES" maxWidth="66.0" minWidth="66.0" prefWidth="66.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 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 minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                    </rowConstraints>
                    <children>
                        <Button alignment="BASELINE_CENTER" mnemonicParsing="false" prefHeight="26.0" prefWidth="66.0" text="+" />
                        <Button mnemonicParsing="false" prefHeight="26.0" prefWidth="66.0" text="-" GridPane.columnIndex="1" />
                        <Button mnemonicParsing="false" prefHeight="27.0" prefWidth="66.0" text="x" GridPane.rowIndex="1" />
                        <Button mnemonicParsing="false" prefHeight="27.0" prefWidth="66.0" text="÷" GridPane.columnIndex="1" GridPane.rowIndex="1" />
                        <Button mnemonicParsing="false" prefHeight="26.0" prefWidth="66.0" text="^2" GridPane.rowIndex="2" />
                        <Button mnemonicParsing="false" prefHeight="26.0" prefWidth="66.0" text="√" GridPane.columnIndex="1" GridPane.rowIndex="2" />
                        <Button mnemonicParsing="false" prefHeight="26.0" prefWidth="66.0" text="^-1" GridPane.columnIndex="2" />
                        <Button mnemonicParsing="false" prefHeight="16.0" prefWidth="66.0" text="-x" GridPane.columnIndex="2" GridPane.rowIndex="1" />
                        <Button mnemonicParsing="false" prefHeight="27.0" prefWidth="66.0" text="AC" GridPane.columnIndex="2" GridPane.rowIndex="2" />
                        <Button mnemonicParsing="false" prefWidth="66.0" text="7" GridPane.rowIndex="3" />
                        <Button mnemonicParsing="false" prefWidth="66.0" text="8" GridPane.columnIndex="1" GridPane.rowIndex="3" />
                        <Button mnemonicParsing="false" prefWidth="66.0" text="9" GridPane.columnIndex="2" GridPane.rowIndex="3" />
                        <Button mnemonicParsing="false" prefWidth="66.0" text="4" GridPane.rowIndex="4" />
                        <Button mnemonicParsing="false" prefWidth="66.0" text="5" GridPane.columnIndex="1" GridPane.rowIndex="4" />
                        <Button mnemonicParsing="false" prefWidth="66.0" text="6" GridPane.columnIndex="2" GridPane.rowIndex="4" />
                        <Button mnemonicParsing="false" prefWidth="66.0" text="DEL" GridPane.rowIndex="6" />
                        <Button mnemonicParsing="false" prefWidth="66.0" text="2" GridPane.columnIndex="1" GridPane.rowIndex="5" />
                        <Button mnemonicParsing="false" prefWidth="66.0" text="1" GridPane.rowIndex="5" />
                        <Button mnemonicParsing="false" prefWidth="66.0" text="0" GridPane.columnIndex="1" GridPane.rowIndex="6" />
                        <Button mnemonicParsing="false" prefWidth="66.0" text="3" GridPane.columnIndex="2" GridPane.rowIndex="5" />
                        <Button mnemonicParsing="false" prefWidth="66.0" text="." GridPane.columnIndex="2" GridPane.rowIndex="6" />
                    </children>
                </GridPane>
                <Button   onAction="#notationSansPriorite" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefWidth="200.0" text="=" translateY="-30.0"/>
            </children>
        </VBox>
    </children>
</AnchorPane>

这是错误:

Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at javafx.fxml@19/javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1857)
    at javafx.fxml@19/javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1724)
    at javafx.base@19/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at javafx.base@19/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
    at javafx.base@19/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at javafx.base@19/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at javafx.base@19/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base@19/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at javafx.base@19/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
    at javafx.base@19/javafx.event.Event.fireEvent(Event.java:198)
    at javafx.controls@19/javafx.scene.control.MenuItem.fire(MenuItem.java:459)
    at javafx.controls@19/com.sun.javafx.scene.control.ContextMenuContent$MenuItemContainer.doSelect(ContextMenuContent.java:1385)
    at javafx.controls@19/com.sun.javafx.scene.control.ContextMenuContent$MenuItemContainer.lambda$createChildren$12(ContextMenuContent.java:1338)
    at javafx.base@19/com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:247)
    at javafx.base@19/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at javafx.base@19/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
    at javafx.base@19/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at javafx.base@19/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at javafx.base@19/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at javafx.base@19/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base@19/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base@19/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base@19/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base@19/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base@19/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at javafx.base@19/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at javafx.base@19/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at javafx.base@19/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.base@19/javafx.event.Event.fireEvent(Event.java:198)
    at javafx.graphics@19/javafx.scene.Scene$MouseHandler.process(Scene.java:3894)
    at javafx.graphics@19/javafx.scene.Scene.processMouseEvent(Scene.java:1887)
    at javafx.graphics@19/javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2620)
    at javafx.graphics@19/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:411)
    at javafx.graphics@19/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:301)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at javafx.graphics@19/com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$2(GlassViewEventHandler.java:450)
    at javafx.graphics@19/com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:424)
    at javafx.graphics@19/com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:449)
    at javafx.graphics@19/com.sun.glass.ui.View.handleMouseEvent(View.java:551)
    at javafx.graphics@19/com.sun.glass.ui.View.notifyMouse(View.java:937)
    at javafx.graphics@19/com.sun.glass.ui.mac.MacView.notifyMouse(MacView.java:127)
Caused by: java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:116)
    at java.base/java.lang.reflect.Method.invoke(Method.java:578)
    at com.sun.javafx.reflect.Trampoline.invoke(MethodUtil.java:77)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:578)
    at javafx.base@19/com.sun.javafx.reflect.MethodUtil.invoke(MethodUtil.java:275)
    at javafx.fxml@19/com.sun.javafx.fxml.MethodHelper.invoke(MethodHelper.java:84)
    at javafx.fxml@19/javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1854)
    ... 40 more
Caused by: java.lang.NullPointerException
    at java.base/java.util.Objects.requireNonNull(Objects.java:233)
    at com.example.demo/com.example.demo.modele.MenuBarController.modeStandard(MenuBarController.java:50)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    ... 47 more

这是我对最小可重现示例的最佳尝试。有2个控制器和2个fxml文件,分别命名为BaseController和base.fxml,ButtonController和button.fxml。

首先是HelloApplication:

package com.example.test;

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

import java.io.IOException;

public class HelloApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        Parent root = FXMLLoader.load(getClass().getResource("base.fxml"));
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

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

这里显示的是 BaseController :

package com.example.test;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.layout.VBox;

import java.io.IOException;

public class BaseController {

    @FXML
    private VBox vBoxBase;

    public void importButton() throws IOException {
        Parent root = FXMLLoader.load(getClass().getResource("button.fxml"));
        vBoxBase = (VBox) root;
    }
}

这是base.fxml:

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

<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>

<AnchorPane fx:controller="com.example.test.BaseController" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <MenuBar layoutY="8.0">
        <menus>
          <Menu mnemonicParsing="false" text="Import">
            <items>
              <MenuItem onAction="#importButton" mnemonicParsing="false" text="Import button" />
            </items>
          </Menu>
        </menus>
      </MenuBar>
      <VBox fx:id="vBoxBase" layoutX="250.0" layoutY="100.0" prefHeight="200.0" prefWidth="100.0" />
   </children>
</AnchorPane>

现在这是 ButtonController :

package com.example.test;

public class ButtonController {

    public void buttonFonction() {
        System.out.println("button pressed");
    }
}

最后是button.fxml:

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>


<VBox fx:controller="com.example.test.ButtonController" alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <Button onAction="#buttonFonction" mnemonicParsing="false" text="Imported Button" />
      <Label text="Imported label" />
   </children>
</VBox>

我的目标是,当按下标有“导入按钮”的 MenuItem 时,button.fxml 会导入到 vBoxBase 中。我没有任何错误,但在调试时,显示的所有值均为空,但 VBox 具有正确的子集,但这些值也为空。但是,按钮的 onAction 被保留。

附注这是我的第一篇文章,如果缺少某些内容或者您想完整地查看我的项目,我将很乐意尽我所能来帮助您解决这个问题

javafx model-view-controller controller fxml
1个回答
0
投票

不要将 FXML 注入节点设置为新值

在您的

BaseController
中,您有:

@FXML
private VBox vBoxBase;

这会将

vBoxBase
引用设置为由 FXMLLoader 创建的具有相应
fx:id
的节点。该节点是加载器返回的节点图的一部分。

然后你这样做:

Parent root = FXMLLoader.load(getClass().getResource("button.fxml"));
vBoxBase = (VBox) root;

这会将

vBoxBase
节点设置为与 FXMLLoader 注入的节点不同的节点,并且该节点不是 FXMLLoader 返回的节点层次结构的一部分,因此关联的节点永远不会附加到场景图,并且您永远无法看到或交互有了它。

您可能想做的事情

修改与容器节点关联的child场景图

vBoxBase

Parent root = FXMLLoader.load(getClass().getResource("button.fxml"));
vBoxBase.getChildren().add(root);

您正在对菜单项选择执行此操作,因此每次选择菜单项时,它都会向

VBox
添加一个新值。

但是,也许您只想在菜单选择中的 VBox 中选择一个项目,在这种情况下您可以这样做:

Parent root = FXMLLoader.load(getClass().getResource("button.fxml"));
vBoxBase.getChildren().setAll(root);

如果您不希望每次选择菜单项时都为根创建新的节点图,则可以在

initialize
方法中加载它,并将引用存储在实例变量中,并在使用时引用它。

private Parent buttonRoot;

public void initialize() {
    buttonRoot = FXMLLoader.load(getClass().getResource("button.fxml"));
}

public void importButton() throws IOException {
    vBoxBase.getChildren().setAll(buttonRoot);
}

如果您想使用该模式,那么 FXML 有一种简化它的方法,我不会在这里详细说明,如果有兴趣,请参阅嵌套控制器

如果您只有一个要设置的项目,那么(例如,如果您使用了边框窗格)您可以将其设置在边框窗格中的某个位置,可能将其包含在堆栈窗格中以进行对齐,尽管我赢了不在这里证明这一点。

总体布局建议

  1. 不要硬编码尺寸。
    • 仅在需要时谨慎使用布局提示和首选尺寸。
  2. 让布局窗格为您处理布局。
    • 谨慎使用绝对定位窗格(如 AnchorPane)(或根本不使用)。
  3. 为您的布局选择适当的布局窗格。
    • 例如,传统的菜单驱动应用程序可以使用 BorderPane 而不是 AnchorPane 作为其根。

示例

应用前面提到的设置

vBoxBase
子级的修复程序以及一些常规布局建议,您会得到如下所示的结果:

BaseController.java

package com.example.test;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.layout.VBox;

import java.io.IOException;
import java.util.Objects;

public class BaseController {

    @FXML
    private VBox vBoxBase;

    public void importButton() throws IOException {
        Parent buttonControl = FXMLLoader.load(
                Objects.requireNonNull(
                        getClass().getResource(
                                "button.fxml"
                        )
                )
        );

        // various options for adding content.
        // try enabling them one at a time to test the differences.

        // adds a new button to the vbox
        // every time this method is called.
        vBoxBase.getChildren().add(buttonControl);

        // replaces existing content in the vbox with a new button
        // loaded from fxml every time this method is called.
        //vBoxBase.getChildren().setAll(buttonControl);
    }
}

base.fxml

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

<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane fx:controller="com.example.test.BaseController" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1">
    <top>
        <MenuBar>
            <menus>
                <Menu mnemonicParsing="false" text="Import">
                    <items>
                        <MenuItem onAction="#importButton" mnemonicParsing="false" text="Import button" />
                    </items>
                </Menu>
            </menus>
        </MenuBar>
    </top>
    <center>
        <VBox fx:id="vBoxBase"/>
    </center>
</BorderPane>

按钮.fxml

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>

<VBox fx:controller="com.example.test.ButtonController" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1">
    <children>
        <Button onAction="#buttonFonction" mnemonicParsing="false" text="Imported Button" />
        <Label text="Imported label" />
    </children>
</VBox>

代码的其余部分保持不变(尽管我建议修复代码中的拼写错误,但这类事情总是很烦人并且可能导致错误)。

© www.soinside.com 2019 - 2024. All rights reserved.