Search code examples
javafxfxml

how to apply bidirectional method when doing calculation number between integer and double value of text field in the following code?


I am new to binding especially bidirectional binding. I have three text fields of quantity, price, and total. the following code from online which converted these text field into Integer and Double format for their values.

    StringConverter<Number> converter = new NumberStringConverter();
    StringConverter<Double> toDouble = new DoubleStringConverter();
    
    TextFormatter<Number> quantity = new TextFormatter<>(converter);
    TextFormatter<Double> price = new TextFormatter<>(toDouble);
    TextFormatter<Double> total = new TextFormatter<>(toDouble);

    Quantity.setTextFormatter(quantity);
    Price.setTextFormatter(price);
    Total.setTextFormatter(total);
    
    total.valueProperty().bindBidirectional(Bindings.createObjectBinding(() -> {
        
        // ?????????????????????????????????????????
        
    }, quantity.valueProperty(), price.valueProperty()));
    

Please help fill out the calculation or any other way to do trigger the change whenever i enter differnt number of the text field?


Solution

  • Since the text field representing the total is not editable, you don't need a bidirectional binding here, just a regular binding that updates one value (the total's value) when the computed value changes.

    You can do something like

    total.valueProperty().bind(Bindings.createObjectBinding(
        () -> quantity.getValue() * price.getValue(),
        quantity.valueProperty(), price.valueProperty()));
    

    The Bindings.createXXXBinding(...) methods create observable values whose values are computed (here essentially by the formula quantity * price). The binding expresses the fact that when the computed value changes, the value of total should change. A bidirectional binding expresses the idea that when either one of two values changes, the other should be set to match it. This simply won't work in this scenario, because you can't set a computed value.

    From a programming perspective, this works because the bindBidirectional() method expects a WritableValue (of the appropriate type); the Bindings.createXXXBinding() methods return an ObservableValue, which is not a WritableValue. (By contrast, the bind() method only expects an ObservableValue.) This makes sense from a semantic perspective too: if you know the total, there's no unique way to determine the price and quantity; however if you know the price and quantity, you can determine the total. So the relationship is not symmetric.

    Here's a complete working example. This can probably be improved substantially, e.g. by using TextFormatters that restrict to valid entry and custom StringConverter implementations that use localized number formats, etc.

    import javafx.application.Application;
    import javafx.beans.binding.Bindings;
    import javafx.geometry.HPos;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.Label;
    import javafx.scene.control.TextField;
    import javafx.scene.control.TextFormatter;
    import javafx.scene.layout.ColumnConstraints;
    import javafx.scene.layout.GridPane;
    import javafx.stage.Stage;
    import javafx.util.converter.DoubleStringConverter;
    import javafx.util.converter.IntegerStringConverter;
    
    
    public class App extends Application {
    
        @Override
        public void start(Stage stage) {
            
            TextFormatter<Integer> quantity = new TextFormatter<>(new IntegerStringConverter(), 0);
            TextFormatter<Double> price = new TextFormatter<>(new DoubleStringConverter(), 0.0);
            TextFormatter<Double> total = new TextFormatter<>(new DoubleStringConverter(), 0.0);
            
            
            
            total.valueProperty().bind(Bindings.createObjectBinding(
                    () -> quantity.getValue() * price.getValue(), 
                    quantity.valueProperty(), price.valueProperty()));
            
            TextField quantityTF = new TextField("0");
            quantityTF.setTextFormatter(quantity);
            
            TextField priceTF = new TextField("0.0");
            priceTF.setTextFormatter(price);
            TextField totalTF = new TextField();
            totalTF.setEditable(false);
            totalTF.setTextFormatter(total);
            
            
            GridPane root = new GridPane();
            ColumnConstraints leftCol = new ColumnConstraints();
            leftCol.setHalignment(HPos.RIGHT);
            ColumnConstraints rightCol = new ColumnConstraints();
            rightCol.setHalignment(HPos.LEFT);
            root.getColumnConstraints().setAll(
                    leftCol, rightCol
            );
            root.setAlignment(Pos.CENTER);
            root.setPadding(new Insets(5));
            root.setHgap(5);
            root.setVgap(5);
            root.addRow(0, new Label("Price:"), priceTF);
            root.addRow(1, new Label("Quantity:"), quantityTF);
            root.addRow(2, new Label("Total:"), totalTF);
            
            stage.setScene(new Scene(root, 800, 500));
            stage.show();
        }
    
        public static void main(String[] args) {
            launch();
        }
    
    }