Cusom component Listener

Hi, I have a custom component made of a Slider and a Label, with museListener on the Slider that changes the Label text on mouseEnter.
How can I listen to the Slider in the component from the PluignEditor.cpp?

In the PluginEditor I addAndMakeVisible the custom component like any other component of the plugin.

Here’s how I did that. First, add a Listener class to your custom component. I copy&pasted the Listener from an existing class (like Slider, for example). In the public part of my custom component, I have this:

		struct Listener
	{
		Listener();
		virtual ~Listener();

		virtual void onValueChange( float newValue ) = 0;
	};

	void setListener( Listener* pTheListener ) { pListener = pTheListener; }

Then in the private section I declared a listener pointer. (I only needed one listener, so I don’t have to keep a list of listeners like most classes I’ve seen do. That’s why I have setListener above, instead of addListener.)

		Listener* pListener = NULL;

Then I added my custom component’s Listener as a public parent class for my editor:

                        public MyCustomComponent::Listener,

and overrode its callback there

virtual void onValueChange( float newValue ) override;

In the editor, after creating the custom component, I call setListener(this).

Then in the custom component, when I want to call the callback, I just check if pListener is non-NULL, and if so, then I call its onValueChange() function.

Not sure if there is an easier way for you (such as simply using the Slider sub-component’s Listener instead of making a new Listener in your custom component), but this is working for me.

2 Likes

Oh, and to define the constructor and destructor, which don’t do anything, I just added this to the custom component’s .cpp file:

MyCustomComponent::Listener::Listener() {}
MyCustomComponent::Listener::~Listener() {}

do you even need to provide a constructor? Button::Listener doesn’t…

Assuming you have a structure like this:

struct CustomComponent
{
    Label label;
    Slider slider;
}; 

and you have a class like this:

struct MainComponent : public Component
{
   MainComponent();
   void resized() override;
   void paint(Graphics& g) override;
   CustomComponent customComponent;
};

you can make your MainComponent inherit from Slider::Listener and add your main class as a listener

struct MainComponent : public Component, public Slider::Listener
{
   MainComponent();
   void resized() override;
   void paint(Graphics& g) override;
   void sliderValueChanged (Slider *slider) override; // <- add this 
   CustomComponent customComponent;
}

and in your MainComponent constructor, just add your MainComponent as a listener:

MainComponent::MainComponent()
{
    customComponent.slider.addListener(this);
}

Finally, to see if that slider in your custom component was moved:

void MainComponent::sliderValueChanged (Slider *slider)
{
    if( slider == &customComponent.slider )
    {
        /* it was */
    }
}
1 Like

Thanks a lot @matkatmusic!
You explained me perfectly!!
I’ll try as I can tonight!

np. you should probably go through the Listener/Broadcaster tutorial and some of the other tutorials for sliders and audio plugins:
https://docs.juce.com/master/tutorial_code_basic_plugin.html

Your solution works great!
Thanks!

I have been trying to add a listener to my custom component (similar to button/slider listeners) but have been struggling to mimic button/slider. I found your post recently and it has been very helpful but I’m wondering if you can clarify this bit:

Then in the custom component, when I want to call the callback, I just check if pListener is non-NULL, and if so, then I call its onValueChange() function.

I don’t understand where in the custom component you are calling onValueChange() and how. Shouldn’t this be called continuously to see if the value is changed? How can that be accomplished in the custom component? And where is the float newValue argument coming from when you call it within the custom component?

I’m wondering if you have an implementation you could provide.

I don’t poll the change, I broadcast it. Whether it’s a click on a Component, a change of a parameter’s value, or whatever it is you want to listen to, you call the listener’s callback to notify it of the event. (For example, I have some custom components that issue callbacks to their listener when clicking on the Component. I don’t actually have an onValueChanged() callback like that, but I have similar ones. You can look at the Slider::Listener class for how it is used and where its callback is called from.)

As Howard said you dont poll changes, you broadcast changes to listeners when your broadcaster gets interacted with. IE when the slider knob gets moved, its value changes, and it tells all its listeners hey my value changed to this

S pseudocode that might clear some things up.
Here is a CustomComponent class that allows other objects to register as listeners (these listeners get stored in the listeners array member variable. We use a special ListenerList class for this as it provides the call() method that lets you call methods on each object in the listener list). Note the type of objects this ListenerList stores. It stores Listener objects (the inner class within CustomComponent). This is important because the Listener class has the virtual valueChanged() method, so every object stored in this listener list will have a valueChanged() method that can be called.

This component also has a value member variable. When the component is interacted with (ie if it was a slider this could be when the slider gets moved) that value is incremented. After we increment, we need to let all the listeners that have subscribed to these changes know that a changed occurred by calling each listener’s valueChanged method and pass in the new value as an argument.

class CustomComponent {

    public:

     class Listener {
        public:
            virtual ~Listener() = default;

           // this will be implemented by objects that want to listen for changes
            virtual void valueChanged(int newValue) {};

        };
       
        // imagine that this method gets called whenever the user clicks on this component
       // actual interaction code is not shown
        void interactWithComponent() { 
           // increment the value member
            value++;
             // now call the value changed method of every single listener that has subscribed to be notified 
             // pass in the value so that listeners can use it however they please
              listeners.call([this](Listener &l) { l.valueChanged(this->value); });
         }
       
        // these methods are used by objects that want to subscribe and unsubscribe from changes that occur in the custom component
        void addListener(Listener *l) {listeners.add(l);}
        void removeListener(Listener *l) {listeners.remove(l);}

    private:
        int value = 0;
        juce::ListenerList<Listener> listeners;

Now we need a class that implements this listener interface. This class would be the one that wants to be notified when changes occur in the CustomComponent (ie when the value changes). All we need to do is implement that listener interface and add ourselves as a listener to the custom component (we also need to be sure to remove ourselves as a listener when the object gets destructed):

class SomeListener : public CustomComponent::Listener
{

public:
    SomeListener() { customComponent.addListener(this); }
   ~SomeListener() {customComponent.removeListener(this); }

     // implement the valueChanged method
    // this is what CustomComponent calls for every one of its listeners
    void valueChanged(int newValue) override { DBG("the value changed to: " + String(newValue); }
    
   CustomComponent customComponent;

}

Now when the the custom component is interacted with (there isnt actually in code for that part, just imagine that when the custom component is clicked or something that the interactWithComponent method gets called) and the value changes, the CustomComponent will essentially loop over every one of its listeners that have registered themselves using the addListener method and call the valueChanged() method that each individual listener has implemented.

Howard and stonepresto, thank you so much for the feedback. The additional example code is helpful. The interactWithComponent() function with this bit in particular is what I was missing:

listeners.call([this](Listener &l) { l.valueChanged(this->value); });

I implemented it as given and call the interactWithComponent() in my CustomComponent’s mouseDown() function and it works.

I changed the argument in valueChanged() to CustomComponent* to mimick the slider/button style. I’m using it to control a reverb audio source from a GUI class. So then in my class that inherits CustomComponent::Listener (DeckGUI, in this case) I can do:

void DeckGUI::valueChanged(CustomComponent* customComponent)
{
    if (customComponent== &customComponentName1)
    {
        reverbSource->setParameter(customComponent->getValue());
    }
    if (customComponent== &customComponentName2)
    {
        reverbSource->setParameter(customComponent->getValue());
    }
}

Thanks for the help!