Is there a way to access the Object which triggers a Value Change to a Listener?

My Listener Path is like:

PluginEditor.h

class EightAudioProcessorEditor  : public AudioProcessorEditor, public MenuBarModel, public KeyListener, public KeyPress
{
...
private:
   ...
    AulusKnob mKnobA;
    AulusDisplay mDisplay;
    ...
}

PluginEditor.cpp

EightAudioProcessorEditor::EightAudioProcessorEditor(EightAudioProcessor& p)
    : AudioProcessorEditor(&p),
    processor(p),
    keyboardComponent(p.keyboardState, MidiKeyboardComponent::horizontalKeyboard),
    menuBar(this)
{
    ...
    mKnobA.setName("Knob A");
    mKnobA.value.addListener(&mDisplay);
    addAndMakeVisible(mKnobA);
    ...
    mDisplay.setName("Display");
    addAndMakeVisible(mDisplay);
    ...
}

AulusKnob.h

class AulusKnob: public Component, public Timer
{
public:
    ...
    Value value;
    void setValue(int value);
    ...
}

AulusKnob.cpp

...
void AulusKnob::setValue(int val)
{
    value.setValue(val);
}
...

AulusDisplay.h

class AulusDisplay  : public Component, public Value::Listener
{
public:
    ...
    void valueChanged(Value& value) override;
    ...

AulusDisplay.cpp

...
void AulusDisplay::valueChanged(Value& value)
{
    //String t = ((Component)(value.getValueSource())).getName();
    DBG(value.toString().getIntValue());

    // How to access the Sender here ?
    // DBG(dynamic_cast<Component*>(value.operator juce::var().getObject()).getName());
    // DBG((Component)(value.operator juce::var().getObject()).getName());

    drawValue("Test", value.toString().getIntValue());
}
...

In the Method AulusDisplay::valueChanged() I would like to access the Sender Object which triggered the Listeners valueChanged() Method to access it’s Name or other Properties.

Thanks a lot!

Cheers
Dirk

Perhaps you can call a lambda in that valueChanged() function, and capture what you need when you initialize the lambda outside of the class?

Hi Matkat, how could it look like ?

here’s how you’d call a lambda when your Listener responds to the Value changing:

void AulusDisplay::valueChanged(Value& value)
{
    DBG(value.toString().getIntValue());

    if( lambda )
        lambda( value );

    drawValue("Test", value.toString().getIntValue());
}

Here’s how you’d add that lambda member variable to your listener class.

class AulusDisplay  : public Component, public Value::Listener
{
public:
    ...
    void valueChanged(Value& value) override;
    ...
    std::function<void(juce::Value)> lambda;
    ...
};

capture your Sender when you create your lambda. Here is an example capturing your Editor.

EightAudioProcessorEditor::EightAudioProcessorEditor(EightAudioProcessor& p)
    : AudioProcessorEditor(&p),
    processor(p),
    keyboardComponent(p.keyboardState, MidiKeyboardComponent::horizontalKeyboard),
    menuBar(this)
{
    ...
    mKnobA.setName("Knob A");
    mKnobA.value.addListener(&mDisplay);
    addAndMakeVisible(mKnobA);
    ...
    mDisplay.setName("Display");
    mDisplay.lambda = [safePtr = Component::SafePointer<EightAudioProcessorEditor>(this)](juce::Value value) mutable
    { 
        //do something with value and whatever else you captured in the lambda.
        //your editor was captured inside a safePtr.
        //you can call editor member functions from here, if you check that the editor is stil valid.
    }; 
    addAndMakeVisible(mDisplay);
    ...
}

and don’t forget to add this line at the beginning of the lambda’s body

if (!safePtr) return;

That’s the whole point of the SafePointer, in case the captured component was deleted in the meantime.

TBH I would use the capture as last resort, but it does work.

@daniel do you have an alternate solution to what they’re asking to be able to do? This is the only way that I can think of. Well, you could use Listener list, but that still requires a lambda… :thinking::thinking:

Thank you a lot, also Daniel! I implemented the Lambda as you wrote but I don’t see any value in that. I passed in a new Variable name to get a String Value from the Editor but it seems not to work as you wrote.

The name Variable is empty after calling the Lambda.

Thats how I implemented it:

PluginEditor.cpp

    ...
    mDisplay.lambda = [safePtr = Component::SafePointer<EightAudioProcessorEditor>(this)](juce::Value value, juce::String name) mutable
    {
        if (!safePtr) return;
        //do something with value and whatever else you captured in the lambda.
        //your editor was captured inside a safePtr.
        //you can call editor member functions from here, if you check that the editor is stil valid.
        name = "1234";
    };
    ...

AulusDisplay.h

    ...
    std::function<void(juce::Value, juce::String)> lambda;
    ...

AulusDisplay.cpp

...
void AulusDisplay::valueChanged(Value& value)
{
    //String t = ((Component)(value.getValueSource())).getName();
    DBG(value.toString().getIntValue());
    // DBG(dynamic_cast<Component*>(value.operator juce::var().getObject()).getName());
    // DBG((Component)(value.operator juce::var().getObject()).getName());
    
    String name = "";
    if (lambda)
        lambda(value, name);

    DBG("Name: " + name);

    drawValue("Test", value.toString().getIntValue());
}
...

If I set value inside the Lambda Function then it works for the value Variable. Wonder why it doesn’t work for the name Variable.

PluginEditor.cpp

    mDisplay.lambda = [safePtr = Component::SafePointer<EightAudioProcessorEditor>(this)](juce::Value value, juce::String name) mutable
    {
        if (!safePtr) return;
        //do something with value and whatever else you captured in the lambda.
        //your editor was captured inside a safePtr.
        //you can call editor member functions from here, if you check that the editor is stil valid.
        name = "1234";
        value = 10;
    };

AulusDisplay.cpp

void AulusDisplay::valueChanged(Value& value)
{
    DBG(value.toString().getIntValue());  // << Here the Value is between 1 and 127

    String name = "";
    if (lambda)
        lambda(value, name);

    DBG("Name: " + name);

    drawValue("Test", value.toString().getIntValue());  // Here the Value is 10 from the lambda Function
}

Maybe it has to do with the fact that the name Variable is not part of the Signature of the valueChanged() Method.

In the end I want to pass the Name of the Knobs:

PluginEditor.cpp

    mDisplay.lambda = [safePtr = Component::SafePointer<EightAudioProcessorEditor>(this)](juce::Value value, juce::String name) mutable
    {
        if (!safePtr) return;
        if (safePtr->mKnobA.mouseEntered) {
            name = safePtr->mKnobA.getName();
        } 
    };

If I use a Value instead of a String then it works.

Thank you both a lot for your Help !!

Thats how I implemented it finally:

AulusDisplay.h

    ...
    std::function<void(juce::Value, juce::Value)> valueChangedL;
    ...

AulusDisplay.cpp

...
void AulusDisplay::valueChanged(Value& value)
{
    // DBG(value.toString().getIntValue());
    
    Value name;
    if (valueChangedL)
        valueChangedL(value, name);

    // DBG("Name: " + name.toString());

    drawValue(name.toString(), value.toString().getIntValue());
}
...

PluginEditor.cpp

    ...
    mDisplay.valueChangedL = [safePtr = Component::SafePointer<EightAudioProcessorEditor>(this)](juce::Value value, juce::Value name) mutable
    {
        if (!safePtr) return;
        if (safePtr->mKnobA.mouseEntered) {
            name = safePtr->mKnobA.getName();
        }  
    };
    ...

I also figured out that Visual Studio doesn’t recognize the Members of the Editor if used via a SafePointer:

I understand that the reference to the Value object doesn’t help, and also there is no access path from the Value further, because it has neither a parent link (because it is not a Component), now a user value field where information could be transported.

Generally I would avoid this kind of loose coupling, as it is often hard to maintain. When the hierarchy is recreated, you need to make sure all connections are also recreated.

Instead of listening to the Value object in the editor I would add a Value in the AulusDisplay and listen to that one. And then I would call this in the constructor of the Editor, where aulus and knobA are both known:

mDisplay.value.referTo (mKnobA.getValueObject());

and implement the Value::Listener in AulusDisplay:

void AulusDisplay::valueChanged (Value&) override
{
    repaint();
}

That makes all listener adding simple straight forward.

@dschiller that’s nasty about VS. Xcode takes often long for that task, you wait 2-5 seconds for the auto complete sometimes, but at least they appear eventually.

1 Like

Thanks Daniel. I did that!

PluginEditor.cpp

EightAudioProcessorEditor::EightAudioProcessorEditor(EightAudioProcessor& p)
    : AudioProcessorEditor(&p),
    processor(p),
    keyboardComponent(p.keyboardState, MidiKeyboardComponent::horizontalKeyboard),
    menuBar(this)
{
    ...
    mKnobA.setName("Knob A");
    mKnobA.setType(AulusKnob::Type::KnobA);
    mKnobA.setLabel("Release");
    mKnobA.image = ImageCache::getFromMemory(BinaryData::knobA_png, BinaryData::knobA_pngSize);
    //mKnobA.value.addListener(&mDisplay);
    //mKnobA.mouseStates.addListener(&mDisplay);
    addAndMakeVisible(mKnobA);
    
    mDisplay.value.addListener(&mDisplay);
    mDisplay.value.referTo(mKnobA.value);  // How to listen to multiple Values
    mDisplay.value.referTo(mKnobB.value);  // How to listen to multiple Values
    mDisplay.valueChangedL = [safePtr = Component::SafePointer<EightAudioProcessorEditor>(this)](juce::Value value, juce::Value name) mutable
    {
        if (!safePtr) return;
        if (safePtr->mKnobA.mouseEntered) {
            name = safePtr->mKnobA.getLabel();
            value = safePtr->mKnobA.getValue();
            return;
        }
        if (safePtr->mKnobB.mouseEntered) {
            name = safePtr->mKnobB.getLabel();
            value = safePtr->mKnobB.getValue();
            return;
        }
        name = "CLEAR";
    };
    ...
}

That way I cannot assign multiple Values to the Display Listener.

Also I could not find the getValueObject() Method in

mDisplay.value.referTo (mKnobA.getValueObject());

Instead I use mDisplay.value.referTo(mKnobA.value);.

It’s empty because you passed by copy, not by reference, into the lambda.

1 Like

How to get the Reference instead of the Copy then ?

see the part on “pass-by-reference implementations” in the Lvalue References section

You are right Matkat, I can use

std::function<void(juce::Value, juce::String&)> valueChangedL;

instead of

std::function<void(juce::Value, juce::String)> valueChangedL;

And then it also works with a String instead of a Value.

Yup, and you can also pass that Value instance by reference too.

1 Like

Make sense.

AulusDisplay.h

std::function<void(juce::Value&, juce::String&)> valueChangedL;

Thanks a lot!