Replacing child nodes in ValueTrees

I need to update some properties in my tree without triggering listeners. since they’re buried deep in the tree and I have no way to disable listeners, I’m resorting to the next best thing, which is replacing the whole node. Unfortunately, that just ain’t behaving as expected, because ValueTree::getChild() returns by copy, not by reference.

    {
        ValueTree hiddenRoot("HiddenRoot");
        hiddenRoot.appendChild( ValueTree("TaskQueue"), nullptr );
        
        hiddenRoot.getChild(0).appendChild( ValueTree("Task1"), nullptr );
        DBG( hiddenRoot.toXmlString());
        jassertfalse;
        
        hiddenRoot.getChild(0).getChild(0) = ValueTree("Task2" );
        
        DBG( hiddenRoot.toXmlString() );
        jassertfalse;
    }

prints out this:

<?xml version="1.0" encoding="UTF-8"?>

<HiddenRoot>
  <TaskQueue>
    <Task1/>
  </TaskQueue>
</HiddenRoot>

JUCE Assertion failure in Main.cpp:79
<?xml version="1.0" encoding="UTF-8"?>

<HiddenRoot>
  <TaskQueue>
    <Task1/> <-- hoping for Task2 here />
  </TaskQueue>
</HiddenRoot>

I know that getChild(int index) returns by value (it returns a copy).
the operator= being called changes the internal sharedObject.

if the first returned value of getChild(0) returns a ValueTree wrapper around the <TaskQueue/> tree, why isn’t <TaskQueue> updated when I replace a child of it?

is there an easy way to replace this node without converting the whole thing to XML and replacing it via the XML modifier functions and then converting it back to ValueTree?

That’s expected behaviour, unfortunately. I did consider not giving ValueTree an operator= to avoid this confusion, but that would make it tricky to use.

Probably what you want to use is ValueTree::copyPropertiesAndChildrenFrom(). I’m a little surprised that we never added a method that would replace a child, but I’m not 100% sure whether that’s something that’d play nicely with undo/redo.

I ended up doing a crazy solution of cloning the tree I need to swap children on, doing the child node swap, and then replacing the tree with the cloned tree. The reason was because ValueTree::copyPropertiesAndChildrenFrom() would have fired a listener callback for every thing, and doing the tree clone/swap/replace ended up just calling valueTreeRedirected for the tree root. Since these valueTrees are being used in a TreeView anonymously, there is no means of removing listeners on specific nodes in the tree.

it’s highly un-optimal, and I have no idea what’ll happen when my tree is massive, but it gives me all the behavior with minimal callbacks for tree changes that I needed for this little project:

I will probably abandon the ValueTree as my data structure because the whole callback system triggers way too many callbacks when a deeply nested child is changed.

Do you have any alternatives in mind? Not that I don’t like ValueTrees, but I’d like to get to know some other ideas.

the XmlElement because it doesn’t have a listener system built-in but it would be pretty easy to write a wrapper class that adds that functionality.
another option is just a simple struct like this:

struct Item
{
    String name;
    Time created, completed;
    bool completed;
    OwnedArray<Item> childNodes;

    Item* getParent();
    ValueTree saveToValueTree(); //for serializing to/from disk easily
    void loadFromValueTree();
};

somethin’ simple like that…
The thing in the gif above was built from the ValueTreesDemo so it would have the drag/drop functionality and reordering.

Ah ok, I thought you plan on using a data model library.

for this little nested checkbox thing? Using a library seems totally overkill…