Calling a parent component function from within a child

#1

I have made a component, which has a public function.

class GuiLightbox    : public Component
{
public:
    GuiLightbox();
    ~GuiLightbox();

    void paint (Graphics&) override;
    void resized() override;
    void setVisibilityUnderLightbox(bool is_visible);

This component has a child, and I want to be able to call
setVisibilityUnderLightbox()
from the child.

    auto lightbox = this->getParentComponent();
    lightbox->setVisibilityUnderLightbox(true);

However, I can’t do that, setVisibilityUnderLightbox() can’t be seen by the child.
What’s the best way to do that?

Thank you!

0 Likes

#2

This is not the best solution but if you want it working quickly, you can maybe do :

auto lightbox = dynamic_cast<GuiLightbox*>(getParentComponent()); 
if (lightbox)
  lightbox->setVisibilityUnderLightbox(true);

A better solution would be to pass a pointer or reference of the parent component into the child component. Even better would be a callback mechanism.

0 Likes

#3

I’ve totally moved to ValueTree’s for this type of thing. ie. using a proper data model to communicate state. On my current project I have both AppProperties and RuntimeProperties ValueTrees, which I pass around to GUI and non-GUI objects interested in data from the model. To make things even cleaner, I have a ValueTreeWrapper class from which I derive data specific wrappers. This hides the actual ‘ValueTree-ness’ of the model from the client. The wrapper is handed a ValueTree root, locates the child it is interested in, and connects to that. The client of the wrapper signs up for data changes using data specific callback, the same way you use Button::onClick. For example, I have transport ValueTree, which has a Transport wrapper for clients to use. It has an onStateChanged callback that you simply assign a std::function<void(int state)> to, and you are good to go. I plan on sharing this code soon, in case anyone is interested. :slight_smile:

1 Like

#4

thank you, the dynamic cast worked. What are the downsides with that by the way?

0 Likes

#5

The class will become completely tied to the other class and object instance like that. Maybe in the future the parent object will not be a GuiLightbox instance and the child component code would need to be changed. (Probably after some really tedious runtime failures and debugging.)

3 Likes

#6

I usually either pass a pointer/reference to my child object, or use a public std::function as a callback function.

In the most cases I will use a reference to my parent, or a pointer, if the parent may be null.
But there are cases, where I need to call a parent function within an event of the child (for example on a event of a custom component I need to call the parents update function) that a “callback” function might be more useful (see onClick of juce::TextButton).

0 Likes

#7

I plan on sharing this code soon, in case anyone is interested

I would love to see that, thanks

0 Likes

#8

Why should there be any difference between storing a pointer and doing a dynamic_cast to the parent component? If you remove (or alter the signature) of setVisibilityUnderLightbox() you’ll get a compile time error just as if you’d stored a pointer to the parent. And even if you forget to check for a null return from the dynamic_cast, you most probably get a rather easy localised runtime error if the parent’s no longer a GuiLightbox. No need to overcomplicate stuff… :slight_smile:

0 Likes

#9

The problem doesn’t lie in tracking down the error in case of a code change, but rather having a well organized, structured and abstracted code base. If you have to pass a pointer to the parent on creation of a child object, everyone who reads this, or the header file of the child class, will notice that the child requires access to some stuff of the parent.

If you just silently cast the parent component, it can lead to some real complicated bugs, where something is modified by a child object, but you don’t realize that because it isn’t apparent that it needs to modify/access the parent.

Also if you use “callback” functions, you aren’t limited to a specific parent class:

In my opinion not so good:

class Child, public Component
{
public:
    Child (Parent* parent) { this->parent = parent; }
    ~Child() = default;
    
    void resized() override { parent->childUpdated(); }
private:
    Parent* parent;
}

and

class Parent
{
public:
    Parent() { child = make_unique<Child>(this); }
    ~Parent() = default;
    
    void childUpdated() { /* Do something */ }
private:
    unique_ptr<Child> child;
}

vs in my opinion better

class Child, public Component
{
public:
    Child() = default;
    ~Child() = default;
    
    void resized() override { onUpdate(); }
    std::function<void()> onUpdate;
}

and

class Parent
{
public:
    Parent()
    {
        child = make_unique<Child>();
        child.onUpdate = [this] () { childUpdated(); };
    }
    ~Parent() = default;
    
    void childUpdated() { /* Do something */ }
private:
    unique_ptr<Child> child;
}

because in this case you can also do

class CompletelyOtherParent
{
public:
    CompletelyOtherParent()
    {
        child = make_unique<Child>();
        child.onUpdate = [this] () { childUpdatedDifferent(); };
    }
    ~CompletelyOtherParent() = default;
    
    void childUpdatedDifferent() { /* Do something */ }
private:
    unique_ptr<Child> child;
}

without the need to modify the Child class

0 Likes

#10

Please take this advice with a pinch of salt, especially if you are not very experienced with lambdas. Be aware, that what you capture is evaluated, when you set the callback, not when it is executed. It is very easy to get the lifetime wrong and call a callback with a dangling captured object!

It is really the best to work in hierarchies, and work your way in one direction, from the general to the specific, from the outer container to the inner.
And if you need to notify your container, @cpr’s advice is gold: try to abstract into a model, that notifies whoever needs to be notified (in case of resized, usually the outmost container’s setBounds() will cascade to each child anyway).

0 Likes

#11

Yes I should have mentioned the disadvantages of this approach. Thanks for clearing that up for me (it’s late). If you decide to use lambdas, also check if the lambda is executable (for example it could happen that the lambda was never assigned).

I probably would also use hierarchies and abstraction, but in some cases, like for example some listener or simply callback functionalities, lambdas are sometimes the way to go, at least for me.

@daniel Sorry that you have to correct some things of mine tonight, I should probably get some sleep

0 Likes

#12

please, no worries… I do write wonky things myself sometimes… this is a community effort :slight_smile:
Your input is much appreciated

0 Likes

#13

Thanks and no offence taken. I’m actually glad that there is somebody that clarifies the things I have missed. As I’m no newby to C++, but also no developer with a 30 year background, I know many things about the language, but can also relate to beginners, who try to wrap their head around everything. That’s why I always struggle for good explanations, because I want to make it as easy to understand as possible but also to not spread any false information.

0 Likes

#14

That’s a lot of code just to make a simple function call! I would argue that good code should do something, not merely exist as a flag to indicate a possible future action the code might take (i.e. a function call to the parent!).

The need or want to have a two-way communication between parent and child in a GUI framework is something a programmer probably realizes quite early (as this topic is a proof of), so it shouldn’t come as a surprise to someone debugging the code that the child might call parent function, even without a hint in its constructor…

Besides, if you have the suspicion that something fishy is going on in connection to an object, or if you plan to alter it’s class, most probably you’ll do a text search for the class and then the dynamic_cast will be found, and thus it’s no longer a “silently” done cast.

Finally, if you consider

getParentComponent()->doSomethingFishy();

a hard to find bug because you didn’t get a prior warning from the .h file, what about

{
   Component c;
   c.doSomethingFishy()
}

?

Hope no offence is taken… I just happens to disagree :slight_smile:

0 Likes

#15

No offense taken. Especially in C++ you learn pretty quickly, you have to adapt and embrace suggestions from others. The code posted by me was really just a demonstration of how someone could approach this problem. But it was coded from memory with no further thought on the implementation. So I’m glad many people post their opinion on different approaches along with their own suggestion, because then you have a wide variety to choose from. Because let’s be honest everyone has their own style and prefers to do things their way.

1 Like