JavaFX更新来自Task的tableview中的进度条 [英] JavaFX Update progressbar in tableview from Task

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

问题描述

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



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



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

解决方案

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

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

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



单元格值工厂定义如何填充列的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()返回特定TableView行的Person实例
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 for任务,例如:

  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以及属性方法。使用属性方法的唯一优点是它允许表值数据基于属性更改事件自动更新,这对于直接获取器是不可能的。但是这里因为url实际上是最终的并且对于给定的任务没有改变,所以从该任务是否为该文件提供了getter或property方法也没有区别。


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更新来自Task的tableview中的进度条的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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