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.


#8

I’ve succeeded in implementing my TreeView component which mirrors a ValueTree structure, thanks to the generous help of people on this forum.

I now have some specific questions about best approaches to ValueTree Listeners. There are two features about the ValueTreesDemo.h code which appear to me to be shortcuts, and I want to know whether it is worth my time to re-implement them.

1:
In the example code, each TreeViewItem listens to a single ValueTree. But this generates a lot of unnecessary traffic, as listeners register changes at any depth. A TreeViewItem close to the root is going to receive and filter out exponentially more changes that are irrelevant to it. Granted, each one is just a boolean comparison, but in a structure with thousands of nodes and dozens of layers, this might add up.

A more efficient way to approach this might be to have only one ValueTree Listener outside of the tree structure, listening to the root ValueTree and somehow relaying each change to the correct TreeViewItem.

2:
If a change registers as positive, the TreeViewItem redraws its structure. This also seems inefficient, as minor changes could be implemented with moves or edits, without having to recalculate the whole thing.

A more efficient way might be to use the variables provided in the prototypes in the Listener class (valueTreeChildAdded(), valueTreePropertyChanged(), etc.) to implement changes on a case-by-case basis.

In both cases, the example code is very simple and effective, at the cost of some extra noise. In both cases, the alternative methods that I have suggested seem to be more efficient, but also quite error- and headache-prone.

My question is: Is the sample code written like this because it is sample code, and any serious program should define more efficient methods? Or is the noise it generates inconsequential? Is this the type of shortcut that you would welcome in your code?

The answer might be quite subjective here, but your opinion will be quite valuable, as I don’t have an intuition for these things yet and am learning as I go.


#9

I think that is not 100% correct. The Listeners detect only changes of the tree or it’s subtrees:

This method is called when a property of this tree (or of one of its sub-trees) is changed.

That means, no events for the parents are sent. In case of a leaf it would only send one event.

This sounds intriguing in the first place, and may be appropriate, if there is special knowledge available for the tree, that makes certain structures inside the tree to entities. But for a generic solution, you will most likely end up replicating the tree outside the actual tree, adding maintenance overhead and defeating the purpose.
Like you said yourself:

The current implementation resembles a visitor pattern for the tree and it’s sub trees, and is at least as effective as any other lookup method.

IMHO this would fall under “premature optimisation”.

That is indeed true, the destruction and recreating of sub nodes might be avoided, if each node had the means to update itself.
My gut feeling is that there is little win for much effort, but you might find a nice solution, that avoids this recreating.


#10

Check out dave96’s ValueTree video and his ValueTreeObject class


#11

I am doing some pretty complicated and highly editable custom TreeViewItems with many ValueTree attachment and dependencies. To my surprise, treeHasChanged() has proved to be very stable.