javajavafxjavafx-tableview

Javafx tableview bind changes inside cellValueFactory to table observablelist


I have javafx table which includes member wise monthly charges of an organization. In this table I show some charges with their default amount so that in any case user can update the amount or else proceed as it was.What I actually need is if user update any values, those should bind to the table observablelist immediately.

Create table :

private void initCollectionTable(ObservableList<Member> mList) {

    ...

    total_pay_col.setCellValueFactory(new SubscriptionValueFactory());
    detail_view_col.setCellValueFactory(new DisplaySubscriptionFactory());

   ...

   collection_tbl.setItems(mlz);
}

SubscriptionValueFactory Class :

public class SubscriptionValueFactory implements Callback<TableColumn.CellDataFeatures<Member, String>, ObservableValue<String>> {

    @Override
    public ObservableValue<String> call(TableColumn.CellDataFeatures<Member, String> param) {

        //Here Subscriptions are equal to various member charges
        Member ml = param.getValue();
        List<MemberSubscriptions> mbrSubs = new ArrayList<>(ml.getMemberSubscriptions());
        double sum =mbrSubs.stream().mapToDouble(a -> a.getAmount()).sum();
        return new SimpleObjectProperty<>(TextFormatHandler.CURRENCY_DECIMAL_FORMAT.format(sum));
    }

}

DisplaySubscriptionFactory Class

public class DisplaySubscriptionFactory implements Callback<TableColumn.CellDataFeatures<Member, Button>, ObservableValue<Button>> {

    @Override
    public ObservableValue<Button> call(TableColumn.CellDataFeatures<Member, Button> param) {
        Member ml = param.getValue();

        List<MemberSubscriptions> mbrSubs = new ArrayList<>(ml.getMemberSubscriptions());

        double sum =mbrSubs.stream().mapToDouble(a -> a.getAmount()).sum();
        boolean flag = // SOME BOOLEAN CHECK HERE-----

        param.getValue().setTotalSubscription(sum);
        Button button = new Button("View Info");
        button.setOnAction((evt) -> {
            Alert alert_details = new Alert(Alert.AlertType.INFORMATION);
            alert_details.setTitle("Subscription Information");
            alert_details.setHeaderText("Member Subscription information for each installment");
            alert_details.getDialogPane().setContent(createContentGrid(mbrSubs, sum, flag));
            alert_details.show();
        });

        return new SimpleObjectProperty<>(button);
    }

This method creates a grid to show member charges within textfields. So any value change inside the textfield, should update its own property of the table observablelist.

private Node createContentGrid(List<MemberSubscriptions> mbrSubs, double sum, boolean flag) {

    GridPane grid = new GridPane();
    grid.setHgap(20);
    grid.setVgap(10);
    grid.setPadding(new Insets(20, 20, 10, 10));

    Label totLabel = new Label(TextFormatHandler.CURRENCY_DECIMAL_FORMAT.format(sum));
    totLabel.setFont(Font.font("System Bold", 21.0));
    Label col_h_1 = new Label("Subscription");
    col_h_1.setFont(Font.font("System Bold", 17.0));
    Label col_h_2 = new Label("Amount");
    col_h_2.setFont(Font.font("System Bold", 17.0));

    grid.add(col_h_1, 0, 0);
    grid.add(col_h_2, 1, 0);
    Label[] labels = new Label[mbrSubs.size()];
    TextField[] fields = new TextField[mbrSubs.size()];
    for (int i = 0; i < mbrSubs.size(); i++) {
        MemberSubscriptions get = mbrSubs.get(i);
        labels[i] = new Label("label");
        fields[i] = new TextField();
        fields[i].setTextFormatter(TextFormatHandler.currencyFormatter());
        labels[i].setText(get.getMemberSubscription().getFeeName());
        fields[i].setText(TextFormatHandler.CURRENCY_DECIMAL_FORMAT.format(get.getAmount()));
        grid.add(labels[i], 0, i + 1);
        grid.add(fields[i], 1, i + 1);
    }
    grid.add(totLabel, 1, mbrSubs.size() + 1);
    return grid;
}

Member Class:

public class Member {
    private Integer id;
    private String memberId;
    ...
    private Set<MemberSubscriptions> memberSubscriptions = new HashSet<>();
    ...

MemberSubscriptions Class

public class MemberSubscriptions {
    private Integer id;
    private Member member;
    private Double amount;
    ...

enter image description here


Solution

  • I think I have found the answer to your question. I've simplified it, so maybe you can use it to solve your problem. Here is my code for my classes:

    Model:

    public class Model {
    
        private LongProperty id;
        private DoubleProperty amount;
        private ObjectProperty<SubModel> subModel;
    
        public Model(Long id, Double amount, SubModel subModel) {
            this.id = new SimpleLongProperty(id);
            this.amount = new SimpleDoubleProperty(amount);
            this.subModel = new SimpleObjectProperty<>(subModel);
        }
    
        public long getId() {
            return id.get();
        }
    
        public LongProperty idProperty() {
            return id;
        }
    
        public DoubleProperty amountProperty() {
            return amount;
        }
    
        public SubModel getSubModel() {
            return subModel.get();
        }
    
        public ObjectProperty<SubModel> subModelProperty() {
            return subModel;
        }
    }
    

    SubModel:

    public class SubModel {
    
        private LongProperty id;
        private DoubleProperty xAmount;
        private DoubleProperty yAmount;
    
        public SubModel(Long id, Double xAmount, Double yAmount) {
            this.id = new SimpleLongProperty(id);
            this.xAmount = new SimpleDoubleProperty(xAmount);
            this.yAmount = new SimpleDoubleProperty(yAmount);
        }
    
        public long getId() {
            return id.get();
        }
    
    
        public double getxAmount() {
            return xAmount.get();
        }
    
        public DoubleProperty xAmountProperty() {
            return xAmount;
        }
    
        public double getyAmount() {
            return yAmount.get();
        }
    
        public DoubleProperty yAmountProperty() {
            return yAmount;
        }
    }
    

    Button cell:

    public class ButtonTableCell<S, T> extends TableCell<S, T> {
    
        private Button button;
    
        public ButtonTableCell() {
            this.button = new Button("View Info");
            button.setOnAction(event -> {
                Model model = (Model) getTableRow().getItem();
                Dialog<SubModel> dialog = new AmountDialog(model.getSubModel());
                dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
                dialog.showAndWait().ifPresent(result -> {
                    model.amountProperty().set(result.getxAmount() + result.getyAmount());
                });
            });
        }
    
        @Override protected void updateItem(T item, boolean empty) {
            super.updateItem(item, empty);
            setText(null);
            if (empty) {
                setGraphic(null);
            } else {
                setGraphic(button);
            }
        }
    }
    

    Dialog:

    public class AmountDialog extends Dialog<SubModel> {
    
        public AmountDialog(SubModel model) {
            setTitle("Title");
            setHeaderText("Header");
            getDialogPane().setContent(createContent(model));
            setResultConverter(button -> {
                if (button == ButtonType.OK) {
                    return model;
                }
                return null;
            });
        }
    
        private GridPane createContent(SubModel model) {
            GridPane grid = new GridPane();
            grid.setHgap(20);
            grid.setVgap(10);
            grid.setPadding(new Insets(20, 20, 10, 10));
            Label sub = new Label("Sub");
            Label amount = new Label("Amount");
    
            Label amountX = new Label("AmountX");
            Label amountY = new Label("AmountY");
    
            TextField tfAmounX = new TextField(String.valueOf(model.getxAmount()));
            TextField tfAmountY = new TextField(String.valueOf(model.getyAmount()));
    
            Bindings.bindBidirectional(tfAmounX.textProperty(), model.xAmountProperty(), new StringDoubleConverter());
            Bindings.bindBidirectional(tfAmountY.textProperty(), model.yAmountProperty(), new StringDoubleConverter());
    
            grid.add(sub, 0, 0);
            grid.add(amount, 1, 0);
    
            grid.add(amountX, 0, 1);
            grid.add(tfAmounX, 1, 1);
    
            grid.add(amountY, 0, 2);
            grid.add(tfAmountY, 1, 2);
    
            return grid;
        }
    
        private class StringDoubleConverter extends StringConverter<Number> {
            @Override public String toString(Number object) {
                return object.toString();
            }
    
            @Override public Number fromString(String string) {
                // You may handle NumberFormatException.
                return Double.valueOf(string);
            }
        }
    }
    

    And the Controller:

    public class Controller implements Initializable {
    
        @FXML
        private TableView<Model> table;
        @FXML
        private TableColumn<Model, Long> colId;
        @FXML
        private TableColumn<Model, Double> colAmount;
        @FXML
        private TableColumn<Model, SubModel> colInfo;
    
        @Override
        public void initialize(URL location, ResourceBundle resources) {
            setupTable();
            setupData();
        }
    
        private void setupTable() {
            colId.setCellValueFactory(data -> data.getValue().idProperty().asObject());
            colAmount.setCellValueFactory(data -> data.getValue().amountProperty().asObject());
            colInfo.setCellValueFactory(data -> data.getValue().subModelProperty());
    
            colInfo.setCellFactory(factory -> new ButtonTableCell<>());
        }
    
        private void setupData() {
            SubModel firstSubModel = new SubModel(1L, 0D, 0D);
            SubModel secondSubModel = new SubModel(2L, 0D, 0D);
    
            Model fist = new Model(1L, 0D, firstSubModel);
            Model second = new Model(2L, 0D, secondSubModel);
    
            ObservableList<Model> tableData = FXCollections.observableArrayList();
            tableData.add(fist);
            tableData.add(second);
    
            table.setItems(tableData);
        }
    }
    

    I know a little bit long answer but I hope it solves your problem. For me it is working on the OK press, but you can go even further if you want to update instantly when you change the value in any field of the dialog.