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);
    }
}
2 Likes

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!

Sorry to wake this up again, but I just tried your recursive child printout, seemed interesting, and it does work quite nicely.

But I’m curious about this line that gets some sort of class name:

        String type = typeid(*childComp).name();

What exactly is this doing? I can’t seem to really figure it out or debug into it. It produces output like this:

       Level 5 (N10WaveEditor11WaveOscModeE) name = Osc Mode SubView, id = waom
          Level 6 (N4juce5LabelE) name = Device Type Label
          Level 6 (14ComboBoxIncDec) name = ComboBoxIncDec, id = wa38
            Level 7 (N4juce8ComboBoxE) name = Device Type Menu, id = wa38
              Level 8 (N4juce5LabelE) name = cbox label
            Level 7 (6IncDec) name = incDec
              Level 8 (12IncDecButton) name = incDecButton
              Level 8 (12IncDecButton) name = incDecButton
          Level 6 (N4juce5LabelE) name = Osc Mode Label
          Level 6 (14ComboBoxIncDec) name = ComboBoxIncDec, id = wa18
            Level 7 (N4juce8ComboBoxE) name = Osc Mode Menu, id = wa18
              Level 8 (N4juce5LabelE) name = cbox label
            Level 7 (6IncDec) name = incDec
              Level 8 (12IncDecButton) name = incDecButton
              Level 8 (12IncDecButton) name = incDecButton

Where does something like “N4juce8ComboBoxE” come from and why do all of those with ‘N’ at the front have an ‘E’ on the end? Just trying to understand it…

https://en.cppreference.com/w/cpp/types/type_info/name

Returns an implementation defined null-terminated character string containing the name of the type. No guarantees are given; in particular, the returned string can be identical for several types and change between invocations of the same program.

The string you get back can be pretty much useless depending on your compiler.

If you are using clang, not on Windows, you can do

auto demangled = abi::__cxa_demangle (typeid (* childComp).name(), nullptr, nullptr, &status))

to get a slightly less messy name.

2 Likes

Thanks, that does work better!

Level 1 (MainComponent::DebugRow) name = Debug Row SubView, id = dbRO
  Level 2 (juce::Label) name = Position Label
  Level 2 (ComboBoxIncDec) name = ComboBoxIncDec, id = guiD
    Level 3 (juce::ComboBox) name = GUI Scale Menu, id = guiD
      Level 4 (juce::Label) name = cbox label
    Level 3 (IncDec) name = incDec
      Level 4 (IncDecButton) name = incDecButton
      Level 4 (IncDecButton) name = incDecButton
  Level 2 (ComboBoxIncDec) name = ComboBoxIncDec, id = guiC
    Level 3 (juce::ComboBox) name = GUI Colour SchemeMenu, id = guiC
      Level 4 (juce::Label) name = cbox label
    Level 3 (IncDec) name = incDec
      Level 4 (IncDecButton) name = incDecButton
      Level 4 (IncDecButton) name = incDecButton
  Level 2 (KToggleButton) name = No Inside Curves Checkbox, id = guiI
  Level 2 (NumberBox) name = Gradient Corners Value, id = guiN
    Level 3 (AppearanceData::NumberBoxLookAndFeel::SliderLabelComp2) name = Gradient Corners Value lbl, id = guiN
    Level 3 (IncDecButton) name = incDecButton
    Level 3 (IncDecButton) name = incDecButton
  Level 2 (KToggleButton) name = Accessibility Checkbox, id = guiA