I’m facing a problem with thread and interface.
In my example I have a long process. Along calculations I must stop and ask the user informations. I cannot do that before. So I have to open a window and retrieve the user answer.
There are 2 ways that I know of :
- create a new Thread.
- create a Runnable along with the use of Platform.RunLater.
The « copy multiple file » process will be a good example to explain what’s the problem. In this example we know we have to « launch » a long process : Copy every single file (one by one). The main interface has a ProgressBar. The goal is to update the ProgressBar on a regular basis. Then ; along the computation emerges a specific case which require the user attention.
If I used the Thread approach : The ProgressBar updates properly (using bound properties). I end up with the exception « not a JavaFx application » as soon as I try to open a new window (from this side process). This is normal and documented on this very website.
If I used the Runnable approach : the problem is about updates. The new window opens but the progress bar isn’t changed until a « refresh » occurs (see code example in the zip file linked below).
Any other suggestion I could find is not well documented or even explained properly (like service). So I'm stuck.
I’m a surprised not to be able to do that. I’m wondering if I’m doing something wrong or if I don’t use the right approach. Maybe this is JavaFx limitation. Any help greatly appreciated.
Thanks.
Golden Rule of JavaFX: Any and all interactions with a live scene graph must occur on the JavaFX Application Thread. No exceptions.
In JavaFX, if you're going to run work on a background thread, and you need that work to report progress back to the user, then you should first consider using a
javafx.concurrent.Task. It provides a nice API for publishing messages, progress, and a result on the JavaFX Application Thread. From there, you just need to figure out how to prompt the user for more information in the middle of the task executing on a background thread.The simplest solution, at least in my opinion, is to use a
CompletableFuture. You can configure it to execute aSupplieron the FX thread and have the background thread calljoin()to wait for a result. This only requires that you provide some sort of callback to yourTaskfor the future to invoke. That callback could be anything. Some options include:java.util.function.Supplierjava.util.function.Functionjavafx.util.Callback(essentially equivalent to aFunction)...
Or even your own interface/class. It doesn't have to be a functional interface, by the way.
Note this callback/listener idea can be applied to more than just prompting the user. For instance, if you've created a model/business class to perform the actual work, and you don't want this class to know anything about JavaFX, then you can adapt it to report messages/progress in its own way. The
Taskwould then register listeners/callbacks as needed. That essentially makes theTaskjust a lightweight adapter allowing JavaFX to observe the progress and update the UI accordingly.Proof of Concept
The code below launches a task that fakes long-running work. This task will prompt the user to ask if the task should continue upon half the work being completed. The task then waits for a response, and whether or not it will continue depends on said response.
The primary window is displaying a progress bar and message label to show such things still work as expected. When the task prompts the user, a modal alert will be displayed that will wait for the user to respond.
MockTask.java
Note I use a
Callback<String, Boolean>for simplicity. But that interface is generic, and so it can be used with any types you want.Main.java