JavaFX Collection Property和Binding

By | 2017年4月10日

ObservableList, ObservableSet和ObservableMap可以封装成Property,支持binding。

1. ListProperty

ListProperty是ObservableList的封装,数据存储在传入的一个ObservableList。ListProperty实现了ObservableList接口,可以直接调用ObservableList方法,来操作封装的ObservableList对象。

1.1 创建ListProperty

ListProperty必须要接受一个ObservableList才能使用,向一个空的ListProperty添加元素,会抛出异常。使用set方法,给一个空ListProperty传递ObservableList,或构造ListProperty时传递ObservableList。

// Create an empty ListProperty.
ListProperty<String> listProperty = new SimpleListProperty<>();
// Throw exception.
// listProperty.add("Hello");
listProperty.set(FXCollections.observableArrayList());
listProperty.add("Hello");

// Pass an ObservableList in constructor.
ListProperty<String> listProperty2 = new SimpleListProperty<>(
    FXCollections.observableArrayList());
listProperty.add("Hello");

1.2 监听Invalidation事件

  • 封装的ObservableList中的元素,有任何变化,都会触发Invalidation event。
  • 重新set ListProperty中的ObservableList时,遵循普通Property规则,只有调用get方法后,再次set才会触发Invadation event。
ListProperty<String> listProperty = new SimpleListProperty<>(
    FXCollections.observableArrayList());

listProperty.addListener(new InvalidationListener() {
  @Override
  public void invalidated(Observable observable) {
    System.out.println("ListProperty is invalid!");
  }
});

listProperty.add("one");
listProperty.add("two");
listProperty.get();
listProperty.remove("two");

System.out.println("........................");

listProperty.set(FXCollections.observableArrayList());
listProperty.set(FXCollections.observableArrayList());
listProperty.set(FXCollections.observableArrayList());
listProperty.get();
listProperty.set(FXCollections.observableArrayList());

// Output:
ListProperty is invalid!
ListProperty is invalid!
ListProperty is invalid!
........................
ListProperty is invalid!
ListProperty is invalid!

1.3 监听ChangeListener

  • 封装的ObservableList内容变化时,会触发ChangeLister。因为只是内容变化,本身封装的ObservableList对象没变,所以oldValue和newValue相等。
  • 重新set ListProperty的ObservableList时,和普通Property一样,oldValue是之前的ObservableList,newValue是新的ObservableList。
ListProperty<String> listProperty = new SimpleListProperty<>(
    FXCollections.observableArrayList());

listProperty.addListener(new ChangeListener<ObservableList<String>>() {
  @Override
  public void changed(ObservableValue<? extends ObservableList<String>> observable,
      ObservableList<String> oldValue, ObservableList<String> newValue) {
    System.out.println("oldValue == newValue: " + (oldValue == newValue));
  }
});

listProperty.add("one");
listProperty.add("remove");
System.out.println("***************************");

listProperty.set(FXCollections.observableArrayList());
listProperty.set(FXCollections.observableArrayList());

// Output:
oldValue == newValue: true
oldValue == newValue: true
***************************
oldValue == newValue: false
oldValue == newValue: false

1.4 监听ListChangeListener

ListProperty本身实现了ObservableList,又封装新的ObservableList对象。当add, remove, sort,set等改变ObservableList内容时,会触发ListChangeListener。这个跟普通ObservableList没有区别,下面只是一个小Exmaple。

ListProperty<String> listProperty = new SimpleListProperty<>(
    FXCollections.observableArrayList());

listProperty.addListener(new ListChangeListener<String>() {
  @Override
  public void onChanged(ListChangeListener.Change<? extends String> change) {
    while (change.next()) {
      if (change.wasAdded()) {
        System.out.println("Added size: " + change.getAddedSize());
      }
    }
  }
});

listProperty.add("one");
listProperty.addAll(Lists.newArrayList("two", "three"));

// Output:
Added size: 1
Added size: 2

1.5 ListProperty使用Binding

1.5.1 绑定ListProperty的sizeProperty和emptyProperty

  • size property是一个ReadOnlyIntegerProperty
  • empty property是一个ReadOnlyBooleanProperty

就是普通的Property,想怎么绑定就怎么绑定。

ListProperty<String> listProperty = new SimpleListProperty<>(
    FXCollections.observableArrayList());

StringExpression listDesc = new SimpleStringProperty("Size: ").concat(listProperty.sizeProperty())
    .concat(", Empty: ").concat(listProperty.emptyProperty());

System.out.println(listDesc.get());
listProperty.add("one");
System.out.println(listDesc.get());

// Output:
Size: 0, Empty: true
Size: 1, Empty: false

1.5.2 绑定封装的ObservableList的引用

当ListProperty调用bind绑定另一个ListProperty时,封装的ObservableList与绑定的ListProperty的ObservableList相同。

ObservableList<String> listPropRef1 = FXCollections.observableArrayList();
ListProperty<String> listProp1 = new SimpleListProperty<>(listPropRef1);

ObservableList<String> listPropRef2 = FXCollections.observableArrayList();
ListProperty<String> listProp2 = new SimpleListProperty<>(listPropRef2);

System.out.println("listProp1.get() == listProp2 :" + (listProp1.get() == listPropRef2));
listProp1.bind(listProp2);
System.out.println("listProp1.get() == listProp2 :" + (listProp1.get() == listPropRef2));

// Output:
listProp1.get() == listProp2 :false
listProp1.get() == listProp2 :true

可以使用bindBidrectional()进行双向绑定。

1.5.3 绑定封装的ObservableList的内容

当使用bindContent绑定时,只是内容相同,它们封装的ObservableList是不一样的。

注意:当进行内容绑定时,仍然可以修改ListProperty中的元素(add, remove, clear 等),但会导致元素内容和绑定的不一样,造成不一致。绑定后,最好不要再修改元素内容

ListProperty<String> listProp1 = new SimpleListProperty<>(FXCollections.observableArrayList());

ListProperty<String> listProp2 = new SimpleListProperty<>(FXCollections.observableArrayList());
listProp2.add("one");

System.out.println("listProp1: " + listProp1.getValue());
listProp1.bindContent(listProp2);
System.out.println("listProp1: " + listProp1.getValue());
listProp2.add("two");
System.out.println("listProp1: " + listProp1.getValue());

System.out.println("****************************");
// Change the content after bind content.
listProp1.add("three");
System.out.println("listProp1: " + listProp1.getValue());
System.out.println("listProp2: " + listProp2.getValue());

// Output:
listProp1: []
listProp1: [one]
listProp1: [one, two]
****************************
listProp1: [one, two, three]
listProp2: [one, two]

也可以使用bindBidrectional()双向绑定内容,任何一个ListProperty元素改变时,都进行同步。

ListProperty<String> listProp1 = new SimpleListProperty<>(FXCollections.observableArrayList());

ListProperty<String> listProp2 = new SimpleListProperty<>(FXCollections.observableArrayList());
listProp2.add("one");

listProp1.bindBidirectional(listProp2);
System.out.println("listProp1: " + listProp1.getValue());
listProp1.add("two");
System.out.println("listProp2: " + listProp2.getValue());

// Output:
listProp1: [one]
listProp2: [one, two]

1.5.4 绑定指定位置的元素

// Bind a fix position value.
ObjectBinding<E> valueAt(int index)

// Bind a dynamic position value.
ObjectBinding<E> valueAt(ObservableIntegerValue index)

当给定的index,找不到元素时,ObjectBinding中的内容时null。

ListProperty<String> listProp1 = new SimpleListProperty<>(FXCollections.observableArrayList());

IntegerProperty elementIndex = new SimpleIntegerProperty(0);
ObjectBinding<String> element = listProp1.valueAt(elementIndex);

System.out.println(element.get());
listProp1.addAll(Lists.newArrayList("one", "two", "three"));
System.out.println(element.get());

elementIndex.set(2);
System.out.println(element.get());

// Output:
null
one
three

2. SetProperty

SetProperty的使用和上面说的ListProperty是一样的,唯一不同的是:SetProperty是无序的,因此没有valueAt()方法来绑定指定元素。

创建SetProperty:

  • SimpleSetProperty()
  • SimpleSetProperty(ObservableSet<E> initialValue)
  • SimpleSetProperty(Object bean, String name)
  • SimpleSetProperty(Object bean, String name, ObservableSet<E> initialValue)

3. MapProperty

MapProperty的使用和上面说的ListProperty是一样的,唯一不同的是:MapProperty使用valueAt()方法,通过传入一个key,来绑定指定的value。

创建MapProperty:

  • SimpleMapProperty()
  • SimpleMapProperty(Object bean, String name)
  • SimpleMapProperty(Object bean, String name, ObservableMap<K,V> initialValue)
  • SimpleMapProperty(ObservableMap<K,V> initialValue)
  • 绑定指定key的value:

    MapProperty<String, Integer> mapProp =
        new SimpleMapProperty<>(FXCollections.observableHashMap());
    mapProp.put("one", 1);
    mapProp.put("two", 2);
    mapProp.put("three", 3);
    
    StringProperty mapKey = new SimpleStringProperty("one");
    ObjectBinding<Integer> mapValue = mapProp.valueAt(mapKey);
    
    System.out.println("key: " + mapKey.get() + ", value:" + mapValue.get());
    mapKey.set("three");
    System.out.println("key: " + mapKey.get() + ", value:" + mapValue.get());
    
    // Output:
    key: one, value:1
    key: three, value:3

    4. 总结

    Collection Property (ListProperty, SetProperty和MapProperty)与其它普通Property,如IntegerProperty是一样的,只不过其存储的元素是一个集合,可以监听集合元素改变的事件。