Managing a Gui with nested Components


#1

Hi! Newb here.

I’m sorry for this non JUCE specific and more general C++/Software Design question, but I hope you don’t mind all to much…

I built my first Audio Application by following the tutorials and set up my GuiComponent as member of my AudioAppComponent. My Gui are a bunch of nested Components four levels deep, with Buttons and Labels as members of SubComponents, which are members of Header/Footer/SidebarComponents which are members of the GuiComponent.

I have set up the Logic of the Gui (like showing different screens) with Listener- and State-Patterns. But for handling inputs and outputs I need a class (lets call it Foo) that listens to Buttons and sets Labels far apart in my GuiTree.

When I make Foo a member of AudioAppComponent and it’s on the same level as GuiComponent, I can only come up with the following solutions:

  • make all Components public and make calls on them directly (although this is not the way the tutorials teach to do things)
  • make back-pointers for cross connection between Components
  • hand down variable references that Foo can set
  • save the state of the variables Foo is working on in a ValueTree and make the Components listen to that

I hope I described my problem clear enough and I’m sorry if this question is too basic. I just think there is some sort of design trick I haven’t heard about yet and that I am about to make things much to complex than they have to be…

Thanks for all that have read this far! I’m grateful for every answer! :bowing_man:


#2

After refactoring for a few weeks an reading some more books I have come to a solution and learned two C++ tricks that I’d like to share:

My AudioAppComponent now looks like this:

class MainContentComponent : public AudioAppComponent
{
public:
    MainContentComponent ()
    : foo (gui)
    {
        // Constructor things...
    };

    // AudioAppComponent  member functions...

private:
    Gui gui;
    Foo foo //listens to changes in Gui and sets Gui elements
};

with Gui and Foo

class Gui : public Component,
             public GuiInterface
{
public:
    Gui ()
    {
        // ...
    };

// Component functions, GuiInterface implementations
// and child Components making up the GUI

};
class Foo : public Button::Listener
{
public:
    Foo (GuiInterface& gui)
    : interface (gui)
    {
        // Constructor things...
    };

    // Butto::Listener implementation...

private:
    GuiInterface& interface;
};

with GuiInterface being a virtual class defining what Gui must be able to react to.
If for example a Button click should change the text of a Label, Foo could call

void buttonClicked (Button* b) override
{
    interface.changeLabelText();
};

where changeLabelText() is a virtual function of GuiInterface that Gui has to define.

By using an Interface, I could separate the things Gui has to be able to do, with the actual Layout and Look of the GUI, which is now the only thing defined in the class.

To add Foo as a Listener to the buttons in Gui, I still have to somehow call button.addListener ( /* address of Foo */ ); and for this I’ve come up with this:

At the end of the MainContentComponent constructor I call a Gui function called registerListeners ().

void Gui::registerListeners()
{
    Component* parent = this->getParentComponent();
    MainContentComponent* parentAsMCC = dynamic_cast<MainContentComponent*>(parent);

    guiButton.addListener ( &(parentAsMCC->foo) );
}

First, a Component type pointer to MainContentComponent is created. Then that pointer is turned from a Component the to a MainContentComponent type pointer. With that pointer can get the address of the MainContentComponent member foo.

But since foo is a private member of MainContentComponent, Gui has no access to it. So for this to work friend Gui has to be added in the class declaration of MainContentComponent, so Gui has access to all private members.

Maybe this is somehow helpful for others. If you are a beginner like me and you have trouble getting pointers to parent objects, you might look into casting some Component pointers received by getParentComponent() (although this only works for Component Objects…)


#3

Hi Wolfdad. First, I wanted to thank you for taking the time to provide this detailed example, which I have found helpful. Second, I wanted to know if you encountered (and solved) a problem I am having, which is that in order to make a pointer to MainContentComponent class, I have to #include “MainContentComponent.h” in my sub-component class. My sub-component class is #included in the MainContentComponent so I can AddAndMakeVisible. The reciprocal #include statements cause all sorts of errors, and I wind up going back to passing the variables I need into the constructor for my sub-component instead of passing a pointer to the parent, which works but seems unnecessarily ponderous. What am I missing?


#4

You need to use a forward declaration to do that.

So, in your header file that has a pointer to the parent, do not include the header file of the parent.
Instead, declare it as “class Parent* parentPointer;”.

Then, you can include the parent header in your source (cpp) file and avoid the linker errors.


#5

Ah… brilliant! Thanks for this solution.


#6
//somefile.h
#include "../JuceLibraryCode/JuceHeader.h" //or whatever the path is
class Parent; //forward declaration

class MainContentComponent : public Component
{
public:
    MainContentComponent(Parent& p) : 
    parent(&p) //forces a non-null object be used to initialize the `parent` pointer.
    {

    }
private:
    Parent* parent;
}

in somefile.cpp, this is where you’ll #include Parent.h

The rule is: don’t include what you don’t use. If you’re not calling any member functions on your included objects, you can forward declare instead. This will limit the number of included files and also limit the inclusion to only where it is actually being used, which is hopefully just the .cpp file. This is the same reason a lot of programmers swear by not writing any executable code in the header files, except for templates.


#7

If you’re passing a reference as an argument, you should also store it as a reference. A pointer implies that it could be nullptr at some point;

(Also the forward declaration would be class not Class :wink: )


#8

you might need to dynamic_cast it at some point. it’s not wrong, storing it as a pointer. and maybe you want to be able to change the parent object at some point. you can’t do that if it’s a reference…


#9

Well, you can dynamic_cast a reference, or you could simply take the address of it to cast a pointer.

Yes, if you want to change it then it needs to be a pointer (or maybe some other container like std::reference_wrapper or gsl::not_null. But that’s not clear from the example you gave.

What I’m saying is more general advice that if something can be a reference, make it a reference (even if you need to check its RTTI at some point). Using references leads to much clearer, concise and understandable code.


#10

Thanks to everyone for all the helpful suggestions.