JavaFX更新来自Task的tableview中的进度条

时间:2013-05-23 18:32:28

标签: multithreading progress-bar javafx tableview

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

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

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

1 个答案:

答案 0 :(得分:25)

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

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

原始主题还包含一个使用ProgressIndicators的解决方案,以防您更喜欢ProgressBars

progress

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作为数据行并从测试任务属性中提取double值。

单元格值工厂定义如何填充列的double值。它是基于PropertyValueFactory定义的,它接受参数“progress”。这告诉属性值工厂使用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();
  }
});

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

难以理解详细的实施。 。 。当然。但是这些高级辅助工厂和单元为您处理了许多低级链接细节,因此您不需要反复编码它们,从简单的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()的简单String值和Property一样有效,因为PropertyValueFactory可以使用getter和property方法。使用属性方法的唯一优点是它允许表值数据基于属性更改事件自动更新,这对于直接获取器是不可能的。但是这里因为url实际上是最终的并且对于给定的任务没有改变,所以从该任务是否为该文件提供了getter或property方法也没有区别。