How do you update ValueTree parent node properties from children?

How does everyone update parent node properties from Child nodes in ValueTrees?

I just cannot find a way to do this without violating the rule:

Don't set ValueTree properties inside ValueTree::Listener callbacks

The more I go down this road, the more I think ValueTree is the wrong tool for the job because you can’t block notifications (who at the JUCE headquarters decided that was a smart idea…)

I’ve done some hacking on the ValueTreesDemo project and created this little app where checkboxes follow a ‘completed’ property on each node:
update%20parent

but I had to do some hackish things to get the parents to update from the children, thanks to some valuable input from folks like @daniel @eyalamir @TonyAtHarrison and @dave96. Mainly, if a node has children, don’t make the toggle button follow the ‘completed’ property in the value tree, but instead compute it from all of the sub-children.

1 Like

so, just adding some more info/testing:

if I ditch the whole ‘recreate the tree on changes’, and make the toggle buttons follow the ‘completed’ property in the tree, and modify my propertyChanged callback to this:

void TaskQueueItemComponent::valueTreePropertyChanged(ValueTree &treeThatChanged,
                                                      const Identifier &property)
{
    /*
     when is this called?
     
     whenever a child node changes
     whenever a property on our tree changes
     whenever a parent changes
     */
    DBG( "" );
    DBG("this tree: [" << tree[Identifiers::name].toString() << "]");
    DBG("tree that changed: [" << treeThatChanged[Identifiers::name].toString() << "]");
    DBG("property that changed: [" << property << "]");
    if( treeThatChanged.isAChildOf(tree) )
    {
        DBG( "  change occurred on a child" );
        /*
         update our completed/child count and update our toggle button
         */
        updateLabelAndToggleButton();
        if( property == Identifiers::completed )
        {
            DBG( "      updating our property based on our children" );
            bool childrenVal = isCompleted(tree);
            bool ourVal = tree.getProperty(property);
            if( ourVal != childrenVal )
            {
                tree.setProperty(property,
                                 childrenVal,
                                 nullptr);
            }
        }
    }
    else if( tree.isAChildOf(treeThatChanged) )
    {
        DBG( "  change occurred on a parent" );
        if( property == Identifiers::completed )
        {
            bool ourValue = tree.getProperty(property);
            bool parentValue = treeThatChanged.getProperty(property);
            if( ourValue != parentValue )
            {
                DBG( "      updating our property" );
                tree.setProperty(Identifiers::completed,
                                 parentValue,
                                 nullptr);
            }
        }
    }
    else if( treeThatChanged == tree)
    {
        DBG( "  change occurred on our tree" );
    }
}

clicking this checkbox for this tree:

generates this string of callbacks:

this tree: [checkbox colors when selected?]
tree that changed: [checkbox colors when selected?]
property that changed: [completed]
  change occurred on our tree

this tree: [checkboxes]
tree that changed: [checkbox colors when selected?]
property that changed: [completed]
  change occurred on a child
      updating our property based on our children

this tree: [checkboxes]
tree that changed: [checkboxes]
property that changed: [completed]
  change occurred on our tree

this tree: [graphics]
tree that changed: [checkboxes]
property that changed: [completed]
  change occurred on a child
      updating our property based on our children

this tree: [graphics]
tree that changed: [graphics]
property that changed: [completed]
  change occurred on our tree

this tree: [TaskQueueTODO]
tree that changed: [graphics]
property that changed: [completed]
  change occurred on a child
      updating our property based on our children

this tree: [TaskQueueTODO]
tree that changed: [TaskQueueTODO]
property that changed: [completed]
  change occurred on our tree

this tree: [HiddenRoot]
tree that changed: [TaskQueueTODO]
property that changed: [completed]
  change occurred on a child
      updating our property based on our children

this tree: [HiddenRoot]
tree that changed: [HiddenRoot]
property that changed: [completed]
  change occurred on our tree
onValueTreePropertyChanged(HiddenRoot, completed)
onValueTreePropertyChanged(TaskQueue, completed)

this tree: [HiddenRoot]
tree that changed: [graphics]
property that changed: [completed]
  change occurred on a child
      updating our property based on our children
onValueTreePropertyChanged(Task, completed)

this tree: [TaskQueueTODO]
tree that changed: [checkboxes]
property that changed: [completed]
  change occurred on a child
      updating our property based on our children

this tree: [HiddenRoot]
tree that changed: [checkboxes]
property that changed: [completed]
  change occurred on a child
      updating our property based on our children
onValueTreePropertyChanged(Task, completed)

this tree: [graphics]
tree that changed: [checkbox colors when selected?]
property that changed: [completed]
  change occurred on a child
      updating our property based on our children

this tree: [TaskQueueTODO]
tree that changed: [checkbox colors when selected?]
property that changed: [completed]
  change occurred on a child
      updating our property based on our children

this tree: [HiddenRoot]
tree that changed: [checkbox colors when selected?]
property that changed: [completed]
  change occurred on a child
      updating our property based on our children
onValueTreePropertyChanged(Task, completed)

That just seems like a ridiculously high amount of extra callback happening for this. I can’t imagine what’ll happen if my tree becomes really complex.

Any ideas on how to lighten the callback load?

Whenever anyone asks questions like this I always say, trace through a single path drawing operation in the graphics code. You’ll see how much work is done hundreds of times a second.

Having a few callbacks when the user clicks a button on the screen is completely negligible. Also, how nested are your trees likely to get for this to be a genuine scale problem?


I’ve got to say though, I’m still not quite following the problem here. Why do you need to update properties from a inside a property changed callback? I’m not really following all of this HiddenRoot stuff…

If I was writing this, I’d make it so that the leaf nodes have a flag, but group nodes do not. Then when the user clicks a leaf node, you simply update that flag. If the user clicks a group node, you update all the flags in all the leaf nodes inside it. Then when you display the tick-box for a leaf node, you simply show the state of the flag. When you display the tick-box for a group node, you show the state based on whether all its sub-items are ticked or not. Any changes to sub-nodes cause the parent GUI comps to refresh.

If you try to give the group nodes their own copy of a flag which both modifies and depends on the state of multiple other flags, then it’s no wonder you’re ending up with recursive problems, and could also end up with their states getting out of sync.

Plus, if you do it like I’m suggesting, you could also show the tickboxes as tri-state to indicate that some of their child nodes are ticked.

hey @dave96 the HiddenRoot is the hidden root on my TreeView. you know that setting, “TreeView::hideTreeRoot(true)” or whatever it is? that’s all it is. it’s the root node of my ValueTree and TreeViewItem.

@jules the issue I kept running into was handling what to do when a node changes from being a leaf, to having children.

Once I figured out that tickboxes on nodes with children should not referTo the completed property on their respective ValueTree but instead compute how many of their children are completed out of all of their children to set the toggle state, things thinned out a bit.

I ended up ditching the whole system, as per this thread:

Seems like a lot more work for less functionality to me…

well, i will gladly let you check out the project if you have some time and want to show me the way :stuck_out_tongue:

Well you’re modifying the tree at that point anyway, so adding or removing the flag property is just another modification.

right, which triggers more callbacks.

I’ll try to throw a PIP together at some point for you guys to check out and school me on the correct way to get this happening without tons of ValueTree::Listener recursion.

We both just explained how you should approach it - I’m afraid I don’t have time to go over it in any more detail.