Reputation: 37
This question is already asked but i copuldnt udnerstand it. Imagine that. I have a program with 2 scenes. First scene1 is opened which is the scene for connection to database. There is a label called Status which should turn from "Disconnected" to "Connected" when the connection is established(by clicking the COnnect button). So i made a function to take the button "Connect" onClick event. This function is declared and defined inside a controller class (i am using fmxl designing with scene builder). So basicly i want to change the status to "COnnected" (status.setText("Connected")) from the connection function(method) which is inside the controller class. However when I do that, the text isn't changed instantly after the connection is established, but it changes when the scene is about to close and i am about to change the scene to the new one... I read on the internet and i saw that i should use Platform.runLater and threading so i tried:
private void changeSC() throws IOException, InterruptedException, SQLException
{
dbConnect();
Thread thrd = new Thread() {
public void run() {
Platform.runLater(new Runnable() {
@Override public void run() {
status.setText("Connected");
status.setTextFill(Color.GREEN);
}});
}
};
thrd.start();
//pb.setProgress(1.0);
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Thread.sleep(2000);
Main.window.setScene(primary);
}
changeSC is the function that is executed when Connect button is clicked. This is my old version which also doesnt work:
private void changeSC() throws IOException, InterruptedException, SQLException
{
dbConnect();
status.setText("Connected");
status.setTextFill(Color.GREEN);
//pb.setProgress(1.0);
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Thread.sleep(2000);
Main.window.setScene(primary);
}
The problem is with the text which should change to "Connected". It changes just when my scene is about to be switched....
Upvotes: 4
Views: 16562
Reputation: 1668
Since you are connecting to a database, you should put this code to run in background using Task
or a Service
to keep the GUI Thread responding to the user inputs. Just remember that only in the GUI Thread you can update the view state (changing the value of a text in your case). You can use a java Thread
and use Platform.runLater
which means that the code inside is schedule to be precessed by the GUI Thread but in your case you are using in the wrong way. First the logic to connect to the database should be inside the method run
of the thread and once the method finish, set the value of the text and do whatever you want after. Also you'll want to show the new Scene
when all the process has been finished to get a chance to the user to see the change in the text. You can change your code in this way:
private void changeSC() throws IOException, InterruptedException, SQLException
{
Thread thrd = new Thread() {
public void run() {
dbConnect();
Platform.runLater(new Runnable() {
@Override public void run() {
status.setText("Connected");
status.setTextFill(Color.GREEN);
//pb.setProgress(1.0);
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Main.window.setScene(primary);
}
});
}
};
thrd.start();
}
If you choose use a Task
you dont have to deal with Platform.runLater
explicitly. You only need to create a task (an implementation of the class Task
), wrap this inside a java Thread
, start it and the set a handler for the different events (eg: setOnSucceeded
). This is your code using Task
:
private void changeSC() throws IOException, InterruptedException, SQLException
{
Task<Void> task = new Task<Void>(){
@Overrdie
protected Void call() {
dbConnect();
return null;
}
};
//start Task
Thread t = new Thread(task);
t.setDaemon(true); // thread will not prevent application shutdown
t.start();
task.setOnSucceeded(event -> {
Parent root = FXMLLoader.load(getClass().getResource("Design.fxml"));
Scene primary = new Scene(root,1024,768);
primary.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
System.out.println("Text changing to COnnected");
status.setTextFill(Color.GREEN);
Main.window.setScene(primary);
});
}
Upvotes: 0
Reputation: 6952
You need to use a Task
, if you have some long running operation. Otherwise when that operation is called from the JavaFX Application Thread it would block the GUI.
If you want to update the GUI from the Task
you have to use Platform.runlater
, which will run the code on the JavaFX Application Thread:
Platform.runlater
Updates to the Nodes
of your GUI have always to be performed on the JavaFx Thread.
When you update status
inside the Listener
of button
it should work.
button.setOnAction(evt -> {
dbConnect();
status.setText("Connected");
// ...
});
If dbConnect()
takes some time, you can use a Task
:
Task<Void> longRunningTask = new Task<Void>() {
@Override
protected Void call() throws Exception {
dbConnect();
Platform.runLater(() -> status.setText("Connected"));
return null;
}
};
button.setOnAction(e -> {
new Thread(longRunningTask).start();
});
Upvotes: 4