是否可以在FX线程中显示节点

时间:2016-07-21 09:47:46

标签: multithreading javafx-8

我正在制作一个读取文本文件的程序。我想要做的是显示在文件读取之前或期间在单独的线程中创建的任意节点(警报或其他节点)。我尝试使用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之后初始化。

2 个答案:

答案 0 :(得分:1)

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

  1. 更改场景图(即创建新场景或窗口,或更改已显示的节点状态)必须在FX应用程序主题上完成。
  2. 长时间运行的进程应该在后台线程上执行(即不是FX应用程序线程),否则UI将无法响应。
  3. 您的第一个代码块违反了第二个规则(可能,您没有显示太多上下文),而您的第二个代码块违反了第一个规则。

    所以基本上你需要:

    1. 显示FX Application Thread
    2. 中的对话框
    3. 启动一个在后台处理文件的新线程
    4. 从新主题中,安排对FX Application Thread
    5. 上新UI的任何更改
    6. 处理文件完成后,更新FX Application Thread
    7. 上的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);
          }
      }
      

答案 1 :(得分:0)

所以我终于明白了。我不得不将我的文件加载代码和进度更新移动到任务,因此它不会阻止FX线程。指示器显示加载文件的进度。

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