Issue
There is a TableView of orders that has a column for representing order cost. The cost property of the Order object can be changed within the TableView. It means that the Order column cells have the ability to be changed by using TextFieldTableCell. Also, there is a Label outside of TableView which should represent the sum of orders costs. Now, the problem is that I don't know how to bind the sum of the order costs column to the text property() of Label.
Here is some piece of code to clarify the problem:
class Order {
private SimpleStringProperty name;
private SimpleIntegerProperty cost;
public String getName() {
return this.name.get();
}
public void setName(String name) {
this.name.set(name);
}
public SimpleStringProperty nameProperty() {
return this.name;
}
public Integer getCost() {
return this.cost.get();
}
public void setCost(Integer cost) {
this.cost.set(cost);
}
public SimpleIntegerProperty costProperty() {
return this.cost;
}
}
TableView<Order> tableView = new TableView<>();
TableColumn<Order, String> nameColumn = new TableColumn<>();
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
TableColumn<Order, String> costColumn = new TableColumn<>();
costColumn.setCellValueFactory(new PropertyValueFactory<>("cost"));
costColumn.setCellFactory(TextFieldTableCell.forTableColumn());
tableView.getColumns().addAll(nameColumn, costColumn);
Label totalCostLabel = new Label("Total cost should be updated in this label");
VBox vBox = new VBox();
vBox.getChildren().addAll(tableView, totalCostLabel);
Solution
You can do the following:
Create an
ObservableList
with anextractor
mapping to thecostProperty()
, and use it as the table'sitems
list:tableView.setItems(FXCollections.observableArrayList( order -> new Observable[] { order.costProperty() }));
This ensures the list fires update events if any of the
costProperty
of elements of the list change (in addition to the usual events being fired if elements are added or removed from the list, etc.)Create a
DoubleBinding
which binds to the list, and calculates the total cost:DoubleBinding totalCost = Bindings.createDoubleBinding(() -> { double total = 0 ; for (Order order : tableView.getItems()) { total = total + order.getCost(); } return total ; }, tableView.getItems());
Bind the label's
textProperty()
to the total cost:totalCostLabel.textProperty().bind(totalCost.asString());
You can supply a format
to the asString()
method, if you want more control over the way it is displayed.
Here is a complete example, adapted from your code:
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;
public class SummingTable extends Application {
@Override
public void start(Stage stage) {
TableView<Order> tableView = new TableView<>();
tableView.setItems(FXCollections.observableArrayList(
order -> new Observable[] { order.costProperty() }));
tableView.getItems().addAll(
new Order("Order 1", 10),
new Order("Order 2", 20));
tableView.setEditable(true);
TableColumn<Order, String> nameColumn = new TableColumn<>();
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
TableColumn<Order, Integer> costColumn = new TableColumn<>();
costColumn.setCellValueFactory(cellData -> cellData.getValue().costProperty().asObject());
costColumn.setCellFactory(TextFieldTableCell.forTableColumn(new IntegerStringConverter()));
tableView.getColumns().addAll(nameColumn, costColumn);
Label totalCostLabel = new Label("Total cost should be updated in this label");
DoubleBinding totalCost = Bindings.createDoubleBinding(() -> {
double total = 0 ;
for (Order order : tableView.getItems()) {
total = total + order.getCost();
}
return total ;
}, tableView.getItems());
totalCostLabel.textProperty().bind(totalCost.asString());
VBox vBox = new VBox();
vBox.getChildren().addAll(tableView, totalCostLabel);
Scene scene = new Scene(vBox);
stage.setScene(scene);
stage.show();
}
public class Order {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty cost = new SimpleIntegerProperty();
public Order(String name, int cost) {
setName(name);
setCost(cost);
}
public String getName() {
return this.name.get();
}
public void setName(String name) {
this.name.set(name);
}
public StringProperty nameProperty() {
return this.name;
}
public Integer getCost() {
return this.cost.get();
}
public void setCost(Integer cost) {
this.cost.set(cost);
}
public IntegerProperty costProperty() {
return this.cost;
}
}
public static void main(String[] args) {
launch();
}
}
Answered By - James_D