问题
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, ifSomeType#property#testProperty
changes.I cannot just listen for
SomeType#property#testProperty
, since I would not be notified, whenSomeType#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 anObjectProperty<E>
; - It has constructors that can accept a
Callback<E,Observable[]>
, our extractor function; - Inside the
ObjectXProperty
, I use aSimpleObjectProperty
that deleguates all methods; The magic trick lies in the
set(E value)
methods : I create anObjectBinding
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 theObjectXProperty
, to let the "real" binding do his job; it will work again if theunbind
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