0

I have a program where i have a BorderPane as the rootLayout, and want to switch between Anchorpanes within the rootlayout. When i run the app the rootlayout with the anchorpane "login" displays just fine. But when i try to switch the anchorpane in the rootlayout to "startMenu" I get a NullPointerException even though i use the same method as with "Login". Please help! :)

In my main class i want as little as possible so i only have my main and start method that starts the app and shows the login screen:

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();
        }
    }

Controller 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;
        }
}

My login FXML file:

<?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>

And finally my stacktrace:

"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$1.handle(BehaviorSkinBase.java:96)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.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.access$1500(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$null$141(WinApplication.java:102)
    at com.sun.glass.ui.win.WinApplication$$Lambda$38/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

1 Answers1

1

You have multiple instances of your Controller: one you create yourself in Main with Controller controller = new Controller() and one that is created by the FXMLLoader because you specify fx:controller="controller.Controller".

The only place you initialize rootLayout is in the initRootLayout method, and that method is only invoked on the instance you created in Main.

So, for the instance created by the FXMLLoader, rootLayout is never initialized. This means that when you press the button, and handleLogin() is called on the controller instance created by the FXMLLoader, you get a NullPointerException when you do rootLayout.setCenter(..).

Here's how I would structure something like this. If you want to delegate the loading of the FXML to the controller, you can use the <fx:root> structure outlined in How to understand and use `<fx:root>` , in JavaFX?. So:

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 (essentially a view model, tracks whether the user is logged in):

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();
    }
}
Community
  • 1
  • 1
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Okay that makes sense. But how do i initialize the rootlayout in the instance created by the FXMLLoader ? I tried to put initRootLayout in the handleLogin method without any luck. Perhaps i can have just one instance of the controller somehow? – Mikkel Gammelgaard Nov 13 '15 at 20:07
  • I just wouldn't structure it like this at all. Have a look at some examples ([this is a popular one](http://code.makery.ch/library/javafx-8-tutorial/part1/)) and try to follow that structure. Basically, you are doing things backwards: in the usual set up, FXML files create the controller. In your setup it looks like the controller is trying to load its own FXML file. (If you really want to do it that way around, have a look at http://stackoverflow.com/questions/23600926/how-to-understand-and-use-fxroot-in-javafx) – James_D Nov 13 '15 at 20:11
  • I actually used your first link as inspiration - i just didn't like so much code in the main method so i tried to move it to the controller class. Perhaps i should go back and start from scratch.. thanks for your feedback though – Mikkel Gammelgaard Nov 13 '15 at 20:28
  • I'll try and update the answer with an idea how I would do this if I have time. – James_D Nov 13 '15 at 20:29
  • That would be awesome! Cheers – Mikkel Gammelgaard Nov 13 '15 at 20:31
  • See update. I used the `` structure for the root pane, to minimize code in `Main`. The rest is just loading FXML files and their controllers in the usual way. – James_D Nov 13 '15 at 21:02