【问题标题】:JavaFX Update progressbar in tableview from Task来自Task的tableview中的JavaFX更新进度条
【发布时间】:2013-05-19 06:04:06
【问题描述】:

我知道 Task 有一个方法 updateProgress,我需要将进度条绑定到任务,但是我不能这样做,因为我没有进度条作为对象。

我的程序有一个 TableView。一旦用户输入下载 url 并单击下载在 TableView 中创建的新行。行有一些信息和进度条列。然后我开始一个新线程 - 任务。所有下载都在哪里完成,我需要以某种方式更新该行中的进度条。

我尝试将 SimpleDoubleProperty 绑定到任务,但它没有更新进度条...

【问题讨论】:

    标签: multithreading progress-bar javafx tableview


    【解决方案1】:

    James D 在 Oracle JavaFX 论坛帖子中解决了这个问题:Table cell progress indicator。我刚刚将该解决方案复制到此答案中。

    该解决方案创建多个任务并通过TableView 中的一组进度条监控它们的进度。

    原始线程还包括一个使用ProgressIndicators 的解决方案,以防您更喜欢ProgressBars

    import java.util.Random;
    import java.util.concurrent.Executor;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadFactory;
    
    import javafx.application.Application;
    import javafx.concurrent.Task;
    import javafx.scene.Scene;
    import javafx.scene.control.ProgressIndicator ;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TableView;
    import javafx.scene.control.cell.ProgressBarTableCell;
    import javafx.scene.control.cell.PropertyValueFactory;
    import javafx.scene.layout.BorderPane;
    import javafx.stage.Stage;
    
    public class ProgressBarTableCellTest extends Application {
    
      @Override
      public void start(Stage primaryStage) {
        TableView<TestTask> table = new TableView<TestTask>();
        Random rng = new Random();
        for (int i = 0; i < 20; i++) {
          table.getItems().add(
              new TestTask(rng.nextInt(3000) + 2000, rng.nextInt(30) + 20));
        }
    
        TableColumn<TestTask, String> statusCol = new TableColumn("Status");
        statusCol.setCellValueFactory(new PropertyValueFactory<TestTask, String>(
            "message"));
        statusCol.setPrefWidth(75);
    
        TableColumn<TestTask, Double> progressCol = new TableColumn("Progress");
        progressCol.setCellValueFactory(new PropertyValueFactory<TestTask, Double>(
            "progress"));
        progressCol
            .setCellFactory(ProgressBarTableCell.<TestTask> forTableColumn());
    
        table.getColumns().addAll(statusCol, progressCol);
    
        BorderPane root = new BorderPane();
        root.setCenter(table);
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    
        ExecutorService executor = Executors.newFixedThreadPool(table.getItems().size(), new ThreadFactory() {
          @Override
          public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
          }
        });
    
    
        for (TestTask task : table.getItems()) {
          executor.execute(task);
        }
      }
    
      public static void main(String[] args) {
        launch(args);
      }
    
      static class TestTask extends Task<Void> {
    
        private final int waitTime; // milliseconds
        private final int pauseTime; // milliseconds
    
        public static final int NUM_ITERATIONS = 100;
    
        TestTask(int waitTime, int pauseTime) {
          this.waitTime = waitTime;
          this.pauseTime = pauseTime;
        }
    
        @Override
        protected Void call() throws Exception {
          this.updateProgress(ProgressIndicator.INDETERMINATE_PROGRESS, 1);
          this.updateMessage("Waiting...");
          Thread.sleep(waitTime);
          this.updateMessage("Running...");
          for (int i = 0; i < NUM_ITERATIONS; i++) {
            updateProgress((1.0 * i) / NUM_ITERATIONS, 1);
            Thread.sleep(pauseTime);
          }
          this.updateMessage("Done");
          this.updateProgress(1, 1);
          return null;
        }
    
      }
    }
    

    基于评论问题的说明性文字

    仅当您难以理解上述代码的工作原理并希望更深入地了解单元格值和属性连接时,才需要阅读本节。

    这里没有任何类型的绑定(至少我没有看到)。

    绑定(或 ChangeListener,相当于同一件事)隐藏在 PropertyValueFactoryProgressBarTableCell 的实现后面。我们看一下相关代码:

    TableColumn<TestTask, Double> progressCol = new TableColumn("Progress");
    progressCol.setCellValueFactory(
      new PropertyValueFactory<TestTask, Double>("progress")
    );
    progressCol.setCellFactory(
      ProgressBarTableCell.<TestTask> forTableColumn()
    );
    

    progressCol 被定义为将 TestTask 作为数据行并从测试任务属性中提取一个双精度值。

    单元格值工厂定义如何填充列的双精度值。它是基于采用参数“progress”的 PropertyValueFactory 定义的。这告诉属性值工厂使用 JavaFX 命名约定和 Java 反射 API 来查找相关方法以从 TestTask 实例中检索数据。在这种情况下,它将调用 TestTask 实例上名为 progressProperty() 的方法来检索反映任务进度的 ReadOnlyDoubleProperty。

    正如它在文档中所述,PropertyValueFactory 只是下面混乱代码的简写,但关键事实是它返回一个 ObservableValue,Table 实现可以使用该值将单元格的值设置为单元格变化。

    TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");
    firstNameCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() {
      public ObservableValue<String> call(CellDataFeatures<Person, String> p) {
        // p.getValue() returns the Person instance for a particular TableView row
        return p.getValue().firstNameProperty();
      }
    });
    

    好的,所以现在我们有了一个单元格的值,每当任务取得任何进展时,都会将单元格的值反映为任务进度的双精度值。但是我们仍然需要以某种方式以图形方式表示该 double 值。这就是ProgressBarTableCell 所做的。它是一个包含进度条的表格单元格。 forTableColumn 方法创建了一个工厂,该工厂为列中的每个非空行生成 ProgressBarTableCells,并设置进度条的进度以匹配已通过 PropertyValueFactory 链接到任务进度属性的单元格值。

    在理解详细实现时感到困惑。 . .当然。但是这些高级助手工厂和单元为您处理了许多低级链接细节,因此您不需要一遍又一遍地编写它们,并且从简单的 API 使用角度来看,它(希望)很简单,并且合乎逻辑。

    也没有属性(如 SimpleStringProperty 等),所以问题是,如果我需要更多具有 SimpleStringProperty 的列,我该如何将它们添加到这种 TableView 中?

    再次使用 PropertyValueFactory。让我们想象一下,您有一个名为 URL 的字符串属性,然后您可以像这样添加列:

    TableColumn<TestTask, Double> urlCol = new TableColumn("URL");
    urlCol.setCellValueFactory(
      new PropertyValueFactory<TestTask, Double>("url")
    );
    

    注意我们只需要设置单元格值工厂,这是因为该列的默认单元格工厂将返回一个包含标签的单元格,该标签直接显示单元格的字符串值。

    现在,为了使上述内容正常工作,我们需要一个 TestTask 方法,它为任务提供一个 url,例如:

    final ReadOnlyStringWrapper url = new ReadOnlyStringWrapper();
    
    public TestTask(String url) {
      this.url.set(url);
    }
    
    public ReadOnlyStringProperty urlProperty() {
      return url.getReadOnlyProperty()
    }
    

    请注意,命名约定在这里非常重要,它必须是 urlProperty() 不能是其他任何东西,否则 PropertyValueFactory 将找不到属性值访问器。

    注意,出于这些目的,带有 getUrl() 的简单字符串值与属性一样有效,因为 PropertyValueFactory 可以与 getter 和属性方法一起使用。使用属性方法的唯一优点是它允许表值数据根据属性更改事件自动更新,这对于直接的 getter 是不可能的。但是在这里,因为 url 实际上是最终的,并且对于给定的任务不会改变,所以无论是从任务中为这个文件提供 getter 方法还是属性方法都没有区别。

    【讨论】:

    • 测试了这个演示应用程序——它做得很好。但是,对我来说,很难理解这段代码。这里没有任何约束(至少我没有看到)。也没有属性(如 SimpleStringProperty 等),所以问题是,如果我需要更多带有 SimpleStringProperty 的列,我该如何将它们添加到这种 TableView 中?提前致谢。
    • 更新答案以根据萨拉斯评论中的问题提供额外信息
    • 非常感谢您的宝贵时间和如此深刻的回答。这对我帮助很大。
    • urlCol.setCellValueFactory( new PropertyValueFactory&lt;TestTask, Double&gt;("url"),不应该是String而不是Double吗?
    • @jeppla 是的 jeppla,值得指出的是 JavaFX 并发类 TaskService 都继承自 Worker 接口。 Worker 接口公开了一个progress property。所以任何从 Task 或 Service 继承的东西也会暴露 progress 属性。它是监视这些类的进度属性中的更改值,使您可以适当地更新与它们关联的进度条。这是理解示例的关键。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-21
    • 2014-05-19
    • 1970-01-01
    • 2018-02-20
    • 1970-01-01
    • 1970-01-01
    • 2012-12-02
    相关资源
    最近更新 更多