来自任务的表视图中的 JavaFX 更新进度条 [英] JavaFX Update progressbar in tableview from Task

查看:26
本文介绍了来自任务的表视图中的 JavaFX 更新进度条的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

解决方案

James D 在 Oracle JavaFX 论坛帖子中解决了这个问题: 和 ProgressBarTableCell.我们看一下相关代码:

TableColumnprogressCol = new TableColumn("Progress");progressCol.setCellValueFactory(new PropertyValueFactory("progress"));progressCol.setCellFactory(ProgressBarTableCell.forTableColumn());

progressCol 被定义为以 TestTask 作为数据行,并从测试任务属性中提取一个 double 值.

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

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

TableColumnfirstNameCol = new TableColumn("名字");firstNameCol.setCellValueFactory(new Callback, ObservableValue>() {公共 ObservableValuecall(CellDataFeatures p) {//p.getValue() 返回特定 TableView 行的 Person 实例返回 p.getValue().firstNameProperty();}});

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

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

<块引用>

也没有属性(如 SimpleStringProperty 等)所以问题是,如果我需要另外两列带有 SimpleStringProperty 怎么办,我该如何将它们添加到这种 TableView 中?

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

TableColumnurlCol = new TableColumn("URL");urlCol.setCellValueFactory(new PropertyValueFactory("url"));

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

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

final ReadOnlyStringWrapper url = new ReadOnlyStringWrapper();公共测试任务(字符串网址){this.url.set(url);}公共只读字符串属性 urlProperty() {返回 url.getReadOnlyProperty()}

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

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

I know Task has a method updateProgress, I would need to bind progressbar to task, however I cannot do that, as I do not have progressbar as an object.

My program has a TableView. Once user enters download url and clicks download new row created in the TableView. Row has some info and progressbar column. I then start a new thread - task. Where all download is being done and I need to update progress bar in that row somehow.

I tried binding SimpleDoubleProperty to the Task but it does not update progress bar...

解决方案

James D solved this in Oracle JavaFX forum thread: Table cell progress indicator. I have just copied that solution into this answer.

The solution creates multiple tasks and monitors their progress via a set of progress bars in a TableView.

The original thread also includes a solution which uses ProgressIndicators in case you prefer those to 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;
    }

  }
}

Explanatory Text Based on Comment Questions

You only need to read this section if you are having difficulties understanding how the above code works and want to gain a deeper understanding of cell value and property connections.

There is no kind of binding here (at least I do not see).

The binding (or ChangeListener, which amounts to the same thing) is hidden behind the implementation of the PropertyValueFactory and the ProgressBarTableCell. Let's look at the relevant code:

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

The progressCol is defined to take a TestTask as the data row and extract a double value out of the test task property.

The cell value factory defines how the double value for the column is populated. It is defined based upon a PropertyValueFactory which takes the parameter "progress". This tells the property value factory to use JavaFX naming conventions and the Java reflection API to lookup relevant methods to retrieve the data from a TestTask instance. In this case it will invoke a method named progressProperty() on the TestTask instance to retrieve the ReadOnlyDoubleProperty reflecting the tasks progress.

As it states in it's documentation, the PropertyValueFactory is just short hand for the mess of code below, but the key fact is that it is returning an ObservableValue which the Table implementation can use to set the value of the cell as the cell changes.

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();
  }
});

OK, so now we have a cell's value being reflected to the double value of the task's progress whenever the task makes any progress. But we still need to graphically represent that double value somehow. This is what the ProgressBarTableCell does. It is a table cell which contains a progress bar. The forTableColumn method creates a factory which produces the ProgressBarTableCells for each non-empty row in the column and sets the progress bar's progress to match the cell value which has been linked to the task's progress property by the PropertyValueFactory.

Confusing in understanding the detailed implementation . . . sure. But these high level helper factories and cells take care of a lot of the low level linkage details for you so that you don't need to code them over and over and from a plain API usage point of view it is (hopefully) simple and logical.

Also there is no properties (like SimpleStringProperty etc.) so the question would be, what if I need like two more columns with SimpleStringProperty, how do I add them to this kind of TableView?

Use the PropertyValueFactory once again. Let's image you have a string property called URL, then you can add the columns like this:

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

Note we only needed to set the cell value factory, this is because the default cell factory for the column will return a cell containing a label which directly displays the string value of the cell.

Now for the above to work correctly we need a method on TestTask which provides a url for the task, for example:

final ReadOnlyStringWrapper url = new ReadOnlyStringWrapper();

public TestTask(String url) {
  this.url.set(url);
}

public ReadOnlyStringProperty urlProperty() {
  return url.getReadOnlyProperty()
}

Note that the naming convention is really important here, it must be urlProperty() it can't be anything else or the PropertyValueFactory won't find the property value accessor.

Note for these purposes, a simple String value with a getUrl() would have worked just as well as a property as a PropertyValueFactory will work with a getter as well as a property method. The only advantage of using a property method is that it allows the table value data to update automatically based on property change events, which is not possible with a straight getter. But here because the url is effectively final and doesn't change for a given task, it doesn't make a difference whether a getter or property method is provided for this file from the task.

这篇关于来自任务的表视图中的 JavaFX 更新进度条的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆