I am trying to provide feedback in a JavaFX 8 application when a user chooses a menu item that launches a blocking process in another thread. In my real application it's a file download, but I have created a test case using minimal code by way of example:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.MenuButton;
import javafx.scene.control.ToolBar;
import javafx.scene.control.MenuItem;
import javafx.stage.Stage;
public class BlockingThreadTestCase extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
MenuItem menuItem = new MenuItem("Start");
MenuButton menuButton = new MenuButton();
menuButton.setText("Async Process");
menuButton.getItems().addAll(menuItem);
menuItem.setOnAction(event -> {
menuButton.setText("Running...");
Platform.runLater(() -> {
try {
// Simulate a blocking process
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
menuButton.setText(menuButton.getText() + "Done!");
});
});
final ToolBar toolbar = new ToolBar(menuButton);
final Scene scene = new Scene(toolbar);
primaryStage.setScene(scene);
primaryStage.setWidth(150);
primaryStage.show();
}
}
Here's how it's supposed to work: When you select the "Start" menu item, the main menu text should immediately change to "Running...", and then it should append "Done!" after the 5-second sleep that simulates my file download.
What is actually happening is both text updates are firing after the blocking process is done, even though I'm using Platform.runLater()
. What am I doing wrong?
The easiest way to do this is by using a Task
. Platform.runLater
is only needed if you need to update the UI from a different thread and therefore is not necessary in your case. If you would like to track progress of the background task while it is running, you may use updateMessage
and updateProgress
methods in the task to safely pass messages to the UI thread without worrying about the scheduling via EDT. You may find more information on this here https://docs.oracle.com/javase/8/javafx/interoperability-tutorial/concurrency.htm .
See the minimal working example below.
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ToolBar;
import javafx.stage.Stage;
public class BlockingThreadTestCase extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
MenuItem menuItem = new MenuItem("Start");
MenuButton menuButton = new MenuButton();
menuButton.setText("Async Process");
menuButton.getItems().addAll(menuItem);
menuItem.setOnAction(event -> {
menuButton.setText("Running...");
Task task = new Task<Void>() {
@Override
public Void call() {
//SIMULATE A FILE DOWNLOAD
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
};
task.setOnSucceeded(taskFinishEvent -> menuButton.setText(menuButton.getText() + "Done!"));
new Thread(task).start();
});
final ToolBar toolbar = new ToolBar(menuButton);
final Scene scene = new Scene(toolbar);
primaryStage.setScene(scene);
primaryStage.setWidth(150);
primaryStage.show();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With