如何停止 JavaFX 线程并使用警告框等待用户输入

How to stop a JavaFX thread and wait for user input with an Alert Box

我创建了一个 JavaFX 应用程序,我希望它具有以下功能:

开始 运行 - 当 运行 每 2 秒将 appendText() 添加到 JavaFX TextArea 时,然后随机停止一切并显示 Alert.Error.

这真的很基础。我在 Stack Overflow 上搜索了一下,但找不到任何东西。如果此问题已得到解答,请指点我。

为了更好地解释我的问题,这里有一些代码和一些屏幕截图。

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();
        }
    }
}

此代码运行并在运行时在 TextArea 上写入行。实时。到目前为止我遇到了很多问题 - 或多或少,它只会在一切都完成后才写入。

现在我想在 i == 10 时停止它,但我只显示 Alert.Message 然后它一直持续到最后。我想让代码暂停。我怎样才能做到这一点?

我试过:

    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);
        }
    }
    

但在这种情况下,我得到了不在 JavaFX 线程上的错误。

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

你能帮我理解我怎样才能做到这一点吗?我想做的事有可能吗?不知怎的,我无法理解它。

如评论中所述,以及本网站上的许多问题和 API 文档中的显着位置,您 不得 更新 UI 来自后台线程。 TextArea 不会对此进行检查并抛出异常,但您仍然违反了 JavaFX 的线程规则。

仅仅因为您的代码恰好可以在您的特定系统和您正在使用的特定 JavaFX/JVM 版本上运行并不意味着您的代码没有损坏。

一般来说,对于 运行 定期更新 UI 的内容,您应该使用动画 API。参见 JavaFX periodic background task, in particular this answer。在您的情况下,这有点棘手,因为您不能直接从动画事件处理程序调用 showAndWait()

所以你可以这样做:

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();
    }


}

如果您真的 want/need 使用线程(老实说我不认为您会这样做),您可以使用 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();
    }


}