3

This answer provides a solution for an observable list that will send "list updated" notifications if properties of elements of the list change.

In my case, elements (a Element class) of such observable list are complex and I don't like to implement property for each member variable. Due to this, I added into the Element class a BooleanProperty that indicates change of the class.


Element Class

import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;

public class Element {
    // ...
    private ReadOnlyBooleanWrapper changeIndicatorWrapper;

    public Element() {
        //...
        changeIndicatorWrapper = new ReadOnlyBooleanWrapper(false);
    }

    public ReadOnlyBooleanProperty changeIndicatorProperty() {
        return changeIndicatorWrapper.getReadOnlyProperty();
    }

    public void someMethod() {
        // Some update
        changeIndicatorWrapper.set(!changeIndicatorWrapper.get());
    }
}

Observable List

ObservableList<Element> elementsObservableList = FXCollections.observableList(
    new ArrayList<>(),
    (Element element) -> new Observable[] { element.changeIndicatorProperty() }
);

elementsObservableList.addListener(new ListChangeListener<Element>() {
    @Override
    public void onChanged(Change<? extends Element> c) {
        System.out.println("CHANGE");
        while(c.next()) {
            if (c.wasUpdated()) {
                for (int i = c.getFrom(); i < c.getTo(); ++i)
                    System.out.println(elementsObservableList.get(i));
            }
        }
    }
});

My question is about this approach. Repeatedly set the changeIndicatorProperty to true not fire the change event. So, I need to reverse changeIndicatorProperty value changeIndicatorWrapper.set(!changeIndicatorWrapper.get()) each time. It is strange, isn't it?

Can I force programatically the update event?

Community
  • 1
  • 1
David
  • 527
  • 1
  • 8
  • 21

1 Answers1

5

It is strange, isn't it?

No this isn't surprising. For a change to be triggered a change needs to happen. If the BooleanProperty determines no change does happen and therefore the listeners are not notified of anything, this still satisfies the contract of Property.

Actually a Property isn't needed anyways. What is needed is a Observable that notifies it's observers. You could do this by using the following class and calling invalidate:

public class SimpleObservable implements Observable {

    private final List<InvalidationListener> listeners = new LinkedList<>();

    @Override
    public void addListener(InvalidationListener listener) {
        listeners.add(listener);
    }

    @Override
    public void removeListener(InvalidationListener listener) {
        listeners.remove(listener);
    }

    public void invalidate() {
        for (InvalidationListener listener : listeners) {
            try {
                listener.invalidated(this);
            } catch (RuntimeException ex) {
            }
        }
    }

}

Example:

public class Element {

    protected final SimpleObservable observable = new SimpleObservable();

    public Observable getObservable() {
        return observable;
    }

    public static <T extends Element> ObservableList<T> observableArrayList() {
        return FXCollections.observableArrayList(e -> new Observable[]{e.observable});
    }

    private void update() {
        observable.invalidate();
    }

    public static void main(String[] args) {
        ObservableList<Element> list = Element.observableArrayList();
        list.addListener((ListChangeListener.Change<? extends Element> c) -> {
            while (c.next()) {
                if (c.wasUpdated()) {
                    System.out.println("update: [" + c.getFrom() + ", " + c.getTo() + ")");
                }
            }
        });
        list.addAll(new Element(), new Element(), new Element());
        list.get(1).update();
    }

}
fabian
  • 80,457
  • 12
  • 86
  • 114