tec
tec

Reputation: 1149

How to bind nested Task progress property to TableView in JavaFX?

Enironment: OpenJDK12, JavaFX 11

Context: I'm trying to show the Task progress to a TableView, for that, when my code was less complex, my Task object included the bean properties, and the TableView datamodel was my Task object.

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

That worked fine. But I wanted to separate the Task from the bean properties, so I decided to make a kind of wrapper, but I'm unable to retrieve the progress property anymore.

EDIT

Sample Code:

MyApp

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

MyController

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

}

MyWrapper

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

}

MyBean

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

MyTask

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

}

ProgressCell

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>

I don't know how to get the progress bar working, without adding the progressProperty() method in MyWrapper. I was expecting to access the progress property like the name property. Is there some way ? How do you think it would be better?

Any help appreciated.

Upvotes: 1

Views: 254

Answers (2)

kleopatra
kleopatra

Reputation: 51525

There is no support for nested properties (as you noticed and I confirmed in a comment that mysteriously disappeared .. ) - providing the property in a custom cellValueFactory that walks down the tree is the way to go: just do the same for the progress of the task as you do for the name of the bean.

A working code snippet:

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

Note the conversion of DoubleProperty to ObjectProperty<Double> (as Slaw noted in a comment that disappeared as well ;)

Whether or not such deep diving is a good idea depends on your context: it's okay as long as the data is read-only and doesn't change over its lifetime. Otherwise, you would need to take precautions to guard against such change. Which will require additonal logic in the wrapper anyway, so exposing the properties of interest in that layer probably would be the cleaner approach.

Upvotes: 1

M&#225;tray M&#225;rk
M&#225;tray M&#225;rk

Reputation: 466

The first error is thrown because your MyObject class doesn't have a progressProperty function.

If you add this function to your wrapper class it will work.

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

.

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

Upvotes: 0

Related Questions