10

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

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

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

4

1 回答 1

28

James D 在 Oracle JavaFX 论坛主题中解决了这个问题:表格单元进度指示器。我刚刚将该解决方案复制到此答案中。

该解决方案通过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() 的简单 String 值将与属性一样有效,因为 PropertyValueFactory 将与 getter 和属性方法一起使用。使用属性方法的唯一优点是它允许表值数据根据属性更改事件自动更新,这对于直接的 getter 是不可能的。但是在这里,因为 url 实际上是最终的,并且对于给定的任务不会改变,所以无论是从任务中为这个文件提供 getter 方法还是属性方法都没有区别。

于 2013-05-23T23:08:31.100 回答