Update events from ObjectProperty (just like in ObservableList)

十年热恋 提交于 2019-12-13 02:55:57

问题


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).


回答1:


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.




回答2:


I had the same problem last week, and after many tries, I found a solution that seems to work as expected:

  • I created a new class called ObjectXProperty<E>, that has the same interface of an ObjectProperty<E>;
  • It has constructors that can accept a Callback<E,Observable[]>, our extractor function;
  • Inside the ObjectXProperty, I use a SimpleObjectProperty that deleguates all methods;
  • The magic trick lies in the set(E value) methods : I create an ObjectBinding that simply send back the value, but it uses the extractor function to decide when it's become invalidated!

  • This trick will not be applied if the bind method was used previously on the ObjectXProperty, to let the "real" binding do his job; it will work again if the unbind method is called;

Here's my new class ObjectXProperty<E> :

import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.util.Callback;

/**
 *
 * @author Claude Bouchard - 2017
 */

public class ObjectXProperty<E> extends ObjectProperty<E> {

SimpleObjectProperty<E> p;
Callback<E, Observable[]> extractor;

boolean externalBound = false;

public ObjectXProperty(Callback<E, Observable[]> extractor) {
    this.extractor = extractor;
}

public ObjectXProperty(E init, Callback<E, Observable[]> extractor) {

    p = new SimpleObjectProperty();
    this.extractor = extractor;
    set(init);

}

public ObjectXProperty(Object bean, String name, Callback<E, Observable[]> extractor) {
    p = new SimpleObjectProperty(bean, name);
    this.extractor = extractor;
}

public ObjectXProperty(Object bean, String name, E init, Callback<E, Observable[]> extractor) {
    p = new SimpleObjectProperty(bean, name);
    this.extractor = extractor;
    set(init);

}

@Override
public void set(E value) {
    if (!externalBound) {
        if (value != null) {
            p.bind(Bindings.createObjectBinding(() -> {
                return value;
            }, extractor.call(value)));

        } else {
            p.bind(Bindings.createObjectBinding(() -> {
                return value;
            }, new Observable[]{}));
        }
    } else {
        p.set(value); //As expected, it will throw a java.lang.RuntimeException
    }
}

@Override
public E get() {
    return p.get();
}

@Override
public void addListener(ChangeListener<? super E> listener) {
    p.addListener(listener);
}

@Override
public void removeListener(ChangeListener<? super E> listener) {
    p.removeListener(listener);
}

@Override
public void addListener(InvalidationListener listener) {
    p.addListener(listener);
}

@Override
public void removeListener(InvalidationListener listener) {
    p.removeListener(listener);
}

@Override
public Object getBean() {
    return p.getBean();
}

@Override
public String getName() {
    return p.getName();
}

@Override
public void bind(ObservableValue<? extends E> observable) {
    p.bind(observable);
    externalBound = true;
}

@Override
public void unbind() {
    p.unbind();
    externalBound = false;
    set(get()); //to reactivate the extractor on the last value
}

@Override
public boolean isBound() {
    return externalBound;
}

}




回答3:


I came up with the following:

public class ObservableValueProperty<T> extends SimpleObjectProperty<T> {

    private InvalidationListener listener = null;

    private final Callback<T, Observable[]> extractor;

    public ObservableValueProperty() {
    this(null);
    }

    public ObservableValueProperty(final Callback<T, Observable[]> extractor) {
    this.extractor = extractor;
    }

    @Override
    protected void fireValueChangedEvent() {
    super.fireValueChangedEvent();
    }

    @Override
    public void setValue(final T v) {
    if (extractor != null) {
        final T oldValue = super.get();
        if (oldValue != null) {
        for (final Observable o : extractor.call(oldValue)) {
            o.removeListener(listener);
        }
        }
        listener = o -> fireValueChangedEvent();
        for (final Observable o : extractor.call(v)) {
        o.addListener(listener);
        }
    }
    super.setValue(v);
    }
}

public class ObservableValuePropertyTest4 implements ChangeListener<Object> {

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception {
    }

    @Before
    public void setUp() throws Exception {
    }

    @After
    public void tearDown() throws Exception {
    }

    static class NestedBean {

    StringProperty nestedProperty = new SimpleStringProperty("hans");

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

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

    @Override
    public boolean equals(final Object obj) {
        if (obj instanceof NestedBean) {
        System.err.println(this.nestedProperty.get() + " " + ((NestedBean) obj).nestedProperty.get());
        return Objects.equal(this.nestedProperty.get(), ((NestedBean) obj).nestedProperty.get());
        }
        return false;
    }

    }

    private ObservableValueProperty<NestedBean> p;

    private NestedBean nestedBean;

    private String newNestedValue = null;

    @Test
    public void test01() {
    p = new ObservableValueProperty<>(NestedBean.extractor());
    nestedBean = new NestedBean();
    p.setValue(nestedBean);
    p.addListener(this);
    nestedBean.nestedProperty.set("peter");
    assertEquals("peter", newNestedValue);
    }

    @Override
    public void changed(final ObservableValue<? extends Object> observable, final Object oldValue,
        final Object newValue) {
    System.err.println("Changed");
    newNestedValue = nestedBean.nestedProperty.get();

    }

}

Unfortunately, this does not fire any change events because of ExpressionHelper$SingleChange:

@Override
        protected void fireValueChangedEvent() {
            final T oldValue = currentValue;
            currentValue = observable.getValue();
            final boolean changed = (currentValue == null)? (oldValue != null) : !currentValue.equals(oldValue);
            if (changed) {
                try {
                    listener.changed(observable, oldValue, currentValue);
                } catch (Exception e) {
                    Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
                }
            }
        }

This checks for equality and only if not equal, notifies all listeners. When I trigger fireValueChangedEvent() the value has already changed, and new- and old values are equal, therefore no notification to listeners.




回答4:


I think you need to add a listener to your object. This can be done simply. First of all you should write your class with a constructor and with getters this way:

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;

public class SomeType {

    public ObjectProperty<TestProperty> property;

    public SomeType(TestProperty testProperty) {
        this.property = new SimpleObjectProperty<>(testProperty);
    }

    public TestProperty getProperty() {
        return property.get();
    }

    public ObjectProperty<TestProperty> propertyProperty() {
        return property;
    }
}

Then anywhere you have an instance of SomeType you can chain the properties, so you get the property the property's testProperty() and then simply add a listener to it.

someType.getProperty().testProperty().addListener((observable, oldValue, newValue) -> {
        // Do whatever you want if the its value changed.
        // You can also use its old or new value.
    });


来源:https://stackoverflow.com/questions/45081431/update-events-from-objectproperty-just-like-in-observablelist

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!