ChangeListener not giving correct old value

时间:2015-07-28 22:53:25

标签: java javafx-8

Here is the code:

package sample;

import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

/**
 * Created by IDEA on 29/07/15.
 */
public class ListPropertyTest {
    public static void main(String[] args) {
        ListProperty<String> lp =
                new SimpleListProperty<>(FXCollections.observableArrayList());
        lp.addListener(new ListChangeListener<String>() {
            @Override
            public void onChanged(Change<? extends String> c) {
                while(c.next()) {
                    String action = c.wasPermutated() ? "perm"
                            : c.wasUpdated() ? "upd"
                            : c.wasRemoved() ? "rem"
                            : c.wasAdded() ? "add" : "";
                    System.out.println("Action: " + action);
                    System.out.println("Removed: " + c.getRemoved());
                    System.out.println("Added: " + c.getAddedSubList());
                }
            }
        });

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

        lp.addListener(new InvalidationListener() {
            @Override
            public void invalidated(Observable observable) {
                System.out.println("List invalid");
            }
        });

        System.out.println("Add =========");
        lp.addAll("one", "two");
        System.out.println("Set =========");
        lp.set(FXCollections.observableArrayList("two", "three"));
        System.out.println("Remove ============");
        lp.remove("two");
    }
}

Result:

Add =========
List invalid
List changed.
Old: [one, two]
New: [one, two]
Action: add
Removed: []
Added: [one, two]
Set =========
List invalid
List changed.
Old: [one, two]
New: [two, three]
Action: rem
Removed: [one, two]
Added: [two, three]
Remove ============
List invalid
List changed.
Old: [three]
New: [three]
Action: rem
Removed: [two]
Added: []

As you can see, the change listener behaved correctly only in the "Set" part.

1 个答案:

答案 0 :(得分:2)

Most of this is intended behavior.

A function(error) { if(error.code == 0){ // unknown error } else if(error.code == 1) { // permission denied } else if(error.code == 2) { // position unavailable } else if(error.code == 3) { // timeout } console.log(error.message); }, is both an ListProperty and an ObjectProperty<ObservableList>.

Being an ObservableList means it wraps (contains a reference to) an ObjectProperty<ObservableList> and has ObservableList (or setValue(ObservableList)) and set(...) methods. As an getValue(), you can register a ObjectProperty<ObservableList> with it. If the reference to the wrapped list changes, by someone calling ChangeListener<ObservableList>, the setValue(ObservableList) method gets called on any registered ChangeListener<ObservableList>.changed(...), passing in a reference to the ChangeListener itself, the old ListProperty reference, and the new ObservableList reference. You see this behavior when you call ObservableList in your code.

When you simply change the content of the list, however, the wrapped list is still the same physical object. So the "old list" and the "new list" are one and the same. It is a bug that the change listener fires notifications in this case; hat-tip to @kleopatra for pointing this out.

A set also implements ListProperty, by delegating the ObservableList method calls to the wrapped list instance. This means if the content of the list change, any ObservableLists registered with the ListChangeListener get notified. Again, note in this case that there is only one list object; it's just that its contents have been modified.

You can get details about changes that occur to an ListProperty via the ObservableList object passed to the Change method, as you observe in the output from your onChanged(...) method. Note that onChanged and c.getFrom() will give you the indexes of the items that have changed as well.

I almost never have a need for a c.getTo(): in the vast majority of use cases it is enough for me to just use a plain ListProperty implementation. Controls that are based on lists of objects (such as ObservableList and ListView) use them, so you can call TableView to pass in an existing setItems(...) if you need, or (probably more usually) you can just call ObservableList to retrieve the current list and modify it. As you can see from the explanation above, the getItems() allows these controls to observe either kind of change. But it is likely that in your own code, you will use ListProperty directly far more often than you use a ObservableList.