Search code examples
javajavafxpropertychangelistener

Update events from ObjectProperty (just like in ObservableList)


I can use an extractor (Callback<E, Observable[]> extractor) to make a ListProperty fire change events if one of its elements changed one of its properties (update event).

Update Change Event in ObservableList

Is there an equivalent for ObjectProperty<>? I have an SimpleObjectProperty which I want to fire events when properties of it's value (another bean type) change (update change events).

Sample code:

public class TestBean {

    public static <T extends TestBean> Callback<T, Observable[]> extractor() {

    return (final T o) -> new Observable[] { o.testPropertyProperty() };
    }

    private final StringProperty testProperty = new SimpleStringProperty();

    public final StringProperty testPropertyProperty() {
    return this.testProperty;
    }

    public final String getTestProperty() {
    return this.testPropertyProperty().get();
    }

    public final void setTestProperty(final String testProperty) {
    this.testPropertyProperty().set(testProperty);
    }

}

public class SomeType {

    /**
     * How can I listen for changes of TestBean#testProperty?
     */
    private final ObjectProperty<TestBean> property = new SimpleObjectProperty<>();

}

I want to receive change events if the value of SomeType#property changes, but also, if SomeType#property#testProperty changes.

I cannot just listen for SomeType#property#testProperty, since I would not be notified when SomeType#property was changed (I would then listen on the wrong object for changes).


Solution

  • I want to receive change events if value of SomeType#property changes, but also, if SomeType#property#testProperty changes.

    I cannot just listen for SomeType#property#testProperty, since I would not be notified, when SomeType#property was changed (I would then listen on the wrong object for changes).

    This is a limitation of sorts of the current iteration of JavaFX. The built-in way is unreliable and you're better off using 3rd party libraries. See this answer for more information.

    For you case, ReactFX can be utilized in a similar way:

    import javafx.beans.property.ObjectProperty;
    import javafx.beans.property.SimpleObjectProperty;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    
    import org.reactfx.value.Val;
    import org.reactfx.value.Var;
    
    class TestBean {
    
        private final StringProperty testProperty = new SimpleStringProperty();
        public final StringProperty testPropertyProperty()        { return testProperty; }
        public final String getTestProperty()                     { return testProperty.get(); }
        public final void setTestProperty(String newTestProperty) { testProperty.set(newTestProperty); }
    }
    
    public class SomeType {
    
        private final ObjectProperty<TestBean> property = new SimpleObjectProperty<>();
        public final ObjectProperty<TestBean> propertyProperty() { return property; }
        public final TestBean getProperty()                      { return property.get(); }
        public final void setProperty(TestBean newProperty)      { property.set(newProperty); }
    
        public static void main(String[] args) {
            SomeType someType = new SomeType();
            Var<String> chainedTestProperty = Val.selectVar(someType.propertyProperty(), TestBean::testPropertyProperty);
            chainedTestProperty.addListener((obs, oldVal, newVal) -> System.out.println(obs + " " + oldVal + "->" + newVal));
    
            //Tests
            someType.setProperty(new TestBean());
            someType.getProperty().setTestProperty("s1");
            TestBean bean2 = new TestBean();
            bean2.setTestProperty("s2");
            someType.setProperty(bean2);
            someType.setProperty(new TestBean());
        }
    }
    

    Output:

    org.reactfx.value.FlatMappedVar@7aec35a null->s1 
    org.reactfx.value.FlatMappedVar@7aec35a s1->s2 
    org.reactfx.value.FlatMappedVar@7aec35a s2->null
    

    The key line

    Var<String> chainedTestProperty = Val.selectVar(someType.propertyProperty(), TestBean::testPropertyProperty);
    

    is a sort of listener chaining. The first argument is a property (OvservableValue) of some type Type. The second argument is the "sub"-property of some other type Type2 inside Type, which is given as a function from Type to that property.

    Now whenever any "links" in the chain change, you are notified. You can continue to listen to changes in sub-sub-... properties by continuously chaining ovservables this way.