Creating a TreeViewItem tree which mirrors a ValueTree


#1

I’m trying to implement a TreeView component that mirrors a ValueTree structure. I got it working by following the ValueTreesDemo in the Demo runner. But in this example, I noticed that the entire tree is recalculated every time a TreeViewItem is clicked and every time something changes in the ValueTree (assuming that I am reading refreshSubItems() correctly). This seems a bit inefficient, especially for longer ValueTrees, so I thought that I might be able to improve on it by making the TreeViewItems automatically assemble itself in parallel with the ValueTree.

I’m trying to do this by registering the TreeViewItem as a listener to the ValueTree, but I can’t seem to get it working. I haven’t worked much with Listeners yet, so it’s possible that I’m getting something wrong at a fairly basic level. (In fact I’m new to all of this, but I have at least gotten ValueTrees and TreeViewItems up and running elsewhere).

Note: My TreeView doesn’t need to have drag and drop, text editing, or any other kind of feedback to the ValueTree.

The relevant part of my code is this:

  class MapItem : public TreeViewItem,
                  public ValueTree::Listener
  {
   public:
       MapItem(ValueTree)
       {
           mapItemTree.addListener(this);
       }

  private:
        ValueTree mapItemTree;

  // intention: whenever a new ValueTree child is added, add a corresponding MapItem
        void valueTreeChildAdded (ValueTree& parentTree, ValueTree& newChild) override
        {
           this->addSubItem(new MapItem(newChild), parentTree.indexOf(newChild));
        }
   };

I’ll similarly override other functions from the ValueTree::Listener class:

valueTreePropertyChanged(), valueTreeChildRemoved(), valueTreeChildOrderChanged(), valueTreeParentChanged(), treeChildrenChanged()

but I’m not focusing on them until I can get valueTreeChildAdded() working.

My code compiles, but the TreeViewItem tree doesn’t match the ValueTree–it only displays the first item (which is set elsewhere with setRootItem()).

Can anyone see what I’m missing?

My ValueTree is created in a constructor, and it struck me that MapItem, which is constructed after the ValueTree, might not be receiving these initial messages. Is this a possibility, or are Listeners clever enough to send messages only after their receivers have been constructed?


#2

I think I wrote this post poorly, and it’s probably also audacious to expect others to debug my code for me. Let me try rephrase the question–it’s quite simple, and I can’t seem to find the answer elsewhere.

If class B is registered as a Listener to class A, but class A is constructed before class B, is B’s Listener triggered by A’s initialization? (My tests suggest that it is not, but I might have gotten something wrong).

If B’s Listener does not receive A’s initialization, what is the best way of solving this problem so that B stays in sync with A?


#3

The behavior is normal, just adding the listener doesn’t cause the change notification calls to happen. The usual pattern is to add the listener and then change the involved broadcasting object if needed. (Or cause it in some other way to make the notification call.)


#4

Class B obviously can’t even add itself as a listener to listen to class A until it has been constructed.

If it needs to stay in sync maybe try passing information from class A to class B when it’s added as class B calls addListener() on class A.

For example…

class A : 
{
public:
    class Listener
    {
    public:
        virtual void valueChanged (int newValue) = 0;
    }

    void addListener (Listener& listenerToAdd)
    {
        listeners.add (&listenerToAdd); 
        listenerToAdd.valueChanged (value);
    }

    void removeListener (Listener& listenerToRemove)
    {
        listeners.remove (&listenerToRemove);
    }

    void setValue (int newValue)
    {
        value = newValue;
        listeners.call (&Listener::valueChanged, value);
    }

private:
    int value = 0;
    ListenerList<Listener> listeners;
}

Sorry I only read your second post TBH.


#5

edit : hmm, maybe I need to do some test/example code…


#6

Adding a listener only means to add oneself to a ListenerList, and each time something happens, all listeners in the list get a call.

Like you speculated, there is no exception, that a notification about adding something, that was already present, will not happen.

You can simply add all children in the constructor like that:

class MapItem : public TreeViewItem,
                public ValueTree::Listener
{
public:
    MapItem (ValueTree tree)  : mapItemTree (tree)
    {
        mapItemTree.addListener (this);
        for (auto& child : mapItemTree)
            addSubItem (new MapItem (child));
    }

    ~MapItem()
    {
        mapItemTree.removeListener (this);
    }

private:
    ValueTree mapItemTree;

    void valueTreeChildAdded (ValueTree& parentTree, ValueTree& newChild) override
    {
        if (parentTree == mapItemTree)
            addSubItem (new MapItem (newChild), parentTree.indexOf (newChild));
    }
};

Hope that helps


#7

This helps very much. Thank you all for your comments.