【问题标题】:Building Multithreading ProgressBar in JavaFx在 JavaFx 中构建多线程 ProgressBar
【发布时间】:2015-09-15 17:01:52
【问题描述】:

我正在尝试构建一个进度条,当我的应用程序正在检索数据并将其填充到 GUI 时,该进度条正在更新。我认为进度条会被大量重复使用,所以我决定创建自己的类。但是,我不相信我通常理解 Worker/Task 或多线程来创建可重用的情况。创建可以侦听应用程序线程并相应更新进度条的进度条的推荐方法是什么。这是我的尝试:

// Simple Progress Bar View as Pop Up
public class ProgressIndicatorUtil{

    @FXML
    private ProgressBar progressBar;
    @FXML
    private Label statusLabel;
    @FXML
    private Button closeButton;
    @FXML
    private Label valueLabel;

    private Worker worker;
    private Stage stage;

    public void setPopUpStage(Stage stage) {
        this.stage = stage;
    }

    public void setWorker(Worker worker) {
        this.worker = worker;
    }

    public void setLinkToMainPage(Object controller) {

        ((Task<String>) worker).setOnSucceeded(event -> stage.close());

        ((Task<String>) worker).setOnCancelled(event -> {
                closeButton.setVisible(true);
                stage.requestFocus();
                statusLabel.setTextFill(Color.RED);}
        );

        valueLabel.textProperty().bind(Bindings.format("%5.1f%%", worker.progressProperty().multiply(100)));
        progressBar.progressProperty().bind(worker.progressProperty());
        statusLabel.textProperty().bind(worker.messageProperty());
    }

    @FXML
    private void handleClose(ActionEvent e){
        stage.close();
    }
}

调用视图弹出窗口并运行进度条线程的控制器。

public class MyController{

   //Controller calling the view and disabling the main GUI
   private void loadProgressBar(Worker worker){

        try{

            FXMLLoader loader = new FXMLLoader(getClass()
                    .getClassLoader().getResource("main/resources/fxml/ProgressBar.fxml"));
            AnchorPane pane = (AnchorPane)loader.load();
            Stage popUpStage = new Stage();
            popUpStage.initModality(Modality.WINDOW_MODAL);
            Scene scene = new Scene(pane);
            popUpStage.setScene(scene);

            ProgressIndicatorUtil controller = loader.getController();
            controller.setPopUpStage(popUpStage);
            controller.setWorker(worker);
            controller.setLinkToMainPage(this);
            mainPane.setDisable(true);
            popUpStage.showingProperty().addListener((obs, hidden, showing) -> {
                    if(hidden) mainPane.setDisable(false);});
            popUpStage.show();

        }catch(IOException e){
            e.printStackTrace();
        }   
    }



   private void runProgressBar(Worker worker) {
        new Thread((Runnable) worker).start();
   }


    //A user action that runs the progress bar and GUI
    @FXML
    private void aBigProcessingEvent(ActionEvent event) {

        Worker worker = new Task<String>(){

            @Override
            protected String call() throws Exception {        

                updateProgress(0, 3);
                updateMessage("Clearing Data");

                processingEvent01();

                updateProgress(1, 3);
                updateMessage("Retriving Data And Adding To List");

                processingEvent02();

                updateProgress(2, 3);
                updateMessage("Populating Data");

                processingEvent03();

                updateProgress(3, 3);
                return "Finished!";
            }
        };

        loadProgressBar(worker);
        runProgressBar(worker);
    }
}

该程序在视觉上工作正常,但它会引发这样的异常 (Not On FX Application Thread) 并在我的“处理事件”方法上运行 Platform.runLater() 将导致我的进度条立即为 100%,但它不会不再抛出异常。关于如何将应用程序修改方法和工作方法分开,同时保持进度与 processingEvent 方法连接的任何建议?非常感谢。

【问题讨论】:

  • 您可以尝试创建一个 Integer 对象,然后将其传递给线程并在任务完成时递增。然后将您的进度设置为整数/任务数的值。确保在访问对象的引用时使用同步。
  • @MarcinDeszczynski 你不能这样做:它肯定会生成 OP 描述的那种IllegalStateExceptions。绑定到任务的进度属性并调用updateProgress(...) 正是这样做的正确方法。
  • 这段代码对我来说很合适;我无法测试它,因为它不完整。哪一行抛出IllegalStateExceptionprocessingEvent*() 方法有什么作用?您应该将所有耗时的方法保留在后台线程中,但将任何更新 UI 的方法调用包装在 Platform.runLater(...) 中。 (updateProgressupdateMessage 方法被设计为从后台线程调用;它们基本上为您处理对Platform.runLater(...) 的调用。)

标签: java multithreading javafx


【解决方案1】:

您发布的(不完整的)代码没有任何问题,因此您的代码的其他部分存在错误。由于代码不完整,我必须对正在发生的事情做出一些有根据的猜测。 (注意:如果您在发布问题时可以创建complete examples 实际上会更好,这样您就可以确保包含您所询问问题的原因。)

由于您收到IllegalStateException“不在 FX 应用程序线程上”,因此您必须从后台线程更新 UI。由于您发布的唯一在后台线程中运行的代码位于您在 aBigProcessingEvent() 中创建的 Task 中,因此 UI 更新必须在您未显示的一个或多个 processingEventXX() 方法中发生。

如果您将对processingEventXX() 的调用封装在Platform.runLater() 中,那么您将看不到进度条的任何渐进式更新。 Platform.runLater() 安排您提供的可运行对象在 FX 应用程序线程上执行并立即退出。 Task 中没有其他需要时间运行的代码,所以整个任务在很短的时间内就完成了,到 FX Application Thread 渲染下一帧的时候,任务就完成了,进度属性在1.

因此,您的 processingEventXX() 方法可能需要时间来执行,并且还会更新 UI。您必须在 Platform.runLater(...) 中的这些方法中包装更新 UI 的调用。包裹在Platform.runLater(...) 中的代码必须包含需要很长时间才能运行的代码。 IE。他们应该看起来像

private void processingEvent01() {
    // some long-running process here...

    Platform.runLater(() -> {
        // update UI here....
    });

    // some other long-running process here (perhaps)
}

【讨论】:

  • 感谢和抱歉给您带来的困惑,詹姆斯。我不确定我是否应该在工作线程/任务线程中运行更新 UI 的代码,因为它会导致 IllegalStateException。因此,我认为这是我的设计缺陷,我想知道是否可以在 Worker worker = new Task(){} 函数之外使用长进程运行 processingEvent01,然后以某种方式允许后台线程/进度条监听更新.我想那时我将不得不使用 Platform.runLater。
  • 阅读我的回答here 看看是否有帮助。
  • 是的,该链接确实帮助我了解了 JavaFX 多线程的基础知识,而上述使用 Platform.runLater() 进行目标 GUI 更新的答案绝对是我所需要的。再次感谢!
猜你喜欢
  • 1970-01-01
  • 2012-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-03
  • 1970-01-01
  • 2018-12-18
  • 1970-01-01
相关资源
最近更新 更多