【问题标题】:How to bind nested Task progress property to TableView in JavaFX?如何将嵌套的任务进度属性绑定到 JavaFX 中的 TableView?
【发布时间】:2019-09-23 09:25:43
【问题描述】:

环境: OpenJDK12、JavaFX 11

上下文:我正在尝试向 TableView 显示任务进度,为此,当我的代码不太复杂时,我的任务对象包含 bean 属性,而 TableView 数据模型是我的任务对象。

public class MyTask extends Task<Void>{
  private String name;
  //other properties
  public Void call() {
   //"progress" property is inherited from Task.
   //do something and updateProgress()
  }
}

public class MyController {
  ...
  @FXML
  private TableView<MyTask> dataTable;
  @FXML
  private TableColumn<MyTask,Double> progressCol;
  ...
  progressCol.setCellValueFactory(new PropertyValueFactory<MyTask, Double>("progress"));
  progressCol.setCellFactory(ProgressCell.<Double>forTableColumn());
  ...
}

效果很好。但我想将 Task 与 bean 属性分开,所以我决定制作一种包装器,但我无法再检索 progress 属性了。

编辑

示例代码:

我的应用

public class MyApp extends Application {

    @Override
    public void start(Stage stage) throws IOException {
        stage.setMinWidth(800);
        stage.setMinHeight(500);
        FXMLLoader sceneLoader = new FXMLLoader(MyApp.class.getResource("MyScene.fxml"));
        Parent parent = sceneLoader.load();
        Scene scene = new Scene(parent);
        stage.setScene(scene);
        stage.show();
    }
    public static void main(String[] args) {
        launch();
    }
}

我的控制器

public class MyController implements Initializable{

@FXML
private TableView<MyWrapper> dataTable;
@FXML
private TableColumn<MyWrapper, String> nameColumn;
@FXML
private TableColumn<MyWrapper, Double> progressColumn;

public MyController() {
}

@Override
public void initialize(URL location, ResourceBundle resources) {        
    nameColumn.setCellValueFactory((TableColumn.CellDataFeatures<MyWrapper, String> download) -> download.getValue()
            .getMyBean().nameProperty());

    //This line only works when MyWrapper has progressPropery() method
    //progressColumn.setCellValueFactory(new PropertyValueFactory<>("progress"));

    progressColumn.setCellFactory(ProgressCell.<Double>forTableColumn());

    MyWrapper w1 = new MyWrapper("qqqqqqq");
    MyWrapper w2 = new MyWrapper("wwwwww");
    MyWrapper w3 = new MyWrapper("eeeeeee");
    ObservableList<MyWrapper> obsList = FXCollections.observableArrayList();
    obsList.addAll(w1,w2,w3);
    dataTable.setItems(obsList);

    Thread t1 = new Thread(w1.getMyTask());
    t1.start();

}

我的包装器

public class MyWrapper {

    private SimpleObjectProperty<MyBean> myBean;
    private SimpleObjectProperty<MyTask> myTask;
    public MyWrapper(String name) {
        myBean = new SimpleObjectProperty<MyBean>();
        myBean.setValue(new MyBean());
        myBean.getValue().setName(name);
        myTask = new SimpleObjectProperty<MyTask>();
        myTask.setValue(new MyTask());

    }
    public MyBean getMyBean() {
        return myBean.getValue();
    }
    public MyTask getMyTask() {
        return myTask.getValue();
    }

}

我的豆

public class MyBean {

    private SimpleStringProperty name;
    public MyBean() {
        name = new SimpleStringProperty("--");
    }
    public SimpleStringProperty nameProperty() {
        return name;
    }
    public void setName(String name) {
        this.name.setValue(name);
    }
}

我的任务

public class MyTask extends Task<Void>{

    @Override
    protected Void call() throws Exception {
        // Set the total number of steps in our process
        double steps = 1000;

        // Simulate a long running task
        for (int i = 0; i < steps; i++) {

            Thread.sleep(10); // Pause briefly

            // Update our progress and message properties
            updateProgress(i, steps);
            updateMessage(String.valueOf(i));
        }       return null;
    }

}

进度单元

public class ProgressCell extends TableCell<MyWrapper, Double> {

    private ProgressBar bar;
    private ObservableValue<Double> observable;
    private StringProperty colorProperty = new SimpleStringProperty();

    public ProgressCell() {

        bar = new ProgressBar();
        bar.setMaxWidth(Double.MAX_VALUE);
        bar.setProgress(0f);
        bar.styleProperty().bind(colorProperty);

    }

    public static <S> Callback<TableColumn<MyWrapper, Double>, TableCell<MyWrapper, Double>> forTableColumn() {
        return param -> new ProgressCell();
    }

    @Override
    protected void updateItem(Double item, boolean empty) {
        super.updateItem(item, empty);

        if (empty || item == null) {
            setGraphic(null);
        } else {

            final TableColumn<MyWrapper, Double> column = getTableColumn();
            observable = column == null ? null : column.getCellObservableValue(getIndex());

            if (observable != null) {
                bar.progressProperty().bind(observable);
            } else if (item != null) {
                bar.setProgress(item);
            }

            setGraphic(bar);
        }
    }

}

MyScene.fxml

<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.effect.Blend?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.StackPane?>

<AnchorPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="main.java.MyController">

                  <StackPane BorderPane.alignment="CENTER">
                     <children>
                            <TableView id="dataTable" fx:id="dataTable" prefHeight="193.0" prefWidth="678.0" snapToPixel="false">
                                <columns>
                                    <TableColumn fx:id="nameColumn" editable="false" prefWidth="88.0" text="Name" />
                                    <TableColumn fx:id="progressColumn" editable="false" prefWidth="75.0" text="Progress" />
                                </columns>
                                <effect>
                                    <Blend />
                                </effect>
                                <columnResizePolicy>
                                    <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
                                </columnResizePolicy>
                            </TableView>
                     </children>
                  </StackPane>
</AnchorPane>

如果不在 MyWrapper 中添加 progressProperty() 方法,我不知道如何让进度条工作。我期待像name 属性一样访问progress 属性。有什么办法吗?你觉得怎么样会更好?

任何帮助表示赞赏。

【问题讨论】:

  • no valueFactory, no cellObservableValue ;) 只需以与 bean 的 nameProperty 相同的方式公开它(还有更多内容,因为如果任务被替换,如果发生这种情况,您必须注意通知)。然后使用 ProgressTableCell,一切都会自动发生(我想,今天晚些时候会测试,还是在喝咖啡之前 :)

标签: javafx tableview task progress-bar


【解决方案1】:

不支持嵌套属性(正如您所注意到的,我在一条神秘消失的评论中确认了......) - 在自定义 cellValueFactory 中提供属性,沿着树向下走是要走的路:只需对任务的进度就像你为 bean 做的一样。

一个工作代码sn-p:

// column setup
nameColumn.setCellValueFactory(cc -> cc.getValue().getMyBean().nameProperty());
progressColumn.setCellValueFactory(cc -> cc.getValue().getMyTask().progressProperty().asObject());
progressColumn.setCellFactory(ProgressBarTableCell.forTableColumn());
new Thread(w1.getMyTask()).start();

注意 DoublePropertyObjectProperty&lt;Double&gt; 的转换(正如 Slaw 在消失的评论中指出的那样;)

这样的深入研究是否是一个好主意取决于您的上下文:只要数据是只读的并且在其生命周期内不会发生变化,就可以了。否则,您将需要采取预防措施来防止此类更改。无论如何,这将需要包装器中的附加逻辑,因此在该层中公开感兴趣的属性可能是更简洁的方法。

【讨论】:

    【解决方案2】:

    第一个错误被抛出,因为您的 MyObject 类没有 progressProperty 函数。

    如果您将此函数添加到您的包装类中,它将起作用。

    public ReadOnlyDoubleProperty progressProperty() {
      return task.progressProperty();
    }  
    

    progressCol.setCellValueFactory(new PropertyValueFactory<>("progress"));
    

    【讨论】:

    • 我不喜欢这样做,但它确实显示了 ProgressBar。进度值从 -1 跳到 1。我正在调试,看看发生了什么。
    • 你可以将bean包装在你的MyTask类中,那么你就不需要额外的属性方法了。我自己测试了这段代码,进度更新得很好。
    • 否:如果您公开一个属性 - 在 valueFactory 中使用它(与使用容易出错的 PropertyValueFactory 相比)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-17
    • 1970-01-01
    • 2019-12-21
    • 1970-01-01
    • 2010-11-01
    • 1970-01-01
    • 2015-03-06
    相关资源
    最近更新 更多