0

So I have a root BorderPane, an AnchroPane for the left part and another AnchorPane for the bottom. Left contains a Button and Bottom contains a Label. All I want to achieve is that the buttonClick() method of LeftController updates the Bottom.fxml and displays a new text on the label.

I have read a couple of posts like this, but nothing lead me to really solve all problems out.

This is a dummy project, which represents my bigger project (which I want to refactor to achieve better MVC structure) with which I finally want to get to understand how to communicate between controllers, which seems to be impossible for me right now.

My first approach was to make a new instance of the BottomController within leftController's initialize(). That would let me access the BottomController's setLabelText() method, but did not update the labels text. Eventough the console put showed me that the method was called, the labels text was not touched.

FXMLLoader loaderBottom = new FXMLLoader(Main.class.getResource("Bottom.fxml"));
bottom = loaderBottom.load();
bottomController = loaderBottom.getController();

My current approach (on my little dummyProject) to get those controllers to call methods of other controllers is by initializing them within the Main's start() method and add a getter for each controller. Then on the controller side I call those getters to make the controller accessible .. which unfortunately ends in some InvocationTargetException.

Is there a best practice approach update the other FXML's views from different controller classes?

This is the code of my current approach:

Main.java

public class Main extends Application {

    private Stage rootStage;
    private BorderPane mainWindow;
    private AnchorPane left;
    private AnchorPane bottom;
    private MainWindowController mainWindowController;
    private LeftController leftController;
    private BottomController bottomController;

    @Override
    public void start(Stage primaryStage) throws Exception {

        this.rootStage = primaryStage;

        FXMLLoader loaderMainWindow = new FXMLLoader(Main.class.getResource("MainWindow.fxml"));
        mainWindow = loaderMainWindow.load();
        mainWindowController = loaderMainWindow.getController();
        // To make the connection from the controller class to the main class
        mainWindowController.setMain(this);

        FXMLLoader loaderBottom = new FXMLLoader(getClass().getResource("Bottom.fxml"));
        bottom = loaderBottom.load();
        bottomController = loaderBottom.getController();
        // To make the connection from the controller class to the main class
        bottomController.setMain(this);

        FXMLLoader loaderLeft = new FXMLLoader(Main.class.getResource("Left.fxml"));
        left = loaderLeft.load();
        leftController = loaderLeft.getController();
        // To make the connection from the controller class to the main class
        leftController.setMain(this);

        mainWindow.setLeft(left);
        mainWindow.setBottom(bottom);

        Scene scene = new Scene(mainWindow);
        rootStage.setScene(scene);
        rootStage.show();
    }

    public MainWindowController getMainWindowController() { return mainWindowController;}
    public LeftController getLeftController() {return leftController;}
    public BottomController getBottomController() {return bottomController;}

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

}

LeftController.java

public class LeftController implements Initializable {

    @FXML
    private Button button;
    private Main main;
    private BottomController bottomController;

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        bottomController = main.getBottomController();
    }

    @FXML
    public void buttonClick(javafx.event.ActionEvent actionEvent) {
        System.out.println("Some Stuff..");
        bottomController.setLabelText("Some Stuff..");
    }

    public void setMain(Main main) {
        this.main = main;
    }
}

BottomController.java

package sample;

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

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

public class BottomController implements Initializable {

    @FXML
    private Label label;
    private Main main;
    private LeftController leftController;

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {

    }

    @FXML
    public void setLabelText(String text) {
        label.setText(text);
    }

    public void setMain(Main main) {
        this.main = main;
    }
}

Left.fxml

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

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

<AnchorPane prefHeight="400.0" prefWidth="100.0" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.LeftController">
   <children>
      <Button fx:id="button" layoutX="237.0" layoutY="169.0" mnemonicParsing="false" onAction="#buttonClick" text="Button" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" />
   </children>
</AnchorPane>

Bottom.fxml

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

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

<AnchorPane prefWidth="600.0" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.BottomController">
   <children>
      <Label fx:id="label" alignment="CENTER" layoutX="203.0" layoutY="304.0" prefHeight="30.0" text="Label" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0" />
   </children>
</AnchorPane>

Exception:

Exception in Application start method
java.lang.reflect.InvocationTargetException
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:464)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:363)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1051)
Caused by: java.lang.RuntimeException: Exception in Application start method
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:900)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: javafx.fxml.LoadException: 
/C:/Users/Ruphus/IdeaProjects/controllerAccess/out/production/controllerAccess/sample/Left.fxml

    at javafx.fxml/javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2625)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2603)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2466)
    at javafx.fxml/javafx.fxml.FXMLLoader.load(FXMLLoader.java:2435)
    at sample.Main.start(Main.java:39)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:846)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:455)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
    at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
    ... 1 more
Caused by: java.lang.NullPointerException
    at sample.LeftController.initialize(LeftController.java:22)
    at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2573)
    ... 12 more
Exception running application sample.Main

So I would expect the following order of steps: First: the Main's start() method is called (besides main()). Second: start() calls the setMain() methods and by that makes the Main class accessable to the controllers Third: The LeftController's initialize is called to retreive a reference of BottomController to have it available. Fourth: I click the button and change the labels text.

I am very confused by the error trace, since it navigates me to left = loaderLeft.load(); within main. If I comment the line to retreive the BottomController within LeftController out, I can start with no error. So I must assume the controllers initialize() methods are called before the start() method?

rroger
  • 178
  • 1
  • 11
  • Possible duplicate of [What is a NullPointerException, and how do I fix it?](https://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it) – M. le Rutte Aug 22 '19 at 12:31
  • @M.leRutte I don't thinkn so, since I am encountering a `InvocationTargetException` – rroger Aug 22 '19 at 13:13
  • `java.lang.reflect.InvocationTargetException`--> `Caused by: java.lang.RuntimeException` --> `Caused by: javafx.fxml.LoadException` --> `Caused by: java.lang.NullPointerException` – M. le Rutte Aug 22 '19 at 13:21
  • ah oups :D But thats not helping me. I pretty much understand what a NPE is but I can not see the problem with my code. – rroger Aug 22 '19 at 13:34
  • I guess `initialize` is called _before_ `setMain`? – M. le Rutte Aug 22 '19 at 13:41
  • @M.leRutte yeah I guess so too, but I don't know how to circumvent that. – rroger Aug 22 '19 at 13:43

1 Answers1

0

Ok, it was so easy. Today I realized, that I just could move the bottomController = main.getBottomController(); part into the buttonClick() method:

    @FXML
    public void buttonClick(javafx.event.ActionEvent actionEvent) {
        bottomController = main.getBottomController();
        System.out.println("Some Stuff..");
        bottomController.setLabelText("Some Stuff..");
    }

Now it finally interacts with the label as intended.

rroger
  • 178
  • 1
  • 11