0

I have created a JavaFX application which I would want to have the following functionality:

Start running - when running appendText() to a JavaFX TextArea every 2 seconds, and then randomly stop everything and show an Alert.Error.

This is really basic. I searched a bit on Stack Overflow and I could not find anything. If this has already been answered please point me to it.

And to explain my problem a bit better, here is some code and some screenshots.

package com.example.threadstestingplatformrunlater;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.io.IOException;

import static java.lang.Thread.sleep;

public class HelloApplication extends Application {
    
    private static int counter;
    private TextArea textArea;
    
    @Override public void start(Stage stage) throws IOException {
        
        VBox vBox = new VBox();
        textArea = new TextArea();
        vBox.getChildren().add(textArea);
        Scene scene = new Scene(vBox);
        
        vBox.setStyle(ThemeClass.DARK_THEME);
        stage.setTitle("Hello!");
        stage.setScene(scene);
        stage.show();
        
        System.out.println("Thread Name: " + Thread.currentThread().getName() );
        Thread thread = new Thread(this::run);
        thread.start();
    }
    
    private void functionContainingRunLater() throws InterruptedException {
        System.out.println("Thread Name: " + Thread.currentThread().getName() );
        continuouslyWrite(textArea);
    }
    
    public static void continuouslyWrite(TextArea textArea) throws InterruptedException {
        for (int i = 0; i < 20; i++) {

            if(i == 10) {
                
                Platform.runLater(()-> {
                    Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
                    alert.show();
                });
            }
            
            System.out.println(counter);
            textArea.appendText(Thread.currentThread().getName() + ": " + counter+ "\n");
            
            counter++;
            if (counter > 500) {
                break;
            }
            
            sleep(200);
        }
    }
    
    public static void main(String[] args) {
        launch();
    }
    
    private void run() {
        try {
            functionContainingRunLater();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

This code runs and it is writing lines on the TextArea as it goes. In real time. I had quite some issues getting it this far - more or less, it would only write after everything has finished.

Now I would like to stop it when i == 10, but I only show the Alert.Message and then it goes until the end. I would like for the code to pause. How could I achieve that?

I want it to stop at 10

I have tried with:

    public static void continuouslyWrite(TextArea textArea) throws InterruptedException {
        for (int i = 0; i < 20; i++) {

            if(i == 10) {
                    Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
                    alert.show();
            }
            
            System.out.println(counter);
            textArea.appendText(Thread.currentThread().getName() + ": " + counter+ "\n");
            
            counter++;
            if (counter > 500) {
                break;
            }
            
            sleep(200);
        }
    }
    

But in this case, I get the error of not being on JavaFX thread.

Exception in thread "Thread-3" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3

Could you help me understand how could I achieve this? Is what I want to do even possible? Somehow I can not get my head around it.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Jack
  • 129
  • 9
  • 3
    you __must not__ change any property of a node off the fx application thread! learn how to use fx concurrency support, see f.i. https://stackoverflow.com/questions/9966136/javafx-periodic-background-task – kleopatra Feb 14 '22 at 15:48
  • 1
    Perhaps a duplicate of [Can I pause a background Task / Service?](https://stackoverflow.com/questions/14941084/javafx2-can-i-pause-a-background-task-service). – jewelsea Feb 14 '22 at 17:19
  • 2
    Reopened from original dupe because it's not possible to directly apply the Animation-based solution, as `showAndWait()` can't be called from a key frame. The animation-based approach is still probably optimal, but I included an answer referencing pausing the background thread as well. – James_D Feb 14 '22 at 17:36
  • Oh nice.. so i guess i have quite some researching to do.. i thought it was more trivial .. thank you all for your answers :) – Jack Feb 14 '22 at 19:24

1 Answers1

3

As noted in a comment, as well as in many questions on this site and prominently in the API docs, you must not update the UI from a background thread. The TextArea does not check for this and throw an exception, but you are still violating the threading rules of JavaFX.

Just because your code happens to work on your particular system and with the particular JavaFX/JVM versions you are using does not mean that your code is not broken.

In general, to run something periodically that updates the UI, you should use the Animation API. See JavaFX periodic background task, in particular this answer. In your case, this is slightly tricky because you must not call showAndWait() directly from an animation event handler.

So you can do something like this:

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.io.IOException;

public class HelloApplication extends Application {

    private int counter;
    private TextArea textArea;

    @Override public void start(Stage stage) throws IOException {

        VBox vBox = new VBox();
        textArea = new TextArea();
        vBox.getChildren().add(textArea);
        Scene scene = new Scene(vBox);

        stage.setTitle("Hello!");
        stage.setScene(scene);
        stage.show();

        System.out.println("Thread Name: " + Thread.currentThread().getName() );

        Timeline timeline = new Timeline();
        KeyFrame keyFrame = new KeyFrame(Duration.seconds(0.2), event -> {
            textArea.appendText(Thread.currentThread().getName() + ": " + counter+ "\n");
            counter++;
            if (counter == 10) {
                timeline.pause();
                Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
                Platform.runLater(() -> {
                    alert.showAndWait();
                    timeline.play();
                });
            }
        });
        timeline.getKeyFrames().add(keyFrame);
        timeline.setCycleCount(Animation.INDEFINITE);
        timeline.play();
    }



    public static void main(String[] args) {
        launch();
    }


}

If you really want/need to use threads (which I honestly don't think you do), you can use the technique shown in JavaFX2: Can I pause a background Task / Service?:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class HelloApplication extends Application {

    private int counter;
    private TextArea textArea;

    @Override public void start(Stage stage) throws IOException {

        VBox vBox = new VBox();
        textArea = new TextArea();
        vBox.getChildren().add(textArea);
        Scene scene = new Scene(vBox);

        stage.setTitle("Hello!");
        stage.setScene(scene);
        stage.show();

        System.out.println("Thread Name: " + Thread.currentThread().getName() );

        Thread thread = new Thread(() -> {
           try {
               for (int i = 0; i < 20; i++) {

                   if(i == 10) {

                       FutureTask<Void> interrupt = new FutureTask<>(() -> {
                           Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
                           alert.showAndWait();
                           return null ;
                       });
                       Platform.runLater(interrupt);
                       interrupt.get();
                   }

                   System.out.println(counter);
                   Platform.runLater(() ->
                           textArea.appendText(Thread.currentThread().getName() + ": " + counter+ "\n"));

                   counter++;
                   if (counter > 500) {
                       break;
                   }

                   Thread.sleep(200);
               }

           } catch (InterruptedException exc) {
               Thread.currentThread().interrupt();
           } catch (ExecutionException exc) {
               exc.printStackTrace();
           }
        });

        thread.start();

    }



    public static void main(String[] args) {
        launch();
    }


}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Nice :) I ll try this :) I have no idea what I really need.. it was more of a research question at the moment.. just to see if it was possible - I guess i have quite some studying to do before I will get the hang of it :) Thank you :) – Jack Feb 14 '22 at 19:26
  • 1
    Fortunately, the provided solutions likely provide you with the basics of what you need. You just need to do more research and study if you wish to understand the concepts behind the solutions (which is probably a good idea in any case). – jewelsea Feb 14 '22 at 22:44
  • I have just tested your second solution and it works awesome :) now I just need to understand it :)) Thank you :) – Jack Feb 16 '22 at 09:04