简体   繁体   中英

Root cannot be null exception with multiple FXML/Controller in JavaFX

I'm working on a JavaFX project to manage workouts. While it had one big.fxml file, and one big MainController class, it worked fine:

WorkoutsMain.fxml:

<AnchorPane id="AnchorPane" fx:id="anchorPane" prefHeight="900.0" prefWidth="1600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="workouts.WorkoutsMainController">
    <children>
        <SplitPane fx:id="splitPane" dividerPositions="0.15" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
            <items>
                <AnchorPane>
                    <children>
                        <StackPane fx:id="menuPane" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/>
                    </children>
                </AnchorPane>
                <AnchorPane>
                    <children>
                        <Pane fx:id="statisticsPane" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="130.0"/>
                        <Pane fx:id="workoutsPane" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="130.0">
                        //... other FX elements
                        </Pane>
                        <Pane fx:id="calendarPane" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="130.0"/>
                        <StackPane fx:id="logoPane" onMouseClicked="#setLogo" AnchorPane.bottomAnchor="778.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/>
                    </children>
                </AnchorPane>
            </items>
        </SplitPane>
    </children>
</AnchorPane>

WorkoutsMainController.java:

public class WorkoutsMainController implements Initializable, SplitPaneDividerController {
    private final DataBase dataBase = new DataBase();
    private boolean first = true;
    private String logoName;
    @FXML
    private StackPane logoPane, menuPane;
    private final List<Pane> panes = new ArrayList<>();
    @FXML
    private Pane calendarPane, workoutsPane, statisticsPane;
    @FXML
    private SplitPane splitPane;
    @FXML
    private AnchorPane anchorPane;
    // methods and functions
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        setLogo();
        disableSplitPaneDivider(splitPane, 0.1525);
        setUpMainMenu();
    }
}

MainClass.java:

public class MainClass extends Application {
    private Parent anchorPane;
    private final String fxml = "WorkoutsMain.fxml";
    @Override
    public void start(Stage stage) {       
        try {
            anchorPane = FXMLLoader.load(getClass().getResource(fxml));
        } catch (IOException ex) {
            System.out.println("Error when trying to load " + fxml);
          }
        Scene scene = new Scene(anchorPane);
        stage.setScene(scene);
        stage.getIcons().add(new Image(getClass().getResourceAsStream("/calendarIconWhite.png")));
        stage.setTitle("THE WORKOUT CALENDAR 1.7.0 by hazazs®");
        stage.setResizable(false);
        stage.show();
    }
    public static void main(String[] args) {
        launch(args);
    }
}

Then I wanted to cut a piece from the WorkoutsMain.fxml. I have changed a segment in the original big.fxml file like this:
from

<Pane fx:id="workoutsPane" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="130.0">
    //... other FX elements
</Pane>

to

<fx:include source="Workouts.fxml" fx:id="workoutsPane"/>

Then I have created a new fxml file with its own Controller class:
Workouts.fxml:

<Pane fx:id="workoutsPane" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="130.0" xmlns:fx="http://javafx.com/fxml/1" fx:controller="workouts.WorkoutsController">
// other FX elements
</Pane>

I have created an appropriate WorkoutsController class and I have changed my WorkoutsMainController like this:

public class WorkoutsMainController implements Initializable, SplitPaneDividerController {
    protected static final DataBase dataBase = new DataBase();
    private final WorkoutsController workoutsController = new WorkoutsController();
    private boolean first = true;
    private String logoName;
    @FXML
    private StackPane logoPane, menuPane;
    private final List<Pane> panes = new ArrayList<>();
    @FXML
    private Pane calendarPane, workoutsPane, statisticsPane;
    @FXML
    protected static SplitPane splitPane;
    @FXML
    protected static AnchorPane anchorPane;
    // and so on..

But from now I get Exception when I try to start my application:

Exception in Application start method
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:498)
    at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
    at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
    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:498)
    at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application start method
    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$159(LauncherImpl.java:182)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.NullPointerException: Root cannot be null
    at javafx.scene.Scene.<init>(Scene.java:336)
    at javafx.scene.Scene.<init>(Scene.java:194)
    at workouts.MainClass.start(MainClass.java:23)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$166(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$179(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$177(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$178(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$152(WinApplication.java:177)
    ... 1 more

Any suggestion guys, what should I do to make this work once again? With one.fxml and one Controller it worked perfectly.
Thanks

You're creating a new WorkoutsController instance in your WorkoutsMainController , instead of using the one created for you by the FXMLLoader . The @FXML -annotated fields in the instance you create will all be null, so you are probably getting a null pointer exception somewhere. (You should be seeing the "Error when trying to load WorkoutsMain.fxml" message in the console.) Thus anchorPane in your start() method is never initialized and you get an exception when you pass the null anchorPane reference to the Scene constructor.

Note also you have (for some unknown reason) made splitPane and anchorPane static : it makes no sense to do that, and the FXMLLoader will not initialize static fields. So those fields are also null in the controller.

If you don't squash exceptions thrown by loading the FXML, you will be able to see the actual stack trace for the underlying exception.

The fixes are:

  1. don't make @FXML -annotated fields static . See javafx 8 compatibility issues - FXML static fields

  2. To inject the actual controller instance for the included FXML that is created by the FXMLLoader , see Nested Controllers in the documentation. In short, since your fx:include has fx:id="workoutsPane" , you should replace

     private final WorkoutsController workoutsController = new WorkoutsController();

    with

     @FXML private WorkoutsController workoutsPaneController;

    (and replace all occurrences of workoutsController with workoutsPaneController in the remainder of WorkoutsMainController ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM