JavaFX - 主控制器调用子控制器的函数导致空指针异常

JavaFX - Main controller calls sub controller's function which causes Null Pointer Exception

我对 JavaFX 比较陌生,但我尽量说清楚。

问题:当我的主控制器调用我的子控制器的函数时,它运行完全正常,只要它不需要操作 fxml 文件的内容。

令人惊讶的是,如果在初始化函数中操作 fxml 注释组件,代码不会导致任何错误。但是每当我想在其他函数中使用它们时,它都会抛出 NullPointerException。

为了在 fxml 注释组件下更清楚一点,我的意思是例如:@FXML private Label exampleLbl。

我创建了一个简化的示例:

主控制器class:

package application;

import java.net.URL;
import java.util.ResourceBundle;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Tab;
import javafx.scene.layout.AnchorPane;

public class MainController implements Initializable {

    @FXML SubController subController;

    @FXML private Tab Tab1;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        subController = new SubController();

        FXMLLoader loader = new FXMLLoader();

        try {
            AnchorPane anch1 = loader.load(getClass().getResource("SubView.fxml"));
            Tab1.setContent(anch1);
        }
        catch(Exception e) {
            System.out.println("unable to load tab1");
            e.printStackTrace();
        }
    }

    public void fooFunction() {
        if(subController != null) {
            subController.testFunction();   
        }
    }
}

子控制器class:

package application;

import java.net.URL;
import java.util.ResourceBundle;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;

public class SubController implements Initializable {

    @FXML private Label exampleLbl;

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) {
        if(exampleLbl != null) {
            System.out.println("It is printed, so it is not null");
            exampleLbl.setText("So I can manipulate this as I want.");
        }

    }

    public void testFunction() {
        if(exampleLbl == null) {
            System.out.println("It is printed, so it is null now.");
            exampleLbl.setText("But not here. It will throw an error.");
        }
    }
}

nullptrexception 发生在 SubController 的 class 中的 testFunction() 方法,第 26 行。

行:exampleLbl.setText("But not here. It will throw an error.");

所以它在初始化函数中工作正常,但在 testFunction

这是我的 FXML 文件:

主视图 fxml:

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.BorderPane?>

<BorderPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainController">
   <center>
   <TabPane prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE">
        <tabs>
            <Tab fx:id="Tab1" text="Test">
               <content>

               </content>
            </Tab>
        </tabs>
   </TabPane>
   </center>

   <bottom>
        <Button mnemonicParsing="false" onAction="#fooFunction" text="Button" BorderPane.alignment="CENTER" />
   </bottom>
</BorderPane>

子视图 FXML:

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

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

<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.SubController">
      <Label fx:id="exampleLbl" layoutX="5.0" layoutY="128.0" prefHeight="17.0" prefWidth="188.0" text="Label" />
</AnchorPane>

如果有帮助,这是错误消息和控制台输出:

It is printed, so it is not null
It is printed, so it is null now.

Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1774)
    at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1657)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Node.fireEvent(Node.java:8411)
    at javafx.scene.control.Button.fire(Button.java:185)
    at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:182)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase.handle(BehaviorSkinBase.java:96)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase.handle(BehaviorSkinBase.java:89)
    at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Scene$MouseHandler.process(Scene.java:3757)
    at javafx.scene.Scene$MouseHandler.access00(Scene.java:3485)
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:394)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent8(GlassViewEventHandler.java:432)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:431)
    at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
    at com.sun.glass.ui.View.notifyMouse(View.java:937)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null2(WinApplication.java:177)
    at java.lang.Thread.run(Unknown Source)
Caused by: 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 sun.reflect.misc.Trampoline.invoke(Unknown Source)
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at sun.reflect.misc.MethodUtil.invoke(Unknown Source)
    at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1771)
    ... 48 more
Caused by: java.lang.NullPointerException
    at application.SubController.testFunction(SubController.java:26)
    at application.MainController.fooFunction(MainController.java:36)
    ... 58 more

因此,如果有人可以解释我在这里缺少什么并提供简单的解决方案,我将不胜感激。

您在 initialize 中创建的 SubController 实例未与 fxml 文件一起使用。 FXMLLoader 创建 SubController 的不同实例 ,注入 Label 并调用 initialize为此。

您需要使用非static load 方法来加载fxml 文件并检索之后由FXMLLoader 创建的控制器。 (load(URL)static。)没有必要注释未注入 @FXMLsubController)的字段。

主控制器

@Override
public void initialize(URL location, ResourceBundle resources) {
    FXMLLoader loader = new FXMLLoader(getClass().getResource("SubView.fxml"));

    try {
        AnchorPane anch1 = loader.load();
        Tab1.setContent(anch1);

        subController = loader.getController(); // retrieve controller
    } catch(Exception e) {
        System.out.println("unable to load tab1");
        e.printStackTrace();
    }
}

当您使用subController = new SubController()时,您刚刚创建的实例与FXML 文件无关。 None 的字段将被注入,因为控制器实例不是由 FXMLLoader 管理的。在您的情况下,使用以下内容应该可以解决问题:

@Override
public void initialize(URL location, ResourceBundle resources) {
    FXMLLoader loader = new FXMLLoader(getClass().getResource("SubView.fxml"));

    try {
        AnchorPane anch1 = loader.load();
        subController = loader.getController();
        Tab1.setContent(anch1);
    } catch (Exception e) {
        System.out.println("unable to load tab1");
        e.printStackTrace();
    }
}

注意两点:

  1. 我在构建过程中设置了 FXMLLoader 的位置。当您使用 loader.load(/* URL */) 时,您实际上使用的是 static FXMLLoader#load(URL) 方法。您需要使用 实例 load() 方法。
  2. 我将 subController 字段(顺便说一句,不需要用 @FXML 注释)分配给 loader.getController()。该方法将 return FXMLLoader 根据您通过 FXML 文件中的 fx:controller 属性指定的 class 名称创建的实例。 重要提示:您必须在 load() 之后调用 getController()

您可能还想考虑使用 fx:include

MainView.fxml

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

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.BorderPane?>

<BorderPane xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainController">
   <center>
   <TabPane prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE">
        <tabs>
            <Tab fx:id="Tab1" text="Test">
               <content>
                   <!-- replace the value of source as needed -->
                   <fx:include source="SubView.fxml" fx:id="sub"/>
               </content>
            </Tab>
        </tabs>
   </TabPane>
   </center>

   <bottom>
        <Button mnemonicParsing="false" onAction="#fooFunction" text="Button" BorderPane.alignment="CENTER" />
   </bottom>
</BorderPane>

这使得不再需要在 MainController#initialize 方法中手动加载 SubView.fxml 文件。此外,由于我将 fx:id 分配给包含的文件,因此将为您注入 SubController 实例。当涉及到注入控制器时,FXMLLoader 查找名称为 <fx:id>Controller 的适当类型(和注释)字段(参见 Nested Controllers)。