Issue
When a button is clicked, I'd like to clear the textArea, do some work, then print results in the textArea, all in the same method, synchronously.
public void resetClicked(MouseEvent mouseEvent) {
textArea.clear();
String result = someSynchronousWork();
textArea.setText(result);
}
What happens is, the textArea is updated, but the clear action is not visible. The work takes several seconds. If I comment out everything except the textArea.clear()
, it works.
Solution
As I mention in my comment, JavaFX doesn't render the next frame until a "pulse" occurs. This won't happen when you clear the text, run a long-running task, and then set the text all in one method; the pulse happens after all this occurs which means what gets rendered is the new text. Also, running a several-seconds-long task on the JavaFX Application Thread is not a good idea. All blocking and/or long-running tasks should be done on a background thread—otherwise your GUI becomes unresponsive (and your users become unhappy/nervous).
If this task is too simple to use a Task
for then you could try a CompletableFuture
, which may make it easier for you to invoke simple things asynchronously.
public void resetClicked(MouseEvent event) {
event.consume();
textArea.clear();
CompletableFuture.supplyAsync(this::someSynchronousWork)
.whenCompleteAsync((result, error) -> {
if (error != null) {
// notify user
} else {
textArea.setText(result);
}
}, Platform::runLater);
}
Depending on how you want to handle errors, you can do different things. For instance:
// can't ever be an error
supplyAsync(this::someSynchronousWork)
.thenAcceptAsync(textArea::setText, Platform::runLater);
// just want to show "Error" in text area on error
supplyAsync(this::someSynchronousWork)
.exceptionally(error -> "ERROR")
.thenAcceptAsync(textArea::setText, Platform::runLater);
Note: These examples will execute someSynchronousWork()
using the common ForkJoinPool
. You can customize this by passing an Executor
to supplyAsync
.
Note: You may want to disable some of the UI components (e.g. the button) while the task is running to prevent multiple tasks being launched at once. Enable the UI components when the task completes.
Also, you seem be using the onMouseClicked
property of the Button
to process actions. Consider using the onAction
property instead; an onAction
handler is notified for more than just mouse clicks (e.g. when the button has focus and Space or Enter is pressed).
Answered By - Slaw
Answer Checked By - Marilyn (JavaFixing Volunteer)