Recursively find first child component of a given class

To keep my code adaptable to class and layout changes I wanted to be able to pull out the first component of a given type. I came up with this recursive method:

    template<class ComponentClass>
    ComponentClass* getChildComponentOfType()
    {
        for (int i = 0; i < getNumChildComponents(); ++i)
        {
            ComponentClass* comp = dynamic_cast<ComponentClass*> (getChildComponent (i));
            if (comp != nullptr)
                return comp;
            else
                return getChildComponentOfType<ComponentClass>();
        }
        return nullptr;
    }

However I’m getting a bad access crash on the following line (juce_Array.h:240):

        const ScopedLockType lock (getLock());

This is after quite a lot of recursions/iterations. I’m wondering if this might be the wrong approach!?

Let’s play “spot the infinite loop”!

This is the kind of mistake that would surely be easy to spot if you step through it in your debugger…?

1 Like

i guess you are calling local getChildComponentOfType<ComponentClass>() inside your local template itself over and over, no ? it wont go recusively here, unless your ColponentClass is valid pointer, then you can invoke your recursion with it.
or im tripping ?

Yes I’ve realised my mistake here!! Had a bit of a “doh” moment… Need to pass in the parent component to actually drill down into the component tree. Will post my solution shortly!

i wish my doh’s would make me money, i would be rich as hell then

Working solution:

    template<class ComponentClass>
    static ComponentClass* getChildComponentOfClass(Component* parent)
    {
        for (int i = 0; i < parent->getNumChildComponents(); ++i)
        {
            Component* childComp = parent->getChildComponent(i);
            ComponentClass* compOfDesiredClass = dynamic_cast<ComponentClass*> (childComp);

            if (compOfDesiredClass != nullptr)
                return compOfDesiredClass;
            else
            {
                ComponentClass* nextChild = getChildComponentOfClass<ComponentClass> (childComp);
                if (nextChild != nullptr)
                    return nextChild;
            }
        }
        return nullptr;
    }

Or after a code-review… :slight_smile:

template<class ComponentClass>
static ComponentClass* getChildComponentOfClass(Component* parent)
{
    for (int i = 0; i < parent->getNumChildComponents(); ++i)
    {
        auto* childComp = parent->getChildComponent(i);

        if (auto c = dynamic_cast<ComponentClass*> (childComp))
            return c;

        if (auto c = getChildComponentOfClass<ComponentClass> (childComp))
            return c;
    }

    return nullptr;
}

I wish you’d all get on board with the “no-else-after-return” rule!

5 Likes

Thanks Jules, its been a while since I’ve had any code reviewed, appreciate that :slight_smile:

Any chance this method could make it into the Component class?

1 Like

+1 for embeding it into [Component]

I’m not super-keen on that. Unlike findParentComponentOfClass, it’s the kind of thing where you’d add it and then people would start wanting more control, e.g. an option to search backwards, or to return an array of all the matches, etc. And it works perfectly well as a free function like this one.

1 Like

Fair point, I can see how different use cases would warrant different variations.

I found this recursive child-finder method quite helpful, so my thanks to @adamski and @jules

I was trying to understand the nested child structure of a PropertyPanel, and wanted a clean printout of the structure, so inspired by the getChildComponentOfClass method here, I came up with this recursive printout method. It prints the level of recursion, indenting appropriately, and then prints the typeof the Component. If the Component name and/or ID are set, it includes them in the printout as well.

static void printAllChildren (Component* parent, int level = 1)
{
    if (level == 1)
    {
        DBG ("==========================================");
        DBG ("Recursive Printout of All Child Components");
        DBG ("==========================================");
    }
    
    for (int i = 0; i < parent->getNumChildComponents(); ++i)
    {
        auto* childComp = parent->getChildComponent (i);
        String text;
        
        for (int j = 0; j < level - 1; ++j)
            text += "  ";  // add indents
        
        text += "Level " + String (level) + " ";
        String type = typeid(*childComp).name();
        text += "(" + type + ") ";
        
        if (childComp->getName().isNotEmpty())
            text += "name = " + childComp->getName();
        
        if (childComp->getName().isNotEmpty() && childComp->getComponentID().isNotEmpty())
            text += ", ";
        
        if (childComp->getComponentID().isNotEmpty())
            text += "id = " + childComp->getComponentID();
        
        DBG (text);
        
        if (childComp->getNumChildComponents())
            printAllChildren (childComp, level + 1);
    }
}
1 Like

Not of a given class but by ComponentID, this function searches recursively among all children and through the entire genealogy:

// Generic function to get the pointer to a child component by its ComponentID
Component* getChildComponentByID(const String& ComponentID, Component* childToSearchFor = nullptr, int level = 0)
{
    if (childToSearchFor == nullptr)
        childToSearchFor = this;

    for (auto cmp : childToSearchFor->getChildren())
        if (cmp->getComponentID() == ComponentID)
            return cmp;

    int next = level + 1;
    if (childToSearchFor->getChildren().size() > level)
        return getChildComponentByID(ComponentID, childToSearchFor->getChildren()[level], next);

    return nullptr;
}

Could be easily modified to search by component type.

1 Like

I’m confused as to what this code does. The getChildren() function returns an array, not a single Component*, so how can you call getComponentID() on the return value of that function? Should that have been getChildren[level]?

It’s a range-based for loop, introduced with C++11, you can use it to iterate through juce::Array, std::vector or normal arrays:

int numbers[5] = { 1, 10, 100, 1000, 10000 };

for (auto &number : numbers)
    cout << number << "\n";
1 Like

Ah, got it. Thanks!