9

I have a question about multi-threading and the binding of a StringProperty.

I have a class CacheManager, which contains a Thread which updates my cache with the changes on the server. Now, I want to notify the user with a text and percentage of the progress (which are a Label and ProgressBar in JavaFX). I use public static DoubleProperty and StringProperty for this, which are defined in the CacheManager class. I just bind it like this:

progressBar.progressProperty().bind(CacheManager.progress);
someLabel.textProperty().bind(CacheManager.status);

Now, in the Updater thread, I update these Properties. With DoubleProperty this works just fine, and the ProgressBar is showing the progress perfectly. However, updating the Label with the status (which is the text from the StringProperty) throws an error: java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-9

Now, my question is: Why does the DoubleProperty work just fine, while the StringProperty throws an error? What is the difference between them considering multi-threading?

Any ideas on a redesign are also welcome and any help is greatly appreciated!

bashoogzaad
  • 4,611
  • 8
  • 40
  • 65
  • Have you searched the reason of "IllegalStateException: Not on FX application thread"? – Uluk Biy Jun 16 '15 at 09:28
  • I just throws the error in the updater `Thread`, when I try to alter the text for the second time. So on this line: `status.set("Updating...")` – bashoogzaad Jun 16 '15 at 09:46
  • 3
    I *bet* it is because the skin of the `ProgressBar` sets an `InvalidationListener` on the `progressProperty`, while the skin of the `Label` sets a change listener on the `textProperty`. Despite the behaviour of `progressBar.progressProperty` being more convenient, I think the behaviour of `Label.textProperty` is more concise with the fact that all UI changes must take place in the UI thread. Use `Platform.runLater()` or consider JavaFX's `javafx.concurrent.Service` class, that offers the features you want. – Nikos Paraskevopoulos Jun 16 '15 at 11:52
  • Thanks for this information @NikosParaskevopoulos! This is what I was asking for, just to extend my knowledge about JavaFX. – bashoogzaad Jun 16 '15 at 13:11

1 Answers1

14

It is wrong to call code that results in changes to the UI from a thread other than the FX Application Thread, regardless of whether or not it throws an exception. The FX toolkit makes a best effort to throw an exception if you violate this rule, but in some cases the effect on performance is too great to perform the check. If you create these bindings, then any subsequent changes to the properties to which you have bound must be executed on the FX Application Thread. I.e., if you are running in a background thread, you must change the properties with code like:

Platform.runLater(() -> CacheManager.progress.set(...));

and

Platform.runLater(() -> CacheManager.status.set(...));

Since you probably don't want your service code to be tied to JavaFX (via the Platform class), you could consider using listeners instead of bindings, and scheduling the updates from the listeners:

CacheManager.progress.addListener((obs, oldValue, newValue) -> 
    Platform.runLater(() -> progressBar.setProgress(newValue.doubleValue())));
CacheManager.status.addListener((obs, oldStatus, newStatus) -> 
    Platform.runLater(() -> someLabel.setText(newStatus)));

If you replace the bindings with these listeners, then you are free to update the properties on any thread.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thanks for this answer! Really great that you show two ways to do it! – bashoogzaad Jun 16 '15 at 13:10
  • 4
    This answer basically boils down to "don't bind properties in the UI"; this is really the only way? It seems like an obvious flaw in JavaFX's design -- if changing a property needs to happen on the UI thread, it should be the binding code's responsibility to do it there; you shouldn't be changing your data model on the UI thread – Michael Mrozek Apr 30 '16 at 22:21
  • The approach I often take is to split the data model: so I will often have a UI model which updates properties on the UI thread and references a service, or similar. The UI model observes changes in the service and propagates those changes to changes to its own properties, managing the threading as part of that propagation. Then the controller can bind the view to the UI model properties. See http://www.oracle.com/technetwork/articles/java/javafxinteg-2062777.html for some similar techniques. – James_D Apr 30 '16 at 22:27