我目前正在开发我的第一个 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 被保留。
附注这是我的第一篇文章,如果缺少某些内容或者您想完整地查看我的项目,我将很乐意尽我所能来帮助您解决这个问题
不要将 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 有一种简化它的方法,我不会在这里详细说明,如果有兴趣,请参阅嵌套控制器。
如果您只有一个要设置的项目,那么(例如,如果您使用了边框窗格)您可以将其设置在边框窗格中的某个位置,可能将其包含在堆栈窗格中以进行对齐,尽管我赢了不在这里证明这一点。
总体布局建议
示例
应用前面提到的设置
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>
代码的其余部分保持不变(尽管我建议修复代码中的拼写错误,但这类事情总是很烦人并且可能导致错误)。