Why do bindings in JavaFX work in the way they do?

Viewed 856

I'm trying to understand why data binding works in the way it works in JavaFX. It lets you bind ObservableValues to other ObservableValues like this:

val prop0 = SimpleStringProperty("xul")
val prop1 = SimpleStringProperty("baz")
prop0.bind(prop1)

When I do this both properties will have the value baz.

I can also chain bindings:

val prop0 = SimpleStringProperty("xul")
val prop1 = SimpleStringProperty("baz")
val prop2 = SimpleStringProperty("qux")

prop0.bind(prop1)
prop1.bind(prop2)
// all will have the value "qux"

The docs says that I can't set the value of a property which is bound:

prop0.value = "foo" // exception

although I can have cyclic bindings:

val prop0 = SimpleStringProperty("xul")
val prop1 = SimpleStringProperty("baz")
val prop2 = SimpleStringProperty("wom")

prop0.bindBidirectional(prop1)
prop1.bindBidirectional(prop2)
prop2.bindBidirectional(prop0)

but a simple bidirectional binding will result in a StackOverflowError:

prop0.bindBidirectional(prop1)
prop1.bindBidirectional(prop0)

That's why (I guess) there is an explicit way of doing bidirectional binding:

val prop0 = SimpleStringProperty("xul")
val prop1 = SimpleStringProperty("baz")
val prop2 = SimpleStringProperty("wom")

prop0.bindBidirectional(prop1)
prop0.bindBidirectional(prop2)

What I don't understand is why JavaFX restricts unidirectional binding a value to multiple other values:

val prop0 = SimpleStringProperty("xul")
val prop1 = SimpleStringProperty("baz")
val prop2 = SimpleStringProperty("qux")

prop0.bind(prop1)
// this will unbind prop0 from prop1
prop0.bind(prop2)

but lets me create any number of BidirectionalBindings? Why is there a difference between these concepts when a bidirectional binding should have been two unidirectional bindings?

What's also weird is that there is the Binding interface which creates a new entity which will depend on the source ObservableValues:

val num1 = SimpleIntegerProperty(1)
val num2 = SimpleIntegerProperty(2)
// creates a new binding, which will change its value
// whenever num1 or num2 changes
val sum = num1.add(num2)

which can be disposed:

sum.dispose()

but when I call bind or bindBidirectional it does not return a disposable Binding.

I read the docs but these things are not explained in them. Did I miss something? What is the internal logic which makes this behavior necessary?

1 Answers

A binding property a to b means that as long as the binding is "in place" the value of a is always the same as in of value b. Binding a to c in addition to b would require a's value to be the same as b and c, but they could contain different values. For this reason only a single binding is allowed. Unbinding a property automatically takes care of the "disposing".

Bidirectional binding results in the values of both properties being kept the same. Changing one updates the other and therefore you can bind a property in a arbitrary number of properties. If you change a property all other properties that are bidirectionally bound to it are updated and changing one of the other properties updates the property itself which updates all the other properties too. No problem here.
JavaFX chose to unbind bidirectional bindings a different way:

a.bindBidirectional(b);
...
a.unbindBidirectional(b);

This takes care of the "disposing".

Binding objects in contrast to bindings between properties are ObservableValue objects that depend on some Observables. The binding object automatically registers InvalidationListeners to it's dependencies, but there is no way for the dependencies to know that this listener can be removed since the binding object is no longer used. This is why the binding object allows you to do the "cleanup" when you no longer need it by calling it's dispose method.

Related