Suzan Cioc
Suzan Cioc

Reputation: 30127

How to bind nested properties bidirectional in JavaFX?

How to bind nested properties bidirectional in JavaFX?

For example, I have an object p which has propeties prop1 and prop2, which in turn have properties value both.

How to bind them bidirectional, so that they will constrain equal?

package tests.javafx.beans.binding;

import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;

public class Try_BindNested {

    public static class Nested {

        private SimpleDoubleProperty value = new SimpleDoubleProperty();

        public double getValue() {
            return value.get();
        }

        public void setValue(double value) {
            this.value.set(value);
        }

        public SimpleDoubleProperty valueProperty() {
            return value;
        }

    }


    public static class Parent {

        private SimpleObjectProperty<Nested> prop1 = new SimpleObjectProperty<Nested>();

        private SimpleObjectProperty<Nested> prop2 = new SimpleObjectProperty<Nested>();

        public Nested getProp1() {
            return prop1.get();
        }

        public void setProp1(Nested prop1) {
            this.prop1.set(prop1);
        }

        public SimpleObjectProperty<Nested> prop1Property() {
            return prop1;
        }

        public Nested getProp2() {
            return prop2.get();
        }

        public void setProp2(Nested prop1) {
            this.prop2.set(prop1);
        }

        public SimpleObjectProperty<Nested> prop2Property() {
            return prop2;
        }

    }

    public static void main(String[] args) {


        Parent p = new Parent();

        // how to bind bidirectional p.prop1.value = p.prop2.value?


    }

}

Upvotes: 1

Views: 2274

Answers (1)

James_D
James_D

Reputation: 209494

Assuming you want to handle changes to the "intermediate" property (i.e. p.prop2.value is updated if you do p.setProp1(...);) then there's no way to do this directly with bindings; you have to use a pair of listeners.

Using the standard JavaFX properties API:

ObservableDoubleValue prop1Value = Bindings.selectDouble(p.prop1Property(), "value");
ObservableDoubleValue prop2Value = Bindings.selectDouble(p.prop2Property(), "value");
prop1Value.addListener(new ChangeListener<Number>() {
   @Override
   public void changed(ObservableValue<? extends Number> ov, Number oldVal, Number newValue) {
        p.getProp2().setValue(newValue);
   }
});
prop2Value.addListener(new ChangeListener<Number>() {
   @Override
   public void changed(ObservableValue<? extends Number> ov, Number oldVal, Number newValue) {
        p.getProp1().setValue(newValue);
   }
});

The Bindings.select methods are a bit ugly (they rely on reflection and are not type safe); additionally in early JavaFX 8 versions they spew out all sorts of warnings if any of the intermediate properties are null (despite this being a supported use case according to the API docs). You might look at the EasyBind framework if this becomes an issue. You would still need the "double listener" idiom here too, though.

The reason there's no "built-in" bidirectional binding for this case is that it's ambiguous as to how to update the properties. In general, you might want something like p.getProp1().setValue(newVal);, or you might want to change the intermediate property: p.setProp1(new Nested(newVal));

Finally, you might need to be careful doing this with floating point types. The reason you don't end up with infinite recursion with the code above is that the set method in DoubleProperty checks to see if a change really occurs before notifying change listeners. If the value involves any computation, you risk rounding errors creating a situation where the property's check for equality fails incorrectly, and so you could end up with a nice StackOverflowError. If your value property does anything more complex than simply storing the value, you might want to guard the calls to setValue with a tolerance:

private static final double TOLERANCE = 1e-16 ; // or some other suitable small number

prop1Value.addListener(new ChangeListener<Number>() {
   @Override
   public void changed(ObservableValue<? extends Number> ov, Number oldVal, Number newValue) {
        if (Math.abs(p.getProp2.getValue() - newVal) > TOLERANCE) {
            p.getProp2().setValue(newValue);
        }
   }
});

and similarly in the other direction.

Upvotes: 6

Related Questions