Chris
Chris

Reputation: 366

Custom Observable List implementation, notifying consumers of changes

I have created a custom ObservableList implementation for a list of TreeItems. My custom implementation can listen to various notifications from inside my app (using OSGi EventAdmin), and update itself accordingly. I then expect its consumer (a TreeView widget) to be updated with the changes to the list. However, I can't see how to notify the consumer.

In the ObservableList subclass I am implementing addListener(ListChangeListener), which I would expect to get called when the object is added to the widget. However it is never called; I have no listeners thus no apparent way to notify anyone when the list changes. I must be missing something.

Here is a snippet from my TreeItem implementation, which returns an instance of my ObservableList in response to a getChildren call:

    @Override
    public ObservableList<TreeItem<DataObject>> getChildren() {
        if (needChildren) {
            needChildren = false;
            children = new MyObservableList();
        }
        return children;
    }

Here is an abridged version of my custom ObservableList implementation, which simply wraps an FXCollections.observableArrayList and adds an OSGi event handler. I listen to changes on the internal list so that I can pass those changes on to my listeners.

    public class MyObservableList implements ObservableList<TreeItem<DataObject>>, EventHandler {
    private List<ListChangeListener<? super TreeItem<DataObject>>> changeListeners = new ArrayList<>();
    private List<InvalidationListener> invalidationListeners = new ArrayList<>();
    private ObservableList<TreeItem<DataObject>> theList;
    private int size;

    public MyObservableList() {
        theList = FXCollections.observableArrayList();

        theList.addListener(new ListChangeListener<TreeItem<DataObject>>() {
            @Override
            public void onChanged(Change<? extends TreeItem<DataObject>> change) {
                fireValueChangedEvent(change);
            }
        });
    }

    @Override
    public int size() {
        return theList.size();
    }

    @Override
    public boolean isEmpty() {
        return (size == 0);
    }

    @Override
    public boolean contains(Object o) {
        return theList.contains(o);
    }

    @Override
    public Iterator iterator() {
        return theList.iterator();
    }

    @Override
    public boolean remove(Object o) {
        return theList.remove(o);
    }

    @Override
    public boolean addAll(Collection c) {
        return theList.addAll(c);
    }

    @Override
    public boolean addAll(int index, Collection c) {
        return theList.addAll(index, c);
    }


    @Override
    public void clear() {
        theList.clear();
    }

    @Override
    public TreeItem<DataObject> get(int index) {
        return theList.get(index);
    }

    @Override
    public int indexOf(Object o) {
        return theList.indexOf(o);
    }

    @Override
    public int lastIndexOf(Object o) {
        return theList.lastIndexOf(o);
    }

    @Override
    public ListIterator listIterator() {
        return theList.listIterator();
    }

    @Override
    public ListIterator listIterator(int index) {
        return theList.listIterator(index);
    }


    @Override
    public List<TreeItem<DataObject>> subList(int fromIndex, int toIndex) {
        return theList.subList(fromIndex, toIndex);
    }

    @Override
    public Object[] toArray(Object[] a) {
        return theList.toArray(a);
    }

    @Override
    public void addListener(ListChangeListener<? super TreeItem<DataObject>> listChangeListener) {
        changeListeners.add(listChangeListener);
    }

    @Override
    public void removeListener(ListChangeListener<? super TreeItem<DataObject>> listChangeListener) {
        changeListeners.remove(listChangeListener);
    }

    @Override
    public boolean addAll(TreeItem<DataObject>... treeItems) {
        return theList.addAll(treeItems);
    }

    @Override
    public boolean setAll(TreeItem<DataObject>... treeItems) {
        return theList.setAll(treeItems);
    }

    @Override
    public boolean setAll(Collection<? extends TreeItem<DataObject>> treeItems) {
        return theList.setAll(treeItems);
    }

    @Override
    public boolean removeAll(TreeItem<DataObject>... treeItems) {
        return theList.removeAll(treeItems);
    }

    @Override
    public boolean retainAll(TreeItem<DataObject>... treeItems) {
        return theList.retainAll(treeItems);
    }

    @Override
    public void remove(int i, int i2) {
        theList.remove(i, i2);
    }

    @Override
    public Object[] toArray() {
        return theList.toArray();
    }

    @Override
    public boolean add(TreeItem<DataObject> dataObjectTreeItem) {
        return theList.add(dataObjectTreeItem);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return theList.containsAll(c);
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return theList.removeAll(c);
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return theList.retainAll(c);
    }

    @Override
    public TreeItem<DataObject> set(int index, TreeItem<DataObject> element) {
        return theList.set(index, element);
    }

    @Override
    public void add(int index, TreeItem<DataObject> element) {
        theList.add(index, element);
    }

    @Override
    public TreeItem<DataObject> remove(int index) {
        return theList.remove(index);
    }

    @Override
    public void addListener(InvalidationListener invalidationListener) {
        invalidationListeners.add(invalidationListener);
    }

    @Override
    public void removeListener(InvalidationListener invalidationListener) {
        invalidationListeners.remove(invalidationListener);
    }

    private void fireValueChangedEvent(ListChangeListener.Change<? extends TreeItem<DataObject>> change) {
        for (ListChangeListener<? super TreeItem<DataObject>> listener : changeListeners) {
            listener.onChanged(change);
        }
    }

    @Override
    public void handleEvent(Event event) {
        // Here I add or remove TreeItem<DataObject> instances to the list based on event.
        //
        // At this point, onChanged() gets called above in my listener, but my changeListeners list is empty. There is
        // no one to pass the Change on to.
    }
}

Thanks for any help.

Upvotes: 1

Views: 1256

Answers (1)

Chris
Chris

Reputation: 366

I figured out what's going on here after looking through the JavaFX source.

It turns out that the listening on the ObservableList of children is all set up in the TreeItem itself, not the TreeView as I had somehow assumed. This means that any subclass of TreeView that overrides getChildren() must call super.getChildren() and add its children to the resulting list. This means that using a custom ObservableList implementation is not possible as TreeItem is hardcoded to use FXCollections.observableArrayList() to create the list.

I am taking a different approach to this now where I call super.getChildren(), add my children, and then instantiate another object that holds a reference to that list and does all of my app's event handling business, operating on the list as needed. So my getChildren() method looks something like this now.

private MyEventHandler eventHandler;
private ObservableList<TreeItem<DataObject>> children;

@Override
public ObservableList<TreeItem<DataObject>> getChildren() {
    if (needChildren) {
        needChildren = false;
        children = super.getChildren();
        eventHandler = new MyEventHandler(children); // handles app events
    }
    return children;
}

Upvotes: 1

Related Questions