【问题标题】:Custom bidirectional bindings in JavaFXJavaFX 中的自定义双向绑定
【发布时间】:2015-01-19 02:25:36
【问题描述】:

我正在尝试实现一个涉及 2 个字段计算的 GUI。我的模型有 2 个属性和一个绑定。

ObjectProperty<BigDecimal> price = new SimpleObjectProperty<>();
ObjectProperty<BigDecimal> quantity= new SimpleObjectProperty<>();
ObjectBinding<BigDecimal> totalPrice = new ObjectBinding<BigDecimal>() {
    { bind(price,quantity);}
    protected BigDecimal computeValue() {
        if (price.get() == null || quantity.get() == null) return null;
        return price.get().multiply(quantity.get());
    }
};

我的 GUI 有 3 个TextField 来匹配价格、金额、总价格。通常,我正在我的属性和我的TextField 之间进行常规绑定

priceTextField.textProperty().bindBidirectional(myModel.priceProperty(), new NumberStringConverter());

现在这有点棘手。如果用户修改价格或数量,它必须更新 TotalPrice(这是绑定到目前为止所做的)。但我希望能够做到以下几点:如果用户更新 TotalPrice,那么它会根据固定价格重新计算数量。

所以问题是:如何创建这样的流程 => TotalPrice 绑定了价格和数量,但数量绑定了 TotalPrice 和价格。当我在totalPriceTextfield 中输入内容时,它应该更新quantityTextField,反之亦然。

谢谢。

****** 编辑 ******** 这是一段丑陋的代码,只是为了说明我想要实现的目标(nb:我知道我可以使用Binding.multiply 和其他方法,但我需要未来的项目来实现计算功能)

public class TestOnBindings {

    private DoubleProperty price = new SimpleDoubleProperty(10.0);
    private DoubleProperty quantity = new SimpleDoubleProperty(1.0);
    private DoubleProperty total = new SimpleDoubleProperty(1.0);

    private DoubleBinding totalBinding = new DoubleBinding() {
        {bind(quantity,price);}
        @Override
        protected double computeValue() {
            return quantity.get()*price.get();
        }
    };

    private DoubleBinding quantityBinding = new DoubleBinding() {
        {bind(total,price);}
        @Override
        protected double computeValue() {
            return total.get()/price.get();
        }
    }; 


    public TestOnBindings(){
        total.bind(totalBinding); //should really not do that, looks ugly
        quantity.bind(quantityBinding); //now you're asking for troubles
    }


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


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

    public void setTotal(Double total){
        this.total.set(total);
    }


    public Double getTotal(){
        return total.get();
    }


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

    public static void main(String[] args) {
        TestOnBindings test = new TestOnBindings();
        test.setQuantity(5.0);

        System.out.println("Total amount = " + test.getTotal());
    }

}

还有明显的好错误:

线程“main”java.lang.RuntimeException 中的异常:无法设置绑定值。 在 javafx.beans.property.DoublePropertyBase.set(DoublePropertyBase.java:142)

【问题讨论】:

    标签: java data-binding javafx javabeans


    【解决方案1】:

    我认为我的问题已经在另一个post 中得到了回答,并且这个人在他的blog 上提供了一个简单的方法

    我修改了一些他的类以适应我的需要并且有一个非常易于使用的类:

    public class CustomBinding {
    
        public static <A ,B> void bindBidirectional(Property<A> propertyA, Property<B> propertyB, Function<A,B> updateB, Function<B,A> updateA){
            addFlaggedChangeListener(propertyA, propertyB, updateB);
            addFlaggedChangeListener(propertyB, propertyA, updateA);
        }
    
        public static <A ,B> void bind(Property<A> propertyA, Property<B> propertyB, Function<A,B> updateB){
            addFlaggedChangeListener(propertyA, propertyB, updateB);
        }
    
        private static <X,Y> void addFlaggedChangeListener(ObservableValue<X> propertyX, WritableValue<Y> propertyY, Function<X,Y> updateY){
            propertyX.addListener(new ChangeListener<X>() {
                private boolean alreadyCalled = false;
    
                @Override
                public void changed(ObservableValue<? extends X> observable, X oldValue, X newValue) {
                    if(alreadyCalled) return;
                    try {
                        alreadyCalled = true;
                        propertyY.setValue(updateY.apply(newValue));
                    }
                    finally {alreadyCalled = false; }
                }
            });
        }
    }
    

    然后将它应用到我的示例中......仍然需要一些微调,但它可以完成工作。

    public class TestOnBindings {
    
        private DoubleProperty price = new SimpleDoubleProperty(10.0);
        private DoubleProperty quantity = new SimpleDoubleProperty(1.0);
        private DoubleProperty total = new SimpleDoubleProperty(1.0);
    
        public TestOnBindings(){
            CustomBinding.<Number,Number>bindBidirectional(quantity, total, 
                    (newQuantity)-> newQuantity.doubleValue() * price.get(),
                    (newTotal)-> newTotal.doubleValue() /price.get());
    
            CustomBinding.<Number,Number>bind(price, total, 
                    (newPrice)-> newPrice.doubleValue() * quantity.get());
        }
    
        public void setPrice(Double price){this.price.set(price);}
        public void setQuantity(Double quantity){this.quantity.set(quantity);}
        public void setTotal(Double total){this.total.set(total);}
    
        public Double getTotal(){return total.get();}
        public Double getQuantity(){return quantity.get();}
        public Double getPrice(){return price.get();}
    
    
        public static void main(String[] args) {
            TestOnBindings test = new TestOnBindings();
    
            test.setQuantity(5.0);
    
            System.out.println("Quantity = " + test.getQuantity());
            System.out.println("Price = " + test.getPrice());
            System.out.println("Total = " + test.getTotal());
    
            test.setTotal(60.0);
    
            System.out.println("---------------------------------------------");
            System.out.println("Quantity = " + test.getQuantity());
            System.out.println("Price = " + test.getPrice());
            System.out.println("Total = " + test.getTotal());
    
            test.setPrice(5.0);
    
            System.out.println("---------------------------------------------");
            System.out.println("Quantity = " + test.getQuantity());
            System.out.println("Price = " + test.getPrice());
            System.out.println("Total = " + test.getTotal());
        }
    
    }
    

    【讨论】:

      【解决方案2】:

      你真的必须使用绑定吗?这将完成这项工作。无论如何,绑定只是花哨的侦听器。

      public TestOnBindings(){
          price.addListener((obs,ov,nv) -> {
              total.set(price.doubleValue()*quantity.doubleValue());
          });
          //could be same listener as price if total is a complicated function
          quantity.addListener((obs,ov,nv) -> {
              total.set(price.doubleValue()*quantity.doubleValue());
          });
          total.addListener((obs,ov,nv) -> {
              quantity.set(total.doubleValue()/price.doubleValue());
          });
      }
      

      【讨论】:

      • 嗨,Brian,是的,您是对的,感谢您的评论。唯一的事情是您要避免以无休止的刷新循环结束。
      • 我想过这个,但是我测试的时候并没有发生。如果这是一个问题,您可以编写非匿名侦听器,并在设置总数的那个中执行 total.removeListener(...); 并在之后重新添加。
      • TestOnBindings test = new TestOnBindings(); test.setQuantity(5.0); System.out.println("总金额 = " + test.getTotal()); test.setTotal(60.0); System.out.println("数量金额 = " + test.getQuantity());我以输出 100/6/60 结束(价格初始值为 10,数量为 1,总计为 1),这表明存在循环。感谢您的所有建议。
      • 我只是测试了错误和无限循环,我没有检查数学。它现在有效,只是一个愚蠢的数学错误。我有 qty = tot / qty 。我找不到参考,但有一种机制可以阻止听众循环。
      • 没错,您的代码不会导致无限循环。但是在 GUI 上工作时,由于前面提到的无限循环问题,以下代码确实会导致 StackOverflowError。 slider1.valueProperty().addListener((obs,nv,ov)-&gt;{slider2.setValue(nv.doubleValue());}); slider2.valueProperty().addListener((obs,nv,ov)-&gt;{slider1.setValue(nv.doubleValue());});
      猜你喜欢
      • 2015-03-01
      • 2014-01-09
      • 1970-01-01
      • 2016-06-10
      • 1970-01-01
      • 2017-04-22
      • 1970-01-01
      • 1970-01-01
      • 2019-07-23
      相关资源
      最近更新 更多