I am developing an application with several TextField
objects that need to be updated to reflect changes in associated back-end properties. The TextField
s are not editable, only the back-end may change their content.
As I understand, the correct way about this is to run the heavy computation on a separate thread so as not to block the UI. I did this using javafx.concurrent.Task
and communicated a single value back to the JavaFX thread using updateMessage()
, which worked well. However, I need more than one value to be updated as the back-end does its crunching.
Since the back-end values are stored as JavaFX properties, I tried simply binding them to the textProperty
of each GUI element and let the bindings do the work. This doesn't work, however; after running for a few moments, the TextField
s stop updating even though the back-end task is still running. No exceptions are raised.
I also tried using Platform.runLater()
to actively update the TextField
s rather than binding. The issue here is that the runLater()
tasks are scheduled faster than the platform can run them, and so the GUI becomes sluggish and needs to time to "catch up" even after the back-end task is finished.
I found a few questions on here:
Logger entries translated to the UI stops being updated with time
Multithreading in JavaFX hangs the UI
but my issue persists.
In summary: I have a back-end making changes to properties, and I want those changes to appear on the GUI. The back-end is a genetic algorithm, so its operation is broken down into discrete generations. What I would like is for the TextField
s to refresh at least once in between generations, even if this delays the next generation. It is more important that the GUI responds well than that the GA runs fast.
I can post a few code examples if I haven't made the issue clear.
UPDATE
I managed to do it following James_D's suggestion. To solve the issue of the back-end having to wait for the console to print, I implemented a buffered console of sorts. It stores the strings to print in a StringBuffer
and actually appends them to the TextArea
when a flush()
method is called. I used an AtomicBoolean to prevent the next generation from happening until the flush is complete, as it is done by a Platform.runLater()
runnable. Also note that this solution is incredibly slow.
Not sure if I completely understand, but I think this may help.
Using Platform.runLater(...) is an appropriate approach for this.
The trick to avoiding flooding the FX Application Thread is to use an Atomic variable to store the value you're interested in. In the Platform.runLater(...) method, retrieve it and set it to a sentinel value. From your background thread, update the Atomic variable, but only issue a new Platform.runLater(...) if it's been set back to its sentinel value.
I figured this out by looking at the source code for Task. Have a look at how the updateMessage(..) method (line 1131 at the time of writing) is implemented.
Here's an example which uses the same technique. This just has a (busy) background thread which counts as fast as it can, updating an IntegerProperty. An observer watches that property and updates an AtomicInteger with the new value. If the current value of the AtomicInteger is -1, it schedules a Platform.runLater().
In the Platform.runLater, I retrieve the value of the AtomicInteger and use it to update a Label, setting the value back to -1 in the process. This signals that I am ready for another UI update.
If you really want to "drive" the back end from the UI: that is throttle the speed of the backend implementation so you see all updates, consider using an
AnimationTimer
. AnAnimationTimer
has ahandle(...)
which is called once per frame render. So you could block the back-end implementation (for example by using a blocking queue) and release it once per invocation of the handle method. Thehandle(...)
method is invoked on the FX Application Thread.The
handle(...)
method takes a parameter which is a timestamp (in nanoseconds), so you can use that to slow the updates further, if once per frame is too fast.For example:
The best way to performing this is by usage of
Task
in JavaFx. This is be by far the best technique I've come across to update UI Controls in JavaFx.