mfc
mfc

Reputation: 323

JavaFX Custom Bindings not working

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

Answers (1)

fabian
fabian

Reputation: 82461

This is because JavaFX is lazy, seriously.

If a dependency is invalidated, a binding is marked as invalid and InvalidationListeners are notified of a potential change. Unless you add ChangeListeners 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

Related Questions