Reputation: 323
I came across this issue on a larger scale application where some custom bindings weren't being updated when the value of the source property changed.
I managed to write a simple class to replicate this issue and I really don't get why this happens. Here's a quick test that replicates the problem:
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
public class TestingBindingsFx {
private final ObjectProperty<MyEvent> objectProperty = new SimpleObjectProperty<MyEvent>(this, "objectProperty");
private final BooleanProperty booleanProperty = new SimpleBooleanProperty(this, "booleanProperty");
private ObjectBinding<MyEvent> bindingObj;
private BooleanBinding bindingBool;
public TestingBindingsFx(ObjectProperty<String> selection) {
setupBindings(selection);
}
private void setupBindings(ObjectProperty<String> selection) {
bindingObj = createObjectBinding(selection);
bindingBool = createBooleanBinding(selection);
objectProperty.bind(bindingObj);
booleanProperty.bind(bindingBool);
}
private static ObjectBinding<MyEvent> createObjectBinding(ObjectProperty<String> selection) {
return new ObjectBinding<MyEvent>() {
{
super.bind(selection);
}
@Override
protected MyEvent computeValue() {
System.out.println("createObjectBinding called");
MyEvent ve = selection.get() == null ? MyEvent.EVENT1
: MyEvent.EVENT2;
return ve;
}
};
}
private static BooleanBinding createBooleanBinding(ObjectProperty<String> selection) {
return new BooleanBinding() {
{
super.bind(selection);
}
@Override
protected boolean computeValue() {
System.out.println("createBooleanBinding called");
return selection.get() == null ? true : false;
}
};
}
public static void main(String[] args) {
ObjClass objclass = new ObjClass();
System.out.println("Instantiating TestingBindingsFx...");
TestingBindingsFx fx = new TestingBindingsFx(objclass.selection);
objclass.selection.addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
System.out.println("changed " + oldValue + "->" + newValue);
}
});
System.out.println("Changing selection property values...");
objclass.selection.set("Test 1");
objclass.selection.set("Test 2");
}
enum MyEvent {
EVENT1,
EVENT2;
}
static class ObjClass {
public final ObjectProperty<String> selection = new SimpleObjectProperty<String>(this, "selection");
}
}
So after running this I see:
Instantiating TestingBindingsFx... createObjectBinding called createBooleanBinding called Changing selection property values... changed null->Test 1 changed Test 1->Test 2
When I expected to see something like:
Instantiating TestingBindingsFx... createObjectBinding called createBooleanBinding called Changing selection property values... changed null->Test 1 createObjectBinding called createBooleanBinding called changed Test 1->Test 2 createObjectBinding called createBooleanBinding called
The ChangeListener
is working as expected (just put it there for verification purposes) and it's getting called every time I change the value of the selection property.
But the custom bindings are never updated after the first time and looking at the code I can't understand why. At first I thought it could be related with weak references, but I even turned the binding objects into class level variables and yet no change.
I feel that I might be missing something crucial here, but after 2 hours of looking at this code I just can't see why. On my actual application it's even more strange since one of the custom bindings actually work fine.
Upvotes: 4
Views: 2392
Reputation: 82461
This is because JavaFX is lazy, seriously.
If a dependency is invalidated, a binding is marked as invalid and InvalidationListener
s are notified of a potential change. Unless you add ChangeListener
s or use get
to retrieve the value, the computeValue
method is never used, since there is "no one who wants to know about the new value". This can improve performance.
Binding properties is done using a InvalidationListener
and the properties are also refreshed lazily.
You could e.g. add change listeners to the properties to force recalculation of values every time the binding is invalidated:
private void setupBindings(ObjectProperty<String> selection) {
bindingObj = createObjectBinding(selection);
bindingBool = createBooleanBinding(selection);
objectProperty.bind(bindingObj);
booleanProperty.bind(bindingBool);
objectProperty.addListener((a,b,c)-> {});
booleanProperty.addListener((a,b,c)-> {});
}
Upvotes: 8