Search code examples
javajavafxobservablelist

How to retrieve the changed item in an ObservableList via ListChangeListener


There is ListChangeListener.Change.wasUpdated() which is used to detect changes within elements for ObservableLists created via ObservableList.observableArrayList(Callback<E,Observable[]> extractor).

How do I retrieve the exact item(s) that caused the change to be triggered?

Edit

Maybe the question isn't clear enough, I'll give an example.

class Foo {
    private StringProperty name = new SimpleStringProperty();
    public final StringProperty nameProperty() { return name; }
    public final String getName() { return name.get(); }
    public final void setName(String n) { name.set(n); }
    public Foo(String fooName) { setName(fooName); }
}

// Creates an ObservableList with an extractor
ObservableList<Foo> fooList = FXCollections.observableArrayList(foo -> new Observable[] { foo.nameProperty() });
Foo fooA = new Foo("Hello");
Foo fooB = new Foo("Kitty");
fooList.add(fooA);
fooList.add(fooB);

fooList.addListener(new ListChangeListener<Foo>() {
     public void onChanged(Change<Foo> c) {
         while (c.next()) {
            if (c.wasUpdated()) {
                // One or more of the elements in list has/have an internal change, but I have no idea which element(s)!
            }
        }
    }
});

fooB.setName("Mickey");

fooB.setName() will trigger a change in the ListChangeListener, which the wasUpdated() condition would return true. However, I have no way to know that it is fooB which has changed within the listener.

This may seem trivial, but I have a map application where the list stores the things which the map has to render. When one of the item changes its position (i.e. lat/long), I need to re-plot on the map. If I have no idea which item has changed location, I'll have to redraw everything that I already have.


Solution

  • You can get the index(es) of the item(s) that were changed, which gives you some useful information:

    import javafx.beans.Observable;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    import javafx.collections.FXCollections;
    import javafx.collections.ListChangeListener.Change;
    import javafx.collections.ObservableList;
    
    public class ListUpdateTest {
    
        public static void main(String[] args) {
    
            ObservableList<Foo> fooList = FXCollections.observableArrayList(foo -> new Observable[] { foo.nameProperty() });
            Foo fooA = new Foo("Hello");
            Foo fooB = new Foo("Kitty");
            fooList.add(fooA);
            fooList.add(fooB);
    
            fooList.addListener((Change<? extends Foo> c) -> {
                 while (c.next()) {
                    if (c.wasUpdated()) {
                        int start = c.getFrom() ;
                        int end = c.getTo() ;
                        for (int i = start ; i < end ; i++) {
                            System.out.println("Element at position "+i+" was updated to: " +c.getList().get(i).getName() );
                        }
                    }
                }
            });
    
            fooB.setName("Mickey");
        }
    
        public static class Foo {
            private StringProperty name = new SimpleStringProperty();
            public final StringProperty nameProperty() { return name; }
            public final String getName() { return name.get(); }
            public final void setName(String n) { name.set(n); }
            public Foo(String fooName) { setName(fooName); }
        }
    }
    

    Note that you can't determine from the list change event the actual properties that changed in those list elements (so, if your extractor pointed to two or more properties, there's no way to find which of those properties changed), and there is no way to get the previous value. This may be enough for your use case, though.