ValueTree callback when root node changed

I’m trying to get a callback (of any kind) in a ValueTree::Listener that listens to a child when I load a preset into a ValueTree.

So far I managed to get a “Tree Redirected” callback when I listen to the root node, but I can’t get any kind of callback when I have listener that listens to to some nested child in this tree.

As a dummy example, I created this debugging class:

struct Listener: public ValueTree::Listener
{
    void valueTreePropertyChanged(ValueTree&, const Identifier&) override
    {
        DBG ("Property Changed");
    }
    //Implements all other callbacks this way... 
};

Later, I want to attach this listener to some child in a tree:
//Creates a tree with one child, could be loaded from XML, etc.

ValueTree createTree()
{
    ValueTree tree ("Tree");
    tree.appendChild(ValueTree("Child"), nullptr);

    return tree;
}

int main ()
{
    //Grabs the default tree:
    auto tree = createTree();

    //Adds Listener to the child
    Listener l;
    tree.getChild(0).addListener(&l);

    //Triggers no callbacks, unless I listen to the parent node: 
    tree = createTree();

    return 0;
}

Any ideas?

When you call it a second time, your replace the old value-tree.

When you use a “ValueTree”-Object its actually a reference to a value-tree not the data itself. In JUCE you can only add listeners to the references.

So add the listener to the ValueTree in the main scope, and check in the callback

 void valueTreePropertyChanged(ValueTree& v, const Identifier&) override
    {
        if (v==tree.getChild(0))
        {
          // do something
         };
    }

Unfortunately, both doing that or copyPropertiesAndChildren from does not trigger any callback (Not just the property change callback, any callback) at the moment of change which is what I’m after.

I can handle changing the listener to the new tree, if I knew that some change to the parent happened…

Also: while listening to the root tree works - I’m trying to create classes that aren’t aware of the root tree, and only know about a child node they’re associated with.

I’m trying to create classes that aren’t aware of the root tree, and only know about a child node they’re associated with.

once the parent is changed, the classes which reference to the child have a reference to the outdated value tree. I know what you trying to do (i tried a similar approach).

Better keep your ValueTree-Base (Model) inside a wrapper class (Controller), and access the data through your controller.

You may want to look inside the projucer source-code how it could be done.

Thanks! I do have a wrapper class. I just simplified for the example here to show the mechanics…

The thing is - I understand the child I’m listening to isn’t valid for future updates, I’m just looking for a way to know it has been invalidated without passing the root. something like parentChanged or treeRedirected - none of which get to the child.

I think the problem is actually a different one:
The ValueTree is a wrapper around a SharedObject, as pointed out previously.
But the Listeners are not added to the SharedObject, instead they are kept in the wrapper object.

If you do this:

    //Adds Listener to the child
    Listener l;
    tree.getChild(0).addListener(&l);

getChild (0) returns a new ValueTree wrapper with a reference to the SharedObject in the tree.
Now the listener is added to the wrapper, which goes immediately out of scope. The listener is lost.

Can you try to keep the leaf you are listening to as member? I haven’t tried, but I think if it works at all, then by keeping the ValueTree alive, not only the SharedObject:

int main ()
{
    //Grabs the default tree:
    auto tree = createTree();

    //Adds Listener to the child
    Listener l;
    leaf = tree.getChild(0);
    leaf.addListener(&l);

    //Triggers no callbacks, unless I listen to the parent node: 
    tree = createTree();

    return 0;
}
private:
    ValueTree leaf;

@daniel Thanks!
Yes, you were right about the scoping issues - fixing it triggers a valueTreeParentChanged() callback which helps!

Funny as that sounds - my actual project did not have the scoping problem, I just didn’t think to respond to that particular callback. :slight_smile:

@chkn looking at the Projucer code now, looks like what they did was manually going over each property in the tree when parsing XML, to update all the existing classes, instead of using the “=” operator on preset load.

bool AppearanceSettings::readFromXML (const XmlElement& xml)
{
    if (xml.hasTagName (settings.getType().toString()))
    {
        const ValueTree newSettings (ValueTree::fromXml (xml));

        // we'll manually copy across the new properties to the existing tree so that
        // any open editors will be kept up to date..
        settings.copyPropertiesFrom (newSettings, nullptr);

        for (int i = settings.getNumChildren(); --i >= 0;)
        {
            ValueTree c (settings.getChild (i));

            const ValueTree newValue (newSettings.getChildWithProperty (Ids::name, c.getProperty (Ids::name)));

            if (newValue.isValid())
                c.copyPropertiesFrom (newValue, nullptr);
        }

        return true;
    }

    return false;
}

Well i meant for the general project settings. Then you can just the internal ValueTree <-> XML parser.
However, there are lots of ways doing it, each method has its cons and pros.

Just read quickly over this thread, but if I get it right, I had the same problems with a value tree based application some month ago. I came to the conclusion that the best way to do a complete ValueTree exchange is to not really exchange it but to iterate over the whole tree, exchanging node by node. This way, all listeners attached get informed. This is the function that I use, in case it’s helpful for your use-case:

void syncValueTreeNotifyListeners (const juce::ValueTree& source, juce::ValueTree& destination)
{
    const int numProperties = source.getNumProperties();
    for (int i = 0; i < numProperties; ++i)
    {
        auto propertyName = source.getPropertyName (i);

        if (destination.hasProperty (propertyName))
            destination.setProperty (propertyName, source.getProperty (propertyName), nullptr);
    }

    for (const auto& child : source)
    {
        auto childType = child.getType();
        auto childInDestination = destination.getChildWithName (childType);

        if (childInDestination.isValid())
            syncValueTreeNotifyListeners (child, childInDestination);
    }
}

@PluginPenguin yes, thank you! I just wrote an almost identical function just 10 minutes ago. :slight_smile:

1 Like

Note that this code assumes unique leafs in each level. Otherwise destination.getChildWithName (childType) will allways return with the first occurrence…