尝试在根布局中的窗格之间切换时出现 NullPointer

NullPointer when trying to switch between panes in rootlayout

我有一个程序,其中我有一个 BorderPane 作为根布局,并且想在根布局内的 Anchorpane 之间切换。当我 运行 应用程序时,带有锚定面板 "login" 的根布局显示得很好。但是,当我尝试将根布局中的锚窗格切换为 "startMenu" 时,即使我使用与 "Login" 相同的方法,我也会得到 NullPointerException。请帮忙! :)

在我的 main class 中,我想要的越少越好,所以我只有 main 和 start 方法来启动应用程序并显示登录屏幕:

package controller;

import javafx.application.Application;
import javafx.stage.Stage;

public class Main extends Application {

        Controller controller = new Controller();

        private Stage window;

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

        public void start(Stage primaryStage) {
            this.window = primaryStage;
            this.window.setTitle("Title");

            controller.setWindow(window);
            controller.initRootLayout();
            controller.showLogin();
        }
    }

控制器class:

    package controller;

    import javafx.fxml.FXMLLoader;
    import javafx.scene.Scene;
    import javafx.scene.layout.AnchorPane;
    import javafx.scene.layout.BorderPane;
    import javafx.stage.Stage;
    import javafx.scene.control.Button;
    import java.io.IOException;

    public class Controller{

        private BorderPane rootLayout;
        public Stage window;

        public Button btnOk;
        public FXMLLoader loader = new FXMLLoader();

        public void initRootLayout() {
            try {
                // Load root layout from fxml file.
                FXMLLoader loader = new FXMLLoader();
                loader.setLocation(Controller.class.getResource("/gui/rootLayout.fxml"));
                rootLayout = loader.load();

                // Show the scene containing the root layout.
                Scene scene = new Scene(rootLayout);
                window.setScene(scene);
                window.show();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public void showLogin() {
            try {
                // Load login.fxml.
                FXMLLoader loader = new FXMLLoader();
                loader.setLocation(Controller.class.getResource("/gui/login.fxml"));
                AnchorPane login = loader.load();

                // Set login.fxml into the center of root layout.
                rootLayout.setCenter(login);

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        public void showStartMenu(){
            try {
                FXMLLoader loader = new FXMLLoader();
                loader.setLocation(Controller.class.getResource("/gui/startMenu.fxml"));
                AnchorPane startMenu = loader.load();

                rootLayout.setCenter(startMenu);

            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public void handleLogin(){
           showStartMenu();
        }

        public void setWindow(Stage window) {
            this.window = window;
        }
}

我的登录 FXML 文件:

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

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.Controller">
   <children>
      <Label layoutX="353.0" layoutY="150.0" text="Sign in" textAlignment="CENTER">
         <font>
            <Font size="34.0" />
         </font>
      </Label>
      <TextField fx:id="fieldUsername" layoutX="311.0" layoutY="239.0" />
      <PasswordField fx:id="fieldPassword" layoutX="311.0" layoutY="312.0" />
      <Button fx:id="btnOk" layoutX="467.0" layoutY="436.0" mnemonicParsing="false" onAction="#handleLogin" prefHeight="31.0" prefWidth="73.0" text="OK" />
      <Hyperlink fx:id="linkForgotPassword" layoutX="375.0" layoutY="343.0" text="Forgot password?" textOverrun="CLIP" underline="true">
         <effect>
            <Blend />
         </effect></Hyperlink>
   </children>
</AnchorPane>

最后是我的堆栈跟踪:

"C:\Program Files\Java\jdk1.8.0_20\bin\java" -Didea.launcher.port=7545 "-Didea.launcher.bin.path=C:\Program Files (x86)\JetBrains\IntelliJ IDEA 14.1.4\bin" -Dfile.encoding=windows-1252 -classpath "C:\Program Files\Java\jdk1.8.0_20\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_20\jre\lib\ext\zipfs.jar;C:\Users\Mikkel\Documents\Client\out\production\JavaFxApplication;C:\Program Files (x86)\JetBrains\IntelliJ IDEA 14.1.4\lib\idea_rt.jar" com.intellij.rt.execution.application.AppMain controller.Main
Exception in thread "JavaFX Application Thread" java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1762)
    at javafx.fxml.FXMLLoader$ControllerMethodEventHandler.handle(FXMLLoader.java:1645)
    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:8216)
    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:3724)
    at javafx.scene.Scene$MouseHandler.access00(Scene.java:3452)
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1728)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2461)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:348)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:273)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:382)
    at com.sun.glass.ui.View.handleMouseEvent(View.java:553)
    at com.sun.glass.ui.View.notifyMouse(View.java:925)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null1(WinApplication.java:102)
    at com.sun.glass.ui.win.WinApplication$$Lambda/1399457240.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:71)
    at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:275)
    at javafx.fxml.FXMLLoader$MethodHandler.invoke(FXMLLoader.java:1759)
    ... 47 more
Caused by: java.lang.NullPointerException
    at controller.Controller.showStartMenu(Controller.java:55)
    at controller.Controller.handleLogin(Controller.java:63)
    ... 57 more

您有多个 Controller 实例:一个是您在 Main 中使用 Controller controller = new Controller() 创建的,另一个是由 FXMLLoader 创建的,因为您指定 fx:controller="controller.Controller"

您初始化 rootLayout 的唯一地方是在 initRootLayout 方法中,并且该方法 在您在 [=19] 中创建的实例上调用=].

因此,对于 FXMLLoader 创建的实例,rootLayout 永远不会被初始化。这意味着当您按下按钮,并且在 FXMLLoader 创建的控制器实例上调用 handleLogin() 时,当您执行 rootLayout.setCenter(..).[=34 时,您会得到一个 NullPointerException =]

下面是我将如何构建这样的内容。如果要将 FXML 的加载委托给控制器,可以使用 How to understand and use `<fx:root>` , in JavaFX? 中概述的 <fx:root> 结构。所以:

application/Main.java:

package application;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import ui.root.RootPane;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(new RootPane(), 600, 600));
        primaryStage.show();
    }

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

ui/root/RootPane.java:

package ui.root;

import java.io.IOException;

import javafx.fxml.FXMLLoader;
import javafx.scene.layout.BorderPane;
import model.Model;
import ui.login.LoginController;
import ui.menu.MenuController;

public class RootPane extends BorderPane {

    private final Model model ;

    public RootPane() throws Exception {
        model = new Model();
        model.loggedInProperty().addListener((obs, wasLoggedIn, isLoggedIn) -> {
            if (isLoggedIn) {
                showMenu();
            } else {
                showLogin();
            }
        });

        FXMLLoader loader = new FXMLLoader(getClass().getResource("root.fxml"));
        loader.setController(this);
        loader.setRoot(this);
        loader.load();
        showLogin();
    }

    private void showMenu() {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("/ui/menu/menu.fxml"));
            setCenter(loader.load());
            MenuController controller = loader.getController();
            controller.setModel(model);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void showLogin() {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("/ui/login/login.fxml"));
            setCenter(loader.load());
            LoginController controller = loader.getController();
            controller.setModel(model);
        } catch (IOException e) {
            e.printStackTrace();
        }       
    }
}

model/Model.java(本质上是一个视图模型,跟踪用户是否登录):

package model;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;

public class Model {

    private BooleanProperty loggedIn = new SimpleBooleanProperty();

    public final BooleanProperty loggedInProperty() {
        return this.loggedIn;
    }


    public final boolean isLoggedIn() {
        return this.loggedInProperty().get();
    }


    public final void setLoggedIn(final boolean loggedIn) {
        this.loggedInProperty().set(loggedIn);
    }


}

root/root.fxml:

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

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

<fx:root xmlns:fx="http://javafx.com/fxml/1" type="BorderPane">
    <top>
        <Label text="Login Screen Example" style="-fx-font-size: 16pt; -fx-font-family:sans-serif;"/>
    </top>
</fx:root>

ui/login/login.fxml:

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

<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.Button?>

<GridPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.login.LoginController">
    <columnConstraints>
        <ColumnConstraints halignment="RIGHT" hgrow="NEVER"/>
        <ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" />
    </columnConstraints>

    <Label text="Username:" GridPane.columnIndex="0" GridPane.rowIndex="0"/>
    <Label text="Password:" GridPane.columnIndex="0" GridPane.rowIndex="1"/>
    <TextField fx:id="userField" GridPane.columnIndex="1" GridPane.rowIndex="0"/>
    <PasswordField fx:id="passwordField" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
    <Button text="Login" onAction="#login" GridPane.rowIndex="2" GridPane.columnSpan="2" GridPane.halignment="CENTER"/>
</GridPane>

ui/login/LoginController.java:

package ui.login;

import javafx.fxml.FXML;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import model.Model;

public class LoginController {
    private Model model ;

    public Model getModel() {
        return model;
    }

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

    @FXML
    private TextField userField ;
    @FXML
    private PasswordField passwordField ;

    @FXML
    private void login() {
        String user = userField.getText() ;
        String password = passwordField.getText();
        // verify...
        model.setLoggedIn(true);
    }
}

ui/menu/menu.fxml:

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

<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>

<HBox xmlns:fx="http://javafx.com/fxml/1" alignment="CENTER" fx:controller="ui.menu.MenuController">
    <Button text="Some Action" onAction="#someAction"/>
    <Button text="Logout" onAction="#logout"/>
    <Button text="Exit" onAction="#exit" fx:id="exitButton"/>
</HBox>

ui/menu/MenuController.java:

package ui.menu;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import model.Model;

public class MenuController {

    private Model model ;

    @FXML
    private Button exitButton ;

    public Model getModel() {
        return model;
    }

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

    @FXML
    private void logout() {
        model.setLoggedIn(false);
    }

    @FXML
    private void someAction() {
        System.out.println("Some action....");
    }

    @FXML
    private void exit() {
        exitButton.getScene().getWindow().hide();
    }
}