Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I safely modify JavaFX GUI nodes from my own Thread?

I try to change a JavaFX GUI node in a thread, but I see the following error:

Exception in thread "Thread-8" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-8

Sample code that generates the error:

public class Controller { 
  public Label label = new Label();

  public void load() {
    MyThread myThread = new MyThread();
    myThread.start();
  }

  public class MyThread extends Thread {
    public void run() {
      ......
      label.setText(""); // IllegalStateException: Not on FX application thread
    }
  }
}
like image 394
user2954895 Avatar asked Nov 13 '13 04:11

user2954895


1 Answers

All manipulations of JavaFX nodes in an active scene graph must run on the JavaFX application thread, otherwise your program may not work correctly.

JavaFX will throw the exception IllegalStateException: Not on FX application thread when you try to modify attributes of scene graph nodes off of the JavaFX application thread. Even if you don't get an IllegalStateException, you should not be modifying scene graph nodes off of the JavaFX application thread because if you do your code may fail unpredictably.

Using Platform.runLater()

Wrap code which manipulates scene graph nodes in Platform.runLater calls to allow the JavaFX system to run the code on the JavaFX application thread.

For example, you can fix your sample program with the following code:

Platform.runLater(() -> label.setText(""));

Alternative using a Task with a message Property

If you are using a JavaFX Task, which has some in-built support for concurrent programming using JavaFX, then you can take advantage of its message property, which can be safely updated from any thread but will relay the property changes only on the JavaFX thread.

Here is an example (from the Task javadoc):

Task<Integer> task = new Task<Integer>() {
    @Override protected Integer call() throws Exception {
        int iterations;
        for (iterations = 0; iterations < 10000000; iterations++) {
            if (isCancelled()) {
                updateMessage("Cancelled");
                break;
            }
            updateMessage("Iteration " + iterations);
            updateProgress(iterations, 10000000);
        }
        return iterations;
    }
}; 

You can then safely bind to the message property to have the changing message value reflected in the UI:

Label iterationLabel = new Label();
iterationLabel.textProperty().bind(
    task.messageProperty()
);

updateMessage javadoc:

Updates the message property. Calls to updateMessage are coalesced and run later on the FX application thread, so calls to updateMessage, even from the FX Application thread, may not necessarily result in immediate updates to this property, and intermediate message values may be coalesced to save on event notifications.

This method is safe to be called from any thread.

There are many examples of usage of updateMessage() in the Task javadoc.

like image 185
jewelsea Avatar answered Oct 01 '22 01:10

jewelsea