【问题标题】:Is it possible to show a Node during FX thread是否可以在 FX 线程期间显示节点
【发布时间】:2016-07-21 09:47:45
【问题描述】:

我正在制作一个读取文本文件的程序。我想要做的是显示在文件读取之前或期间在单独的线程中创建的任意节点(警报或其他节点)。我尝试像这样使用TaskPlatform.runLater()

if (filetoopen != null)
            {
                Platform.runLater(new Runnable() {
                    @Override
                    void run() {
                            Alert alert=new Alert(Alert.AlertType.INFORMATION)
                            alert.setHeaderText('TEST')                         
                    }
                })
                //method to read the file
                Tools.convertFromFile(filetoopen,newredactor)
                lastDirectory = filetoopen.getParentFile()
            }

我想显示读取文件的警报或进度条,但控件在读取完成后初始化。那么,是否可以在读取文件时显示带有进度条的节点?还是我创建的 Runnable 会一直执行到最后?

编辑:对任务的尝试:

class Alerter extends Task{
    Alerter(File f,Editor e)
    {
        file=f
        editor=e
    }
    File file
    Editor editor
    @Override
    protected Object call() throws Exception {
        Dialog dialog=new Dialog()
        DialogPane dp=dialog.getDialogPane()
        dp.setHeaderText('TEST')
        dp.getButtonTypes().add(new ButtonType('Cancel',ButtonBar.ButtonData.CANCEL_CLOSE))
        dialog.setOnCloseRequest(new javafx.event.EventHandler<DialogEvent>() {
            @Override
            void handle(DialogEvent event) {
                dialog.close()
            }
        })
        dialog.show()
        Tools.convertFromFile(file,editor)
        return null
    }
}

对话框在Tools.convertFromFile之后仍然初始化。

【问题讨论】:

  • “我尝试使用TaskPlatform.runLater()”:这是正确的做法,但您发布的代码中没有Task
  • 好的,我添加了一个Task的尝试
  • 查看 controlsFX 和 ProgressDialog。你给它发送一个任务,然后启动它,它就会显示你需要的东西。
  • 您的Task 版本在后台线程上创建并显示Dialog,这违反了JavaFX 线程规则。看我的回答。

标签: multithreading javafx-8


【解决方案1】:

JavaFX(以及几乎所有其他 UI 工具包)中有两个线程规则:

  1. 场景图的更改(即创建新场景或窗口,或更改已显示节点的状态)必须在 FX 应用程序线程上完成。
  2. 应在后台线程(即,不是 FX 应用程序线程)上执行长时间运行的进程,否则 UI 将变得无响应。

您的第一个代码块违反了第二条规则(可能您没有显示太多上下文)并且您的第二个代码块违反了第一条规则。

所以基本上你需要:

  1. 显示来自 FX 应用程序线程的对话框
  2. 启动一个在后台处理文件的新线程
  3. 在新线程中,安排对 FX 应用程序线程上新 UI 的任何更改
  4. 文件处理完成后,更新 FX 应用程序线程上的 UI

您可以使用 Platform.runLater(...) 安排代码在 FX 应用程序线程上运行,但 Task class 为这些更新提供了更方便的 API。

所以:

// set up and show dialog:
ProgressBar progressBar = new ProgressBar();
DialogPane dialogPane = new DialogPane();
dialogPane.getButtonTypes().setAll(ButtonType.OK);
dialogPane.setHeaderText("Processing file");
dialogPane.setContent(progressBar);
dialogPane.lookupButton(ButtonType.OK).setDisable(true);
Dialog dialog = new Dialog();
dialog.setDialogPane(dialogPane);
dialog.show();

// create task:
Task<Void> task = new Task<Void>() {
    @Override
    public Void call() throws Exception {
        Tools.convertFromFile(file, editor);
        // can call updateProgress(...) here to update the progress periodically
        return null ;
    }
};

// update progress bar with progress from task:
progressBar.progressProperty().bind(task.progressProperty());

// when task completes, update dialog:
task.setOnSucceeded(event -> {
    dialogPane.lookupButton(ButtonType.OK).setDisable(false);
    progressBar.progressProperty().unbind();
    progressBar.setProgress(1);
    dialogPane.setHeaderText("Processing complete");
});

// handles errors:
task.setOnFailed(event -> {
    dialogPane.lookupButton(ButtonType.OK).setDisable(false);
    progressBar.progressProperty().unbind();
    progressBar.setProgress(0);
    dialogPane.setHeaderText("An error occurred");
});

// run task in background thread:
Thread thread = new Thread(task);
thread.start();

请注意,您的 Tools.convertFromFile(...) 方法是从后台线程调用的,因此它不得更新 UI(或者至少该方法中任何更新 UI 的调用都必须包含在Platform.runLater(...))。

这是一个完整的 SSCCE(它只是作为一个长时间运行进程的演示而休眠):

import java.util.Random;

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.DialogPane;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class TaskWithProgressDemo extends Application {

    @Override
    public void start(Stage primaryStage) {
        Button button = new Button("Start process");
        button.setOnAction(e -> {

            button.setDisable(true);

            // set up and show dialog:
            ProgressBar progressBar = new ProgressBar();
            DialogPane dialogPane = new DialogPane();
            dialogPane.getButtonTypes().setAll(ButtonType.OK);
            dialogPane.setHeaderText("Processing file in progress");
            dialogPane.setContent(progressBar);
            dialogPane.lookupButton(ButtonType.OK).setDisable(true);
            Dialog<Void> dialog = new Dialog<Void>();
            dialog.setDialogPane(dialogPane);
            dialog.show();

            // create task:
            Task<Void> task = new Task<Void>() {
                @Override
                public Void call() throws Exception {

                    Random rng = new Random();

                    for (int i = 0 ; i <= 100 ; i++) {
                        Thread.sleep(rng.nextInt(40));
                        updateProgress(i, 100);
                    }

                    if (rng.nextBoolean()) {

                        System.out.println("Simulated error");
                        throw new Exception("An unknown error occurred");
                    }

                    return null ;
                }
            };

            // update progress bar with progress from task:
            progressBar.progressProperty().bind(task.progressProperty());

            // when task completes, update dialog:
            task.setOnSucceeded(event -> {
                dialogPane.lookupButton(ButtonType.OK).setDisable(false);
                button.setDisable(false);
                progressBar.progressProperty().unbind();
                progressBar.setProgress(1);
                dialogPane.setHeaderText("Processing complete");
            });

            // handles errors:
            task.setOnFailed(event -> {
                dialogPane.lookupButton(ButtonType.OK).setDisable(false);
                button.setDisable(false);
                progressBar.progressProperty().unbind();
                progressBar.setProgress(0);
                dialogPane.setHeaderText("An error occurred");
            });

            // run task in background thread:
            Thread thread = new Thread(task);
            thread.start();
        });

        StackPane root = new StackPane(button);
        root.setPadding(new Insets(20));
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

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

【讨论】:

  • 感谢您的示例。是否也可以让用户在加载过程中与 GUI 交互?
  • 不确定你的意思。在此示例中,唯一阻止用户与 UI 交互的是显示了一个模式对话框。
  • 没错,我想我必须创建一个新阶段而不是一个对话框来不阻塞用户输入。
  • 或使用对话并致电dialog.initModality(Modality.NONE);
【解决方案2】:

所以我终于想通了。我必须将我的文件加载代码和进度更新都移动到一个任务中,这样它就不会阻塞 FX 线程。指示器显示加载文件的进度。

编辑:要在单独的非阻塞窗口中实现进度显示,必须使用新的舞台而不是其他任何东西。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多