【问题标题】:Update events from ObjectProperty (just like in ObservableList)从 ObjectProperty 更新事件(就像在 ObservableList 中一样)
【发布时间】:2017-07-13 13:02:05
【问题描述】:

如果其中一个元素更改了其属性之一(更新事件),我可以使用提取器 (Callback<E, Observable[]> extractor) 来生成 ListProperty 触发更改事件。

Update Change Event in ObservableList

ObjectProperty<> 是否有等价物?我有一个SimpleObjectProperty,我想在它的值(另一种 bean 类型)的属性发生更改(更新更改事件)时触发事件。

示例代码:

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<>();

}

如果SomeType#property 的值发生变化,我想接收更改事件,如果SomeType#property#testProperty 发生变化,我也想接收。

我不能只听SomeType#property#testProperty,因为当SomeType#property 更改时我不会收到通知(然后我会在错误的对象上侦听更改)。

【问题讨论】:

  • 所以你想听一个SimpleObjectProperty的值变化?
  • 我已经更新了问题,希望现在更清楚了。

标签: java javafx propertychangelistener


【解决方案1】:

如果SomeType#property 的值发生变化,我想接收更改事件,如果SomeType#property#testProperty 发生变化,我也想接收。

我不能只听SomeType#property#testProperty,因为当SomeType#property 更改时我不会收到通知(然后我会在错误的对象上侦听更改)。

这是 JavaFX 当前迭代的各种限制。内置方式不可靠,最好使用 3rd 方库。请参阅此answer 了解更多信息。

对于你的情况,ReactFX 可以以类似的方式使用:

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());
    }
}

输出:

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

关键线

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

是一种侦听器链接。第一个参数是某种类型的属性 (OvservableValue) Type。第二个参数是Type 内的某个其他类型Type2 的“子”属性,它作为从Type 到该属性的函数给出。

现在,只要链中的任何“链接”发生变化,您都会收到通知。您可以通过这种方式连续链接 ovservable,继续监听 sub-sub-... 属性的变化。

【讨论】:

    【解决方案2】:

    我想出了以下内容:

    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();
    
        }
    
    }
    

    不幸的是,由于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);
                    }
                }
            }
    

    这会检查是否相等,只有在不相等时才会通知所有侦听器。当我触发fireValueChangedEvent() 时,值已经改变,新旧值相等,因此不会通知听众。

    【讨论】:

      【解决方案3】:

      上周我遇到了同样的问题,经过多次尝试,我找到了一个似乎可以按预期工作的解决方案:

      • 我创建了一个名为ObjectXProperty&lt;E&gt; 的新类,它与ObjectProperty&lt;E&gt; 具有相同的接口;
      • 它的构造函数可以接受Callback&lt;E,Observable[]&gt;,我们的提取函数;
      • ObjectXProperty 中,我使用了一个SimpleObjectProperty,它代表所有方法;
      • 神奇的技巧在于set(E value) 方法:我创建了一个ObjectBinding,它只是简单地发回,但它使用提取器函数来决定它何时失效!

      • 1234563如果调用unbind 方法,它将再次工作;

      这是我的新课程ObjectXProperty&lt;E&gt;

      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;
      }
      

      }

      【讨论】:

        【解决方案4】:

        我认为你需要为你的对象添加一个监听器。这可以简单地完成。首先,您应该以这种方式使用构造函数和 getter 编写您的类:

        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;
            }
        }
        

        然后在任何有SomeType 实例的地方都可以链接属性,这样就可以得到property 属性的testProperty(),然后简单地向它添加一个侦听器。

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

        【讨论】:

        • 如果我打电话给 SomeType#set(new TestProperty()); 这不起作用我会在旧 bean 上监听更改。
        猜你喜欢
        • 2013-10-25
        • 1970-01-01
        • 2014-06-13
        • 2010-10-05
        • 2016-07-02
        • 1970-01-01
        • 2011-06-09
        • 2021-06-20
        相关资源
        最近更新 更多