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 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.
CodePudding user response:
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();
}
}