Reputation: 1149
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
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
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