Issue
I'm trying to get into JavaFX for making first attempts in making GUIs with Java. Therefore I made a simple neural network which learns the XOR and displays the output in JavaFX. My question is - how can I update the GUI regularly while processing the data?
Everything I achieved so far is a single update in the GUI when the network finished learning. Even if I started the networking in a thread.
I expect that the right handed side of the GUI updates (circle change the colors in dependence of the output) regularly for each n epoch and not only once. The attached image shows the GUI before the network started.
I appreciate any help in advance.
Solution
JavaFX has an "Event Thread", which is responsible for handling button clicks, updating labels, and any other GUI-related tasks. When you call button.setOnAction(e -> doSomething());
, when your button is pressed, doSomething()
happens on the JavaFX thread. During the time that this is running, no other GUI events can occur. This means your interface will completely freeze, which leads to a bad user experience.
Also, you cannot perform GUI operations on any thread other than the JavaFX thread, or you will get an IllegalStateException
. (Try calling Executors.newSingleThreadExecutor().execute(() -> label.setText("hello"));
to see this in action)
Luckily, JavaFX provides methods to get around this.
First, and easiest, is to call your long-running method inside a new thread (perhaps with ExecutorService
s as above), and when you need to modify the interface, wrap those calls in a call to Platform.runLater(() -> updateInterface());
. This will post updateInterface()
to the GUI thread, and will allow it to run.
However, this can be messy, so the preferred method is to use a Service
.
Assume your long running calculation returns an Double
, you create a class extending Service<Double>
, override its createTask()
method, and perform the calculation there, as such:
public class CalculationService extends Service<Double> {
@Override
protected Task<Double> createTask() {
return new Task<Double>() {
@Override
protected Double call() throws Exception {
return doCalculation();
}
};
}
}
Then, in your controller, declare a private final CalculationService service = new CalculationService();
In your controller's initialize()
method, you can then bind the output of this service to anything you want. For example:
calculationDisplayLabel.textProperty().bind(Bindings.createStringBinding(service.valueProperty()));
// continuously updates the label whenever the service calculates a new value
Then, whenever you decide you want to start calculating again, you call service.restart()
to interrupt the process if it is running, and start again from the beginning.
If you want to call code when the value changes, add a listener to the value of the service. For example, if you want it to recalculate as soon as it has finished, call:
service.valueProperty().addListener((obs, old, newValue) -> service.restart());
If in doubt, refer to https://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Service.html
Answered By - cameron1024