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.