unknown
unknown

Reputation: 397

JavaFX Tableview - column value dependent on other columns

I have a TableView in JavaFX. It has a field subTotal which depends on the value of the fields quantity and price. I added a new column for the subTotal.

I have textfields present to add a new row to the table. But, the add button wants to have another textfield for the subTotal, although it does not really necessary for the subtotal column.

What I have tried so far :

TableColumn columnCodeProduct = new TableColumn("Product Code");
columnCodeProduct.setMinWidth(100);
columnCodeProduct.setCellValueFactory(new PropertyValueFactory<Data , Integer>("productname "));

TableColumn columnProductName = new TableColumn("Product Name");
columnProductName.setMinWidth(140);
columnProductName.setCellValueFactory(new PropertyValueFactory<Data , String>("codeproduct"));

TableColumn columnPrice = new TableColumn("Price");
columnPrice.setMinWidth(100);
columnPrice.setCellValueFactory(new PropertyValueFactory<Data , Integer>("price"));

TableColumn columQuantity = new TableColumn("Quantity");
columQuantity.setMinWidth(100);
columQuantity.setCellValueFactory(new PropertyValueFactory<Data , Integer>("quantity"));


TableColumn columnTotal = new TableColumn("Sub Total");
columnTotal.setMinWidth(100);
columQuantity.setCellValueFactory(new PropertyValueFactory<Data , Integer>("sub"));

tableData.getColumns().addAll(columnCodeProduct , columnProductName , columnPrice ,  columQuantity );

tableData.setItems(data);


addButton = new Button("Add Item");


addButton.setOnAction(new EventHandler<ActionEvent>() {

    @Override
    public void handle(ActionEvent event)
    {
        if(addproCodeTextfield.getText().isEmpty() || addproNameTextfield.getText().isEmpty()
            || addPriceTextfield.getText().isEmpty() || quantityTextField.getText().isEmpty())
        {
            System.out.println("Please Add information to all the fields");
        } else {
            data.add(new Data (

                           addproCodeTextfield.getText(),
                           addproNameTextfield.getText(),
                 addPriceTextfield.getText(),                             
                           quantityTextField.getText()));                             
             methodTotal();
        }
    }
});

Data Class

public class Data
{
    private final SimpleStringProperty codeproduct;
    private final SimpleStringProperty productname;
    private final SimpleStringProperty price ;
    private final SimpleStringProperty quantity;



    public Data (String code , String proname ,  String presyo ,  String quant )
    {
        this.codeproduct = new SimpleStringProperty(code);
        this.productname = new SimpleStringProperty(proname);
        this.price = new SimpleStringProperty(presyo);
        this.quantity = new SimpleStringProperty(quant);

    }

    public String getcodeProduct()
    {
        return codeproduct.get();
    }

    public String getproductName()
    {
        return productname.get();
    }

    public String getPrice()
    {
        return price.get();
    }

    public String getQuantity()
    {
        return quantity.get();
    }

}

Upvotes: 11

Views: 7342

Answers (2)

James_D
James_D

Reputation: 209684

I would restructure your model class as @ItachiUchiha suggests. If you feel you need to keep the data stored with String representations, you can just create a binding for the subtotal column:

TableColumn<Data, Number> subtotalColumn = new TableColumn<>("Sub Total");
subTotalColumn.setCellValueFactory(cellData -> {
    Data data = cellData.getValue();
    return Bindings.createDoubleBinding(
            () -> {
                try {
                    double price = Double.parseDouble(data.getPrice());
                    int quantity = Integer.parseInt(data.getQuantity());
                    return price * quantity ;
                } catch (NumberFormatException nfe) {
                    return 0 ;
                }
            },
            data.priceProperty(),
            data.quantityProperty()
    );
});

Upvotes: 13

ItachiUchiha
ItachiUchiha

Reputation: 36792

You can take benefit from JavaFX's power to bind value.

Few points to take care of while implementing a scenario as stated above:

  • The POJO class(in your case Data) fields must have correct types. For example price and quantity must be of SimpleIntegerProperty instead of SimpleStringProperty. This will help us in using Bindings.
  • SubTotal field depends on the values of price and quantity. The best way to achieve this is to bind subTotalProperty to a multiply Binding of price and quantity.

I have created a (not so) simple example basic editable tableview to show the approach. It has additional features, like editable cells, that you (or others seeking the same problem) might need ;)

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.converter.NumberStringConverter;

public class TableViewSample extends Application {

    private TableView<Product> table = new TableView<Product>();
    private final ObservableList<Product> data =
            FXCollections.observableArrayList(
                    new Product("Notebook", 10, 12),
                    new Product("Eraser", 20, 12),
                    new Product("Pencil", 30, 12),
                    new Product("Pen", 40, 12),
                    new Product("Glue", 50, 12));
    final HBox hb = new HBox();

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Book Store Sample");
        stage.setWidth(650);
        stage.setHeight(550);

        final Label label = new Label("Book Store");
        label.setFont(new Font("Arial", 20));

        table.setEditable(true);


        TableColumn name = new TableColumn("Name");
        name.setMinWidth(100);
        name.setCellValueFactory(
                new PropertyValueFactory<Product, String>("name"));
        name.setCellFactory(TextFieldTableCell.forTableColumn());
        name.setOnEditCommit(
                new EventHandler<CellEditEvent<Product, String>>() {
                    @Override
                    public void handle(CellEditEvent<Product, String> t) {
                        ((Product) t.getTableView().getItems().get(
                                t.getTablePosition().getRow())
                        ).setName(t.getNewValue());
                    }
                }
        );


        TableColumn priceCol = new TableColumn("Price");
        priceCol.setMinWidth(100);
        priceCol.setCellValueFactory(
                new PropertyValueFactory<Product, String>("price"));
        priceCol.setCellFactory(TextFieldTableCell.<Product, Number>forTableColumn(new NumberStringConverter()));
        priceCol.setOnEditCommit(
                new EventHandler<CellEditEvent<Product, Number>>() {
                    @Override
                    public void handle(CellEditEvent<Product, Number> t) {
                        ((Product) t.getTableView().getItems().get(
                                t.getTablePosition().getRow())
                        ).setPrice(t.getNewValue().intValue());
                    }
                }
        );

        TableColumn quantityCol = new TableColumn("Quantity");
        quantityCol.setMinWidth(200);
        quantityCol.setCellValueFactory(
                new PropertyValueFactory<Product, Number>("quantity"));
        quantityCol.setCellFactory(TextFieldTableCell.<Product, Number>forTableColumn(new NumberStringConverter()));
        quantityCol.setOnEditCommit(
                new EventHandler<CellEditEvent<Product, Number>>() {
                    @Override
                    public void handle(CellEditEvent<Product, Number> t) {
                        ((Product) t.getTableView().getItems().get(
                                t.getTablePosition().getRow())
                        ).setQuantity(t.getNewValue().intValue());
                    }
                }
        );

        TableColumn subTotalCol = new TableColumn("Sub Total");
        subTotalCol.setMinWidth(200);
        subTotalCol.setCellValueFactory(
                new PropertyValueFactory<Product, String>("subTotal"));


        table.setItems(data);
        table.getColumns().addAll(name, priceCol, quantityCol, subTotalCol);

        final TextField addName = new TextField();
        addName.setPromptText("Name");
        addName.setMaxWidth(name.getPrefWidth());
        final TextField addPrice = new TextField();
        addPrice.setMaxWidth(priceCol.getPrefWidth());
        addPrice.setPromptText("Price");
        final TextField addQuantity = new TextField();
        addQuantity.setMaxWidth(quantityCol.getPrefWidth());
        addQuantity.setPromptText("Quantity");

        final Button addButton = new Button("Add");
        addButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent e) {
                data.add(new Product(
                        name.getText(),
                        Integer.parseInt(addPrice.getText()),
                        Integer.parseInt(addQuantity.getText())));
                addName.clear();
                addPrice.clear();
                addQuantity.clear();
            }
        });

        hb.getChildren().addAll(addName, addPrice, addQuantity, addButton);
        hb.setSpacing(3);

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, table, hb);

        ((Group) scene.getRoot()).getChildren().addAll(vbox);

        stage.setScene(scene);
        stage.show();
    }

    public static class Product {

        private final SimpleStringProperty name;
        private final SimpleIntegerProperty price;
        private final SimpleIntegerProperty quantity;
        private final SimpleIntegerProperty subTotal;

        private Product(String name, int price, int quantity) {
            this.name = new SimpleStringProperty(name);
            this.price = new SimpleIntegerProperty(price);
            this.quantity = new SimpleIntegerProperty(quantity);
            this.subTotal = new SimpleIntegerProperty();
            NumberBinding multiplication = Bindings.multiply(this.priceProperty(), this.quantityProperty());
            this.subTotalProperty().bind(multiplication);
        }

        public String getName() {
            return name.get();
        }

        public SimpleStringProperty nameProperty() {
            return name;
        }

        public void setName(String name) {
            this.name.set(name);
        }

        public int getPrice() {
            return price.get();
        }

        public SimpleIntegerProperty priceProperty() {
            return price;
        }

        public void setPrice(int price) {
            this.price.set(price);
        }

        public int getQuantity() {
            return quantity.get();
        }

        public SimpleIntegerProperty quantityProperty() {
            return quantity;
        }

        public void setQuantity(int quantity) {
            this.quantity.set(quantity);
        }

        public int getSubTotal() {
            return subTotal.get();
        }

        public SimpleIntegerProperty subTotalProperty() {
            return subTotal;
        }

        public void setSubTotal(int subTotal) {
            this.subTotal.set(subTotal);
        }
    }
}

Screenshot

enter image description here

Note - I have defined setCellFactory and setOnCommit to each of the columns. This is because the name, price and quantity columns are editable. You are most welcome to remove them in case you do not seek editable property.

Upvotes: 6

Related Questions