Search code examples
javatextareajava-11javafx-11

Synchronous textArea.clear() followed by textArea.setText() does not clear text


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).