0

I want to change the stage when a user logs in the application. I have created a thread in the Action and inside it I use Platform.runLater to update the stage and show the new one. This is done in the called method.

So I have the following code:

Logincontroller

private Stage primaryStage

@FXML
void btnLoginAction(ActionEvent event) throws ClassNotFoundException {
    Runnable loginRunnable = new Runnable() {
        public void run() {
            ....
        if (user exists and password ok){
           loadMainwindow();
        }else{
           show alert
        }
    };
    Thread loginThread = new Thread(loginRunnable);
    loginThread.start();
}

private void loadMainWindow() throws IOException {

    dummyStage =  (Stage) (btnLogin.getScene()).getWindow();

    //I get the root borderpain from the Main class
    BorderPane root = Main.getRoot();

    //I load the anchorpanes i will use in the new stage
    AnchorPane menuPane = 
    FXMLLoader.load(getClass().getResource("/views/Menu.fxml"));
    AnchorPane centerPane = 
    FXMLLoader.load(getClass().getResource("/views/Home.fxml"));

    //I set the anchorpanes to the root
    root.setLeft(menuPane);
    root.setCenter(centerPane);

    Platform.runLater(new Runnable() {
        public void run() {
            primaryStage.show();
        }
    });
}

And I´m having the following error:

Exception in thread "Thread-3" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3
at javafx.graphics/com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:291)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:493)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.add(VetoableListDecorator.java:206)
at javafx.graphics/javafx.scene.layout.BorderPane$BorderPositionProperty.invalidated(BorderPane.java:692)
at javafx.base/javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
at javafx.base/javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:147)
at javafx.graphics/javafx.scene.layout.BorderPane.setLeft(BorderPane.java:325)
at com.sener.dbgui.controller.LoginController.loadMainWindow(LoginController.java:90)
at com.sener.dbgui.controller.LoginController.access$4(LoginController.java:81)
at com.sener.dbgui.controller.LoginController$1.run(LoginController.java:63)
at java.base/java.lang.Thread.run(Thread.java:844)

Line 81 is the "root.setLeft(menuPane)" line.

So I guess the problem is that when modifying the root borderpane the JAVAFX thread must be running. This is, I must include the "root.set..." statements in the Platform.runLater method.

Nonetheless, this would imply in setting multiple variables for root, menuPane and centerPane to private in the controller class so that Platform.runLater process could access them and all the FXMLLoader, getwindow() and getRoot() methods could be decoupled from Platform.runLater.

So, is it better to create set this variables to private or just call the method inside the Platform.runLater?

OPTION 1. CALL METHOD INSIDE Platform.runLater

@FXML
void btnLoginAction(ActionEvent event) throws ClassNotFoundException {
    Runnable loginRunnable = new Runnable() {
        public void run() {
            ....
        if (user exists and password ok){
            Platform.runLater(new Runnable() {
                public void run() { 
                  loadMainwindow();
                }
            });
        }else{
           show alert
        }
    };
    Thread loginThread = new Thread(loginRunnable);
    loginThread.start();
}

If decoupling FXMLLoader, getWindow() and getRoot methods from Platform.runLater the code of the method would look like this (I would first create private variables for AnchorPanes "menuPane" and "centerPane", BorderPane "root" just like with "primaryStage" variable):

OPTION 2. CALL METHOD AND DECOUPLE FMLXLOADERS, GETROOT() AND GETWINDOW() METHODS FROM Platform.runLater()

private AnchorPane menuPane, centerPane;
private Stage dummyStage;
private BorderPane root;

@FXML
void btnLoginAction(ActionEvent event) throws ClassNotFoundException {
Runnable loginRunnable = new Runnable() {
    public void run() {
        ....
    if (user exists and password ok){
       loadMainwindow();
    }else{
       show alert
    }
};
Thread loginThread = new Thread(loginRunnable);
loginThread.start();
}


private void loadMainWindow() throws IOException {

    root = Main.getRoot();

    primaryStage =  (Stage) (btnLogin.getScene()).getWindow();

    menuPane = FXMLLoader.load(getClass().getResource("/views/Menu.fxml"));
    centerPane = XMLLoader.load(getClass().getResource("/views/Home.fxml"));

    Platform.runLater(new Runnable() {
        public void run() {

            root.setLeft(menuPane);
            root.setCenter(centerPane);

            primaryStage.toFront();
            primaryStage.show();
        }
    });
}

I would like to know which option is the correct one. Or maybe these are wrong and there´s another solution to this.

LazyTurtle
  • 131
  • 1
  • 3
  • 16
  • There is no need to have extra variables, see [this question](https://stackoverflow.com/questions/32272713/lambda-expression-and-variable-capture) about capture scope. Why not have the call to `loadMainWindow` itself wrapped in `Platform::runLater`? Presumably it is only the user login verification that takes time and should be offloaded to another thread... – Itai Jan 23 '18 at 10:00
  • I see, but, in case the method calls to any process that takes time (accessing a database for example) the second option should be the correct one. Is this correct? – LazyTurtle Jan 23 '18 at 10:05
  • Ok, and for the extra variables I see that just creating them as private in the class I would not have to crerate new ones as I could acess the private ones directly from the runLater process – LazyTurtle Jan 23 '18 at 10:08
  • Thank you for the variable tip, I´m going to edit the question and fix related errors to this "adding new variables" topic... – LazyTurtle Jan 23 '18 at 10:16
  • So, if both are correct is the developer´s choice to chose which option to use depending on the process time it takes for the application to process some code. Is this correct? – LazyTurtle Jan 23 '18 at 10:21

0 Answers0