Calling a method on the parent from the child component

This most certainly couldn’t be more trivial, but since I’m still a complete novice in programming and especially in C++, I’m still scratching my head about it.

I want to call a method on a parent component from within its child component. From my searches so far on this forum, the solution I read of the most is to pass a reference of the parent down to the child. Here’s what I got:

MainComponent.h

#pragma once

#include <JuceHeader.h>
#include "ChildComponent.h"

class MainComponent  : public juce::Component
{
public:
    MainComponent();
    ~MainComponent() override;
    void paint (juce::Graphics&) override;
    void resized() override;
    void setLabelText(juce::String newText);

private:
    juce::Label textLabel;
    ChildComponent childComponent;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

MainComponent.cpp

#include "MainComponent.h"

MainComponent::MainComponent()
    :   childComponent(*this)
{
    setSize (600, 400);
    addAndMakeVisible(textLabel);
    textLabel.setText("Text goes here....", juce::dontSendNotification);
    addAndMakeVisible(childComponent);
}

MainComponent::~MainComponent()
{
}

void MainComponent::paint (juce::Graphics& g)
{
    g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));
}

void MainComponent::resized()
{
    textLabel.setBounds(20, 20, getWidth(), 25);
    childComponent.setBounds(0, 50, getWidth(), getHeight() - 50);
}

void MainComponent::setLabelText(juce::String newText)
{
    textLabel.setText(newText, juce::dontSendNotification);
}

ChildComponent.h

#pragma once

#include <JuceHeader.h>
#include "MainComponent.h"

class ChildComponent  : public juce::Component
{
public:
    ChildComponent(MainComponent& mainRef);
    ~ChildComponent() override;
    void paint (juce::Graphics&) override;
    void resized() override;

private:
    MainComponent& mainComponentRef;
    juce::TextButton childButton;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildComponent)
};

ChildComponent.cpp

#include <JuceHeader.h>
#include "ChildComponent.h"

ChildComponent::ChildComponent(MainComponent& mainRef)
    :   mainComponentRef(mainRef)
{
    addAndMakeVisible(childButton);
    childButton.setButtonText("Child Button");
    childButton.onClick = [this]() {mainComponentRef.setLabelText("Updated Text"); };
}

ChildComponent::~ChildComponent()
{
}

void ChildComponent::paint (juce::Graphics& g)
{
    g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId));
    g.setColour (juce::Colours::grey);
    g.drawRect (getLocalBounds(), 1);
}

void ChildComponent::resized()
{
    childButton.setBounds(10, 10, 100, 30);
}

So overall extremely simple, just a Label in the MainComponent and a TextButton in the ChildComponent that’s supposed to call the setLabelText() method to change the label’s text.

The problem is that once I try to compile this I get a multitude of errors (syntax errors, missing type errors, …). From what I’ve gathered from searching the web this might be a circular dependency issue, since I include ChildComponent.h in the MainComponent header and the MainComponent.h in the ChildComponent header?

I’ve also tried forward declaring class MainComponent; in the child header instead of #include "MainComponent.h", but then the compiler complains about the use of undefined type ‘MainComponent’.

What am I doing wrong?

Thanks!

Components already know about their parents:

findParentComponentOfClass<MainComponent>()->setLabelText()

Will work if your child component has been attached to the parent (addAndMakeVisible).
The only niggling issue is how ChildComponent knows about said function.

I will often have nested components like:

class ParentComponent : Component {
    class ChildComponent : Component {...};
};

Or you can include the ParentComponent.h header in your ChildComponent.cpp source file.

1 Like

Welcome!

There are a few solutions.
First a word on includes:

  • Ideally only include in the cpp. In the header prefer forward declarations. That works for all pointers and references:
#pragma once

#include <JuceHeader.h>

class MainComponent; // < tells the compiler that there will be this class at link time
class ChildComponent  : public juce::Component
{
    // [...]
};

The exception is base classes and members that are not a pointer or reference, like the ChildComponent in the MainComponent declaration.

  • It might be easier to create the connection where you have access to both components. It is a bit more tricky in your case, because the button and the label are not siblings. If the childButton was public, you could do in the MainComponent constructor:
childComponent.childButton.onClick = [this]()
{
    textLabel.setText ("Updated Text", juce::dontSendNotification);
}

Hope that helps

1 Like

Here is an excellent overview by @xenakios of different ways to communicate between components, hope this helps:

2 Likes

Thanks everyone!
Time to get back into the hole and experiment with all these different approaches.

Thanks again!

I’ve never tried it but I think postCommandMessage works for it, everything is cleaner and more organized, and you can send multiple messages from any component. Something like that:

child.getParentComponent()->postCommandMessage( 1 );

and in the parent component.

void handleCommandMessage(int command) override
{
     if (command== 1)
     {
         method();
     }
}
2 Likes

I would dispute that, the code is suddenly dependant on magic numbers.
But maybe that’s personal preference.

They are not magic numbers, if you only need to call a function, any number is valid, which can even be used as a parameter. If you need to call several, just create a list of enums and it’s ready to be used at any time by any child component.

I have been reviewing the specific problem and handlecommandmessage is not ideal, since it does not allow user data to be passed

why don’t you pass a reference of the Label instead of the parent component?

1 Like

As a beginner I’m surprised that there doesn’t seem to really be a “right” answer and that there are so many possible solutions (I only have a bit of practice in web dev and using web frameworks like Svelte and React, and I feel things are a lot more clear cut there (at least in Svelte haha)).

I’ve tried to wrap my head around some of your proposed solutions, but I think most of them are still way over my head. For now the easiest solution really was to just simply forward-declare MainComponent in ChildComponent.h and move #include "MainComponent.h" to ChildComponent.cpp instead. Now things are working as I thought they should! :slight_smile:

This is indeed the most universal solution, and I recommend sticking to it.

The reason for all the alternatives are, because in an architecture you try to minimise the details that is known to other structures. Like the MainComponent knows the ChildComponent, but doesn’t need to know what’s inside ChildComponent.
And the other way round, the ChildComponent doesn’t need to know too much about MainComponent.

And in audio we have different threads, which will become a topic on it’s own later in your journey. Calling directly and using data, that might be used in a different thread requires other techniques involving FIFOs or locks.

Also, since there is already a relation from components to their children, it is nice to use this existing relationship with the findParentWithType() that @oli1 suggested. This is great, but has some drawbacks:

  • It involves dynamic_cast which is frowned upon
  • It might return nullptr
  • It cannot be used inside the constructor, because at that point it wasn’t added as child yet

When you continue using JUCE you will find much more concepts for coupling, each with their specific advantages and disadvantages, like AsyncUpdater, ChangeBroadcaster/ChangeListener etc.
Don’t let it confuse you too much, use what you know so far and explore the others, when what you have doesn’t fit.

1 Like

Thank you for the additional information and words of encouragement! All of this is indeed quite overwhelming and confusing at the moment, but the masochist in me kind of enjoys the challenge (plus building audio/midi plugins and apps is WAY more fun than building a website ha).

1 Like

I think the best solution for this specific case is to pass a reference of the Label to the child component, not the parent component.

1 Like

I think the best solution for this specific case is to pass a reference of the Label to the child component, not the parent component.

Not sure if that’s actually better design. It tightly couples the child component to a label that is completely unconnected from the child otherwise. What if in the future you decide to use some other type of object instead of Label? Then you have to change the signature of the constructor of the child component and pass it the new object. “Separation of concerns” would lead me to let the parent component decide how to handle the request from the child component, using a function named something more generic, like updateTextFromChild(text). This lets you change exactly how the parent handles the notification in any way that is needed, without ever touching the child component interface again.

what if instead of passing the label to the constructor, it is passed directly to a function in the way that daniel proposed before, so it is not even necessary to maintain references.

void Child::assignLabel(Label& label)
{
    button.onClick = [&label]() {   
          label.setText("Updated Text", juce::dontSendNotification);
    };
}

I just provide a parent reference to the children. Typically with a forward declaration in the header since this is a circular reference. You are logically creating an association between these two components so in my mind why would it be anything other than a simple pair of references between the two?

4 Likes