Best practice: where to place listeners for buttons in child components?

Hi,
I’ve just discovered that is better to group button components into a child component. But now I have to modify the way the buttons are listened.
I am wondering what is the best practice to declare listeners button in a child component. In the parent or in the child?
In my case, the parent have got a reference to an audio processor, that I use to make actions when the button is clicked.

Rather than use the Listener/Callback paradigm, have you discovered that buttons have an onClick method that allows you to define a lambda to perform the button’s action?

Something like;

myButton.onClick = [this] { doSomething(); };

I find this much cleaner and use it wherever possible. It avoids managing add/removal of listeners and populating callbacks. And the onClick method can be defined beside all the other button particulars, so the code is much more readable.

Thanks!
The onclick() method seems indeed to be a good option.

On your code example, this is the child class, right?
So that means that it have to inherit from Button::Listener AND from the audio processor object.

It will be the same for all my child components with button.
Is it a good option to do like this?

If you search you will find other examples of using the button.onClick() lambda.

this” will be the class where you instantiate your buttons.

You do not need Button::Listener if all the buttons use the onClick() lambda.

You cannot do any GUI or UI in the audioprocessor, so you should not be inheriting.

All of your UI must be done in your editor class on the message thread. The audio thread is only for processing data!

Thank you again for your advises!

To be clear, I tried to make a new project (named newproject) in the projucer, with the plugin template.

If I understand well, the main class of PluginEditor.h inherit from an AudioProcessorEditor:

class NewProjectAudioProcessorEditor  : public juce::AudioProcessorEditor

There is also these lines in the private attribute of this class:

private:
// This reference is provided as a quick way for your editor to
// access the processor object that created it.
NewProjectAudioProcessor& audioProcessor;

To do some stuff with my button, I seems that I need this audioProcessor object.

So, if I want to do stuff in a new child component of this class, I guess I have to give it to my child component, or to make my component inherit from it, doesn’t it?

You had specified inheriting from AudioProcesser in your question. AudioProcessorEditor is completely different, and yes, you can inherit from it.

The audioProcessor variable is a convenient link to your plugin’s associated processor, you will pass it as a parameter to other methods and classes.

I am sensing some confusion. I strongly urge you to work through the example plugin projects to get a feel for how it all works. You can then start your own project by modifying one of those. And, there are also tutorials you can look at.

Good luck with your project!

You don’t need to inherit from AudioProcessorEditor when creating subcomponents. For a subcomponent that displays some buttons, you can just inherit from juce::Component. Say you want to display an OK/Cancel button group, that class might look something like:

class MyButtonGroup   : public juce::Component
{
public:
    MyButtonGroup()
    {
        addAndMakeVisible (okButton);
        addAndMakeVisible (cancelButton);
    }

    void resized() override
    {
        auto bounds = getLocalBounds();
        okButton.setBounds (bounds.removeFromLeft (bounds.getWidth() / 2);
        cancelButton.setBounds (bounds);
    }

    juce::TextButton okButton { "OK" }, cancelButton { "Cancel" };
};

Then you can add the button group component to your AudioProcessorEditor, and set the onClick callbacks to do whatever you like.

Thanks.
Lets say that I want to use the lambda function:

myButton.onClick = [this] { doSomething(); };

In my case the doSomething() is an audio processing, something to do with the AudioProcessorEditor. Something like:

myButton.onClick = [this] {audioProcessor.myClass -> myfunction();}

As say above, the audioProcessor is a private attribute of the main (parent) class:

 private:
// This reference is provided as a quick way for your editor to
// access the processor object that created it.
NewProjectAudioProcessor& audioProcessor;

For now, I made the textButton public in the child component, and I catch the *buttonClicked * in the parent component (with a test to identify the button). But I suppose it is not the best option to set all buttons as public?

Another option, if I want to use the onClick function directly in the child component (where the button is located and painted), is to inherit from AudioProcessorEditor in the child component.
So, most of my child components of my GUI will inherit from it. Is it good?

As I recommended previously, please look at the plugin examples. You can see in the examples how everything is meant to be.

The broad outline of what you want to do is to do all of your processing of data in your class that inherits from AudioProcessor. You do all of your graphical user interface in your class inherited from AudioProcessorEditor.

When your user interface needs to communicate with your AudioProcessor you must pass values in a manner that will not block the AudioProcessor, which must run freely at all times.

The myButton.onClick() lambda can contain arbitrary code as required by your plugin. Just make sure that any code you use will not impede your AudioProcessor class.

And, again, using the myButton.onClick() lambda does not require the listener or callback. So, you either use the onClick() functionality, or the listener/callback, but not both.

We do not know exactly what you are trying to accomplish, so it is hard to answer your questions specifically. That is why I strongly recommend the examples and tutorials so you can get a better understanding of how it all works.

Thanks for your answer.

I read most of the tutorials (I am inspired for tis question by the tutorial Parent and child components).
I also had a look also on the examples, but I don’t see any implementation of the onclick() button in a child component.

If you want to know exactly what I am trying to accomplish, it is maybe simpler with the code:

PluginEditor.h

class MainCommandsComponent   : public juce::Component
{ 
public:
    MainCommandsComponent();
    ~MainCommandsComponent();
    void paint (juce::Graphics&) override;
    void resized() override;
    juce::TextButton resetButton;    
 };

class myProjectProcessorEditor  : public juce::AudioProcessorEditor, public juce::Button::Listener

{
public:
    myProjectAudioProcessorEditor (myProjectAudioProcessor&);
   ~myProjectAudioProcessorEditor() override;
   void paint (juce::Graphics&) override;
   void resized() override;
private:
    // This reference is provided as a quick way for your editor to
    // access the processor object that created it.
   myprojectProcessor& audioProcessor;
   MainCommandsComponent mainCommands;

   void buttonClicked (juce::Button* button) override
   {
     if (button == &mainCommands.resetButton) { audioProcessor.myfunction -> reset(1)  }
   }
};

PluginEditor.cpp

MainCommandsComponent::MainCommandsComponent()
{
    addAndMakeVisible (resetButton);   
}

MainCommandsComponent::~MainCommandsComponent(){}

void MainCommandsComponent::paint (juce::Graphics& g)
{
resetButton.setColour(juce::TextButton::buttonColourId, juce::Colours::red);
resetButton.setColour(juce::TextButton::textColourOffId , juce::Colours::black);
resetButton.setButtonText ("Reset");
}

void MainCommandsComponent::resized() {  resetButton.setBounds (20, 5, 60, 30);  }

myProjectAudioProcessorEditor::myProjectAudioProcessorEditor (myProjectAudioProcessor& p)
: AudioProcessorEditor (&p), audioProcessor (p)
{
    setSize (300, 200);
   addAndMakeVisible (mainCommands);
   mainCommands.resetButton.addListener(this);
}

myProjectAudioProcessorEditor::~myProjectAudioProcessorEditor()
{   mainCommands.resetButton.removeListener(this); }

void myProjectAudioProcessorEditor::paint (juce::Graphics& g)
{  g.fillAll (juce::Colour (255, 153, 0)); // background color   }

void myProjectAudioProcessorEditor::resized()
{    mainCommands.setBounds (10, 50, 100, 40);   }

So as you can see there is a parent component myProjectProcessorEditor and a child component MainCommandsCompnent. The reset button is located the child component.

I am not sure what is the best option to catch actions on this button.
So the question is:

  • is it a good practice to what I’ve done, whith a textButton declared as public and a listener (or a onclick) in the parent component?
  • is it better to implement the treatments in the child component. Do do so, I have to give the reference to myprojectProcessor to the child component.

Thanks again for your advices.

Just personal preference, but I tend to keep the buttonClicked() and similar callbacks in the component that owns the control. If that component does not have sufficient “scope” to handle it itself, then I call a function in the parent, or even the Editor class, to handle it. (And I pretty much always pass a reference to the Editor as a constructor parameter to all my view Components.)

For example, I might respond to buttonClicked() for the button myResetButton by calling editor.onResetButtonClicked(), and letting the Editor handle it from there. That keeps the only connection to the Processor in the Editor class itself, and also allows other side-effects to be handle by the Editor, such as updating other sub-components.

However, I sometimes pass a reference to some member of the Processor class down through the Editor to certain sub-components’ constructors. I find that a little easier than asking the Editor to ask the Processor to give me a pointer/reference to some data that the sub-component needs every time it needs to know what some data is (for drawing data-related graphics, for example).

1 Like

To further Howards comments, I use ValueTree’s as my data model, and abstract the button click from the action… so, a button click in a component that controls something elsewhere, ends up setting a valuetree property (through classes I call ValueTreeWrappers, which contain context specific API’s), and the process that does the action listens to that property. The classic example of this is a Transport. Instead of binding my transport UI to the actual transport, there is a TransportProperties class which I use. This wraps a VT which contains all of the transport properties (obviously, lol), along with getters/setters/changeCallbacks. The transport UI sets those items, and listens to them to reflect changes in the gui, and the transport itself does the same thing.

One possible refactoring of your code is as follows;

In the .h file;

class MainCommandsComponent   : public juce::Component
{ 
public:
       MainCommandsComponent();
       void resized() override;
private:
       juce::TextButton resetButton;    
 };

class myProjectProcessorEditor  : public juce::AudioProcessorEditor
{
public:
      myProjectAudioProcessorEditor (myProjectAudioProcessor&);
      void paint (juce::Graphics&) override;
      void resized() override;
private:
       // This reference is provided as a quick way for your editor to
       // access the processor object that created it.
       myprojectProcessor& audioProcessor;
       MainCommandsComponent mainCommands;
};

In the .cpp file;

MainCommandsComponent::MainCommandsComponent()
{
    resetButton.setColour(juce::TextButton::buttonColourId, juce::Colours::red);
	resetButton.setColour(juce::TextButton::textColourOffId , juce::Colours::black);
	resetButton.setButtonText ("Reset");
	resetButton.onClick = [this, audioProcessor]
	{
		audioProcessor.myfunction()->reset(1);
	};
	addAndMakeVisible (resetButton);   
}

void MainCommandsComponent::resized()
{
	resetButton.setBounds (20, 5, 60, 30);
}

myProjectAudioProcessorEditor::myProjectAudioProcessorEditor (myProjectAudioProcessor& p)
: AudioProcessorEditor (&p), audioProcessor (p)
{
	setSize (300, 200);
	addAndMakeVisible (mainCommands);
}

void myProjectAudioProcessorEditor::paint (juce::Graphics& g)
{
	g.fillAll (juce::Colour (255, 153, 0)); // background color
}

void myProjectAudioProcessorEditor::resized()
{
	mainCommands.setBounds (10, 50, 100, 40);
}

You can see how much cleaner the code can be with the .onClick lambda.
Good luck with your coding!

Thank you very much for your answers!
As mentioned by Howard, I need to pass a reference to the Editor as a constructor parameter to my child view component. But I am not sure how to do it.

From the code of bwall, this is what I’ve done:

In the .h file:

class MainCommandsComponent   : public juce::Component
{ 
public:
   MainCommandsComponent(myProjectAudioProcessor&);
   void resized() override;
private:
   myprojectProcessor& audioProcessor;
   juce::TextButton resetButton;    
};

class myProjectProcessorEditor  : public juce::AudioProcessorEditor
{
public:
  myProjectAudioProcessorEditor (myProjectAudioProcessor&);
  void paint (juce::Graphics&) override;
  void resized() override;
private:
   // This reference is provided as a quick way for your editor to
   // access the processor object that created it.
   myprojectProcessor& audioProcessor;
   MainCommandsComponent mainCommands;
};

In the .cpp file;

MainCommandsComponent::MainCommandsComponent(myprojectProcessor& p)
:audioProcessor (p)
{
    resetButton.setColour(juce::TextButton::buttonColourId, juce::Colours::red);
    resetButton.setColour(juce::TextButton::textColourOffId , juce::Colours::black);
    resetButton.setButtonText ("Reset");
    resetButton.onClick = [this]
    {
	      audioProcessor.myfunction()->reset(1);
    };
    addAndMakeVisible (resetButton);   
}

void MainCommandsComponent::resized()
{
    resetButton.setBounds (20, 5, 60, 30);
}

myProjectAudioProcessorEditor::myProjectAudioProcessorEditor (myProjectAudioProcessor& p)
: AudioProcessorEditor (&p), audioProcessor (p)
{
    
    mainCommands = new MainCommandsComponent(audioProcessor);
    setSize (300, 200);
    addAndMakeVisible (mainCommands);
}

 void myProjectAudioProcessorEditor::paint (juce::Graphics& g)
{
    g.fillAll (juce::Colour (255, 153, 0)); // background color
}

void myProjectAudioProcessorEditor::resized()
{
    mainCommands.setBounds (10, 50, 100, 40);
}

However, the build failed.

In Xcode, I’ve got this error in PluginProcessor.cpp:
Return type of out-of-line definition of myProjectAudioProcessor::myProjectAudioProcessor differs from that in the declaration .

Could you please tell me where is the problem?

your class is named myProjectProcessor in the header but you’ve named the constructor myProjectAudioProcessor in the header and the .cpp

Thanks kisielk, it was an problem of copy/past code to this forum.

There was another error… I was told to explicitly initialize member which does not have a default constructor.
I found how to fix it with initializing mainCommands in the initialization list. I suppose I would have to do it with all the view components, right?

So, here is a new version:

In the .h file:

class MainCommandsComponent   : public juce::Component
{ 
public:
    MainCommandsComponent(myProjectAudioProcessor&);
    void resized() override;
private:
   myprojectProcessor& audioProcessor;
   juce::TextButton resetButton;    
};

class myProjectAudioProcessorEditor  : public juce::AudioProcessorEditor
{
public:
  myProjectAudioProcessorEditor (myProjectAudioProcessor&);
  void paint (juce::Graphics&) override;
  void resized() override;
private:
   // This reference is provided as a quick way for your editor to
   // access the processor object that created it.
   myprojectProcessor& audioProcessor;
   MainCommandsComponent mainCommands;
};

In the .cpp file:

MainCommandsComponent::MainCommandsComponent(myprojectProcessor& p)
    :audioProcessor (p)
 {
    resetButton.setColour(juce::TextButton::buttonColourId, juce::Colours::red);
    resetButton.setColour(juce::TextButton::textColourOffId , juce::Colours::black);
    resetButton.setButtonText ("Reset");
    resetButton.onClick = [this]
    {
	      audioProcessor.myfunction()->reset(1);
    };
    addAndMakeVisible (resetButton);   
}

void MainCommandsComponent::resized()
{
    resetButton.setBounds (20, 5, 60, 30);
}

myProjectAudioProcessorEditor::myProjectAudioProcessorEditor (myProjectAudioProcessor& p)
    : AudioProcessorEditor (&p), audioProcessor (p), mainCommands(audioProcessor)
{
    setSize (300, 200);
    addAndMakeVisible (mainCommands);
}

void myProjectAudioProcessorEditor::paint (juce::Graphics& g)
{
    g.fillAll (juce::Colour (255, 153, 0)); // background color
}

void myProjectAudioProcessorEditor::resized()
{
    mainCommands.setBounds (10, 50, 100, 40);
}

You might try and find some C++ tutorials that you could go through, as these are general C++ issues and not really JUCE-related. Learning a bit more about how classes work in C++ should make exploring the JUCE code much more straightforward.

For the issue at hand, you need to initialize all class members which do not have default constructors. A default constructor is a constructor that doesn’t require any arguments. It looks like your MainCommandsComponent class has no default constructor, and instead has a constructor that takes an AudioProcessor as its argument. When there is no default constructor, you must initialize the object in the initializer list (or in the header file), providing whatever arguments are necessary for the constructor you want to call.

So, for your question about whether or not you must initialize all of your view components this way, it depends entirely on what constructors you have written for those view components!