Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
356 views
in Technique[技术] by (71.8m points)

java - Bidirectional binding with ObjectBinding in JavaFX

I have a simple bean that has some properties related with each other. For example, this bean has a property called discountRate and another called discountValue. The discountRate is the percentage (%) of discount applied to a sale. The discountValue is the value($) of discount applied to a sale. As the user can inform either the percentage or the value and I need store the two values in the database, a JavaFX bidirectional binding would solve the problem, however, as you can imagine, these values are correlated but aren't the same. I tried solve this problem creating bindings in the two sides:

public class ExampleBean{

    private ObjectProperty<BigDecimal> discountValue;
    private ObjectProperty<BigDecimal> discountRate;


    public BigDecimal getDiscountvalue() {
        return discountValueProperty().getValue();
    }

    public void setDiscountValue(BigDecimal discountvalue) {
        this.discountValueProperty().set(discountvalue);
    }

    public ObjectProperty<BigDecimal> discountValueProperty() {
        if(discountValue==null){
            discountValue=new SimpleObjectProperty<BigDecimal>(new BigDecimal("0.00"));
            discountRate=new SimpleObjectProperty<BigDecimal>(new BigDecimal("0.00"));
            configureDiscountBinding();
        }
        return discountValue;
    }

    private void configureDiscountBinding(){
        discountValue.bind(Bindings.createObjectBinding(new Callable<BigDecimal>() {
            @Override
            public BigDecimal call() throws Exception {
                return getDiscountRate().multiply(getTotalValue()).divide(new BigDecimal("100"));
            }
        }, discountRateProperty()));
        discountRate.bind(Bindings.createObjectBinding(new Callable<BigDecimal>() {
            @Override
            public BigDecimal call() throws Exception {
                return getDiscountValue().multiply(new BigDecimal("100")).divide(getTotalValue());
            }
        }, discountValueProperty()));
    }

    public BigDecimal getDiscountRate() {
        return discountRateProperty().getValue();
    }

    public void setDiscountRate(BigDecimal discountRate) {
        this.discountRateProperty().set(discountRate);
    }

    public ObjectProperty<BigDecimal> discountRateProperty() {
        if(discountRate==null){
            discountRate=new SimpleObjectProperty<BigDecimal>(new BigDecimal("0.00"));
            discountValue=new SimpleObjectProperty<BigDecimal>(new BigDecimal("0.00"));
            configureDiscountBinding();
        }
        return discountRate;
    }
}

As you could see, I'm trying calculate the percentage when the value is setted, and calculate the value when the rate is setted. The binding I tried above can't be bound, as this will enter in a eternal loop. Is there a way I can do a binding to solve this problem, or I need do the calculation inside setters?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You will need to listen to changes to the fields but keep track if the listener has been fired, so as not to fire again in endless loops. Inspiration was the actual code from JavaFX, decompiled here.

private void configureDiscountBinding() {
    discountValue.addListener(new ChangeListener<BigDecimal>() {
        private boolean changing;

        @Override public void changed(ObservableValue<? extends BigDecimal> observable, BigDecimal oldValue, BigDecimal newValue) {
            if( !changing ) {
                try {
                    changing = true;
                    discountRate.set(newValue.multiply(new BigDecimal("100")).divide(getTotalValue(), RoundingMode.HALF_DOWN));
                }
                finally {
                    changing = false;
                }
            }
        }
    });

    discountRate.addListener(new ChangeListener<BigDecimal>() {
        private boolean changing;

        @Override public void changed(ObservableValue<? extends BigDecimal> observable, BigDecimal oldValue, BigDecimal newValue) {
            if( !changing ) {
                try {
                    changing = true;
                    discountValue.set(newValue.multiply(getTotalValue()).divide(new BigDecimal("100"), RoundingMode.HALF_DOWN));
                }
                finally {
                    changing = false;
                }
            }
        }
    });
}

This is simplistic and cumbersome; if you are using this feature extensively, you could refactor the inner ChangeListeners to some common type, or some other clever solution.

I tested the code above with the following main (you wil have to provide a BigDecimal getTotalValue() method, in my case I just returned a constant BigDecimal):

public static void main(String[] args) {
    ExampleBean e = new ExampleBean();

    System.out.println("Setting rate to 50%");
    e.discountRateProperty().set(new BigDecimal(50.0));
    System.out.println("-> value=" + e.getDiscountvalue());

    System.out.println("Setting value to 25");
    e.discountValueProperty().set(new BigDecimal(25.0));
    System.out.println("-> rate=" + e.getDiscountRate() + "%");
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...