Main component needs to listen for buttons and sliders in child component

Almost done with a game. So far all code is in main component files (h and cpp). Now I want to add a menu (child) component (h and cpp), kind of a pop-up window in front of the main game screen (component), which will then contain various sliders and buttons used to change various game settings, plus a “Start” game button.

Right now I have the game setting sliders and buttons in the main component with listeners, showing when needed on top of the game, and it works fine, I just think it would be elegant if I put them all into a separate child component (with it’s own framed rectangle and background color).

Since the game settings (variables) are all in the main component, I believe it would be easiest if I just process (listen) to the menu component’s sliders and button in my main component.

I would be most grateful if somebody have the time to offer a simple example, thanks!

I don’t think I’m directly answering your question, in that it’s not an ‘easiest’ answer, but consider that the ‘data model’ (ie. the settings) should be separate from the GUI, which then means your components communicate with that, and not each other? This is the idea that the GUI is a view into, and a controller of, the data. I use JUCE’s ValueTree’s for most of this stuff.

2 Likes

I recently was searching for a similar answer: how to have a second window call member functions in the MainWindow and MainComponent (which is one way of describing what you want to do).

I was graciously helped by my friend Shane @getdunne with a couple of possibilities; here’s what’s working well for me now: storing a reference to the MainComponent in the second window’s component; you can then call member functions of the MainComponent.

I’m new to JUCE, I may not have all the terminology correct, but let me try to share the relevant pieces of code as examples. Ignore my function names, just look at what it does.

When you open/create the second window from the MainComponent (my main component is named MidiDemo, the content component of the second window is named MessagesWindowComponent and it is installed into a MessagesWindow DocumentWindow), pass a reference to *this:

MessagesWindow(MidiDemo& mainRef, const String& name, Colour backgroundColour, int buttonsNeeded);


void MidiDemo::showMessagesWindow ()
{
    if (!messagesWindow)
    {
        messagesWindow.reset(new MessagesWindow(*this,
                                            "Messages",
                                            Desktop::getInstance().getDefaultLookAndFeel().
                                            findColour(ResizableWindow::backgroundColourId),
                                            DocumentWindow::allButtons));
    }
    messagesWindow->setVisible(true);
}

(I’m not killing the window on close, just hiding it; hence testing for the pointer and simply showing it if it exists.)

When the second window creates its content Component, pass it along:

MessagesWindowComponent::MessagesWindowComponent (MidiDemo& mainRef)
: mainComponent(mainRef)
{
	[…]
}

In the second window component (MessagesWindowComponent), store the reference:

private:
    MidiDemo& mainComponent;

Now, any button, slider etc. can call functions in the MainComponent, ie. say you have a button in the secondary component:

midiFilterClockButton.onClick = [this]
{
    // call a member function of the MainComponent
    mainComponent.someFunction();
};

Also, although I am not trying this right now, it seems if you have the pointer to the MainComponent, you could then add listeners and do things that way.
Sorry if that’s very muddled, without uploading the entire project, I hope it helps.

@cpr and @stephenk thank you very much for your suggestions.

This is what I did to do exactly what I wanted for this application, that is listen to child component sliders and buttons in main component.

Files; Main.cpp, MainComponent.h, MainComponent.cpp, MenuComponent.h, and MenuComponent.cpp

MainComponent.h contains, besides the “default” code;

#include "MenuComponent.h"
...
class MainComponent : public AudioAppComponent,
	                  public Button::Listener,
	                  public ChangeListener,
	                  public HighResolutionTimer,
	                  public MixerAudioSource,
	                  public Slider::Listener
...
public:
    void sliderValueChanged (Slider* slider) override;
	void buttonClicked (Button* button) override;
private:
MenuComponent* menu = new MenuComponent (this, this);

MainComponent.cpp;

addAndMakeVisible (menu);
 ...
void MainComponent::sliderValueChanged (Slider* slider)
{
	String id = slider->getComponentID ();
	if (id == "appSize")
    {
      // Do something
...
void MainComponent::buttonClicked (Button* button)
{
	String id = button->getComponentID ();
	if (id == "start")
	{
      // Do something

MenuComponent.h;

class MenuComponent : public Component	                  
{
public:
	MenuComponent (Slider::Listener* sliderParent, Button::Listener* buttonParent);
	~MenuComponent();

MenuComponent.cpp;

MenuComponent::MenuComponent (Slider::Listener* sliderParent, Button::Listener* buttonParent)
{
  // Create your buttons and sliders here but instead of "this", 
  // use "sliderParent" and "sliderButton" for addListener().
  // Also set ComponentID.
  // Example;
    startButton.addListener (buttonParent);
	startButton.setComponentID ("start");
	startButton.setColour (TextButton::buttonColourId, Colours::green);
	startButton.setButtonText ("START NEW GAME");
	addAndMakeVisible (startButton);

For me this works perfect, but I am still wondering if there was a simpler way so I did not have to specify two “parents” in the constructor.

I’m still a beginner here, but couldn’t you just pass one reference to *this as a void, and then cast it to Slider::Listener and Button::Listener - or something like that?

Anyway, good to see your solution.

My suggestion is that you remove the operation ‘start’ from the GUI Components, and that the GUI components don’t need to talk to each other at all. Generally this is referred to as ‘ModeViewController’ (aka MVC). Imagine it like this, the dashboard of your car shows you information about you engine, and allows you to control your engine, but your engine is not in the dashboard. You could have two gas gauges (UI) that is showing you the level of your gas tank. The 2nd gas gauge doesn’t get the information from the 1st, they both get it from the measurement device. As well, you could have two ignition switches. Again, the 2nd switch doesn’t communicate with the first, they both talk to the starter.

It is common for people to combine the GUI with the data model and underlying operations, when they first begin, because it is very easy to do so. But, as your applications become more complex, you will benefit greatly from separating this code appropriately. Instead of creating the connections between the GUI objects, you connect your GUI objects to the ‘model/engine’, where you would have setters/getters/listners for the operations, such as start and app size.

1 Like

Yes I was thinking how I could only pass one, thanks I’ll try your suggestion.

Ok thanks, for now this is just a simple game, but I will keep it in mind when I start my mega colossus synthesizer :slight_smile:

(were you referring to my example or the OP example?)

I’m interested in what you speak of; however, in these examples, what is the ‘model/engine’. Isn’t it the MainComponent?

I would have to think about a proper object break down, but in a general sense I would think there could be a class Game that contains the data. Again, imagine user interface is the window into this data, and the game could even run without the UI. Given that, how do you write the game engine? What API’s do you create for interacting with the game engine?

For example, the code that does the ‘save’ function is removed from the GUI, and put into Game::Save. The GUI would have a reference (or maybe a pointer, if there is a good reason for that) to the game engine, and when the save button is pressed, all it does is call game.Save(). Or if there is a prompt for the save file, the save button is pressed, the file dialog is displayed, and then game.Save(fileName) is called.

For data, such as appSize, I use ValueTrees, as they can easily store the data, they are super cheap to pass around, and make it easy for various pieces of code to get/set/listen for changes. A simplified example of my approach with ValueTree’s is to make one that is top level one (I actually use two, one for persistent data and one for runtime data), which gets populated with all of it’s branches at startup, and passed into any code that wants access to the data.

2 Likes

In that case, couldn’t the top level JUCEApplication class contain the data and the JUCEApplication::Save() function? Or is there a reason not to add stuff to the JUCEApplication?

You could use the App class, just as you could use the GUI components. But ‘good’ design dictates that data and code be grouped in ways that make sense, and keep coupling to a minimum. Is the Application class the game? no, it’s code that starts the app. Of course, it’s never helpful to be overly dogmatic about how you build an app, so, in that sense, these practices aren’t required, but, good practices tend to produce better, more robust, apps.

Having said that, I do create my top level ValueTree’s in the Application class, and in the case of the game, I would pass these top level tree’s into it, and the game class would create it’s tree’s as children. I then pass the top level tree’s into the gui, and use data model specific classes for accessing the various branches. I apologize that my description of my implementations is a bit abstract.

1 Like

Hmm anyway you can help me a bit on this part?

Are you referring to using the single parent? This is not my strong suit, so apologies if it doesn’t work, but I might try something like:

public:
	MenuComponent (MainComponent* main,
                   const String id);


MenuComponent::MenuComponent (MainComponent* main,
                              const String id)
{
    startButton.addListener ((Button::Listener*) main);

Actually, why can’t you just use the MainComponent ptr, why does it need to be a Button::Listener etc.? I know very little about Listeners yet, but the one tutorial I did just assigns it to (this) in the main component. so:

MenuComponent::MenuComponent (MainComponent* main,
                              const String id)
{
    startButton.addListener (main);

Looks great. So in my MainComponent.h where I do;

MenuComponent* menu = new MenuComponent (this, this, "Menu");

Well obviously I can take out the middle parameter, but I am unsure about what exactly the first should be. “this” is not working.

My guess would have been one “this”. I mean, you’re passing a ptr to the MainComponent. Or maybe you should try passing a reference - I am new to this, so maybe I shouldn’t be trying to help. :wink:

public:
	MenuComponent (MainComponent* main,
                   const String id);

MenuComponent::MenuComponent (MainComponent* main,
                              const String id)
{
    startButton.addListener (main);


I mean, you’re calling this from the MainComponent(), so this should be a ptr to the MainComponent:

private:
MenuComponent* menu = new MenuComponent (this, "Menu");

Actually, since I am interested in this answer, I spent some time figuring it out. Again, I am new to JUCE, so if there’s a better way for this particular example, please let me know.

Anyway, you can make it work by passing a reference. This is what I did, basically, although my actual testing code is different. But I had a button in the MainComponent open a second window containing a TextButton. Then I added that TextButton to the MainComponent Listener, and had it call a function back to the MainComponent. In your code, it would look like this I think:

public:
	MenuComponent (MainComponent& main,
                   const String id);
private:
    MainComponent& mainComponent; // store the ref so you can unlink the listener in the MenuComponent's destructor

Then…

MenuComponent::MenuComponent (MainComponent& main)
: mainComponent(main)
{
    startButton.addListener (&mainComponent);
[...]
}
MenuComponent::~MenuComponent()
{
    startButton.removeListener (&mainComponent);
}

And…

private:
MenuComponent* menu = new MenuComponent (*this, "Menu");

You may need a forward declartion at the top of your MenuComponent.h file:

class MainComponent;    // forward declaration

I hope that works for you. If not, here is a link to download my project, which is based on the ListenersAndBroadcasters Tutorial, but adds a second window:

ListenersAndBroadcastersTutorial2a.zip (8.9 KB)

1 Like

@stephenk Hello! So I’ve been trying to get this to work on my end, my windowing code is slightly different (looks like your example has the debugWindow kept as a pointer, mine is just owned by my mainComponent). However, I’m getting an utterly obnoxious error:

cannot initialize a parameter of type 
'juce::Button::Listener *' with an 
rvalue of type 'MainComponent *'

Took me a lot longer than I’d prefer to admit (I’ll admit it anyway: most of today) to figure out the root of it - I’m using AudioAppComponent as my main inheritance, instead of just Component.

I tried testing it by switching your example over to AudioAppComponent (and adding in a bunch of juce modules). The example now just immediately segfaults and kills the JIT compiler the moment I try and run it. :tired_face:

Anyway, any ideas? The only thought I’ve had is to turn the AudioAppComponent into a child of a Component-inherited-mainComponent but I’m honestly not sure that would even work at all.

That was not the reason for the compilation error. Neither AudioAppComponent nor Component were mentioned in the error.

Let’s translate:

cannot initialize a parameter of type 'juce::Button::Listener *' with an 
rvalue of type 'MainComponent *'

The compiler tried on your behalf to implicitly cast a MainComponent* to a juce::Button::Listener *. This works only, if MainComponent inherits juce::Button::Listener, so the compiler seems to be convinced, that is not the case.

When do implicit casts happen?

Either when you assign a variable:

MainComponent * foo;
juce::Button::Listener * bar = foo;

or when you use the variable as argument. The compiler tries to cast the supplied argument to anything, that satisfies the function’s argument type:

button.addListener (&mainComponent);

So you see, the compiler sees your mainComponent as not inheriting juce::Button::Listener.

@stephenk explained above, how to make the mainComponent class inherit from the Button::Listener interface: in the class definition (header file), you add public juce::Button::Listener to the list of base classes and you override the pure virtual function void buttonClicked (Button* buttonThatWasClicked) override

Good luck!