[SOLVED] How to access Graphics Context from another class indirectly?

I have a Component with it’s paint Method and want to draw something on it. I use a public Method to get some Data I need for the drawing.

To be exact, the Component is a Display and from my Knob Class I want to access the Display and draw the Value Change on it.

image

AulusDisplay.cpp

void AulusDisplay::drawValue(String text, int value)
{
    // How to access the Drawing Context here
    // Or how to pass Parameter to the `paint` Method
}

I do not know how I can access the Graphics Context from the paint Method or pass Data to the paint Method.

For internal Methods it’s easy by just passing the Graphics context from paint:

AulusDisplay.cpp

void AulusDisplay::paint (juce::Graphics& g)
{
    ...
    drawValue(g, "Velocity", 127);
}
...
void AulusDisplay::drawValue(Graphics& g, String text, int val)
{
    // Do something with `g`
}

I tried to pass Data to paint and duplicated the Methods to switch within paint but I do not know why I can not convert String to an int.

AulusDisplay.h

class AulusDisplay  : public juce::Component
{
public:
    ...
    void drawValue(String text, int value);

private:
    std::map<String, String> functionData;
    bool drawValueFunction = false;
    ...
    void drawValue(Graphics& g, String text, int value);
    ...
};

AulusDisplay.cpp

void AulusDisplay::drawValue(String text, int value)
{
    functionData["text"] = text;
    functionData["value"] = (String)value;
    drawValueFunction = true;
    repaint();
}
...
void AulusDisplay::paint (juce::Graphics& g)
{
    ...
    if (drawValueFunction) {
        drawValue(g, (String)functionData["text"], (int)(functionData["value"]));
        drawValueFunction = false;                      ^
    }
}

The Error I get is:
juce::String::operator bool': cannot access private member declared in class 'juce::String

I also tried to memorize g by assigning it to a private Graphics variable but I couldn’t figure out how to do that and it seems the context is inaccessible and I cannot assign the context within the paint Method to a private class variable. That would be the easiest way.

Thanks a lot for all kind of help!

Cheers
Dirk

I also tried to memorize g by assigning it to a private Graphics variable but I couldn’t figure out how to do that and it seems the context is in inaccessible and I cannot not assign the context within the paint Method to a private class variable. That would be the easiest way.

That sounds bad. You can’t use a Graphics object after the paint method has finished running anway.

You wrote quite a lot, but are you saying you want to draw the parameter value / knob position in your display?

To convert a string to an int, use string.getIntValue()

Assuming I’m understanding the objective correctly:

  • Make your display listen to the audio parameter
  • When the parameter changes draw the value on the display

Do you want to show the knob being currently adjusted, or just some specific knob on the display?

Hey Jim, yes you point in a good Direction. For sure on a certain Point the Display should listen to the Knobs. An I also was afraid to memorize the context. Now I know it’s a bad approach.

Yes Jim, all knobs should react on the Display. My first attempt was to access the Display Class with drawing Functions but in a later Stage I should use Change Listener.

If I use

drawValue(g, functionData["text"], String::getIntValue(functionData["value"]));

I get the Error:

a nonstatic member reference must be relative to a specific object

Ah right, ok.

So you need to call .getIntValue() on your string object. You can only do String::getIntValue if getIntValue is a static function.

so this is valid code:

String x{"1"}; 
int y = x.getIntValue(); 

But not

String x{"1"}; 
int y = String::getIntValue(x);
1 Like

Thanks Jim! Misunderstood it because of the string.. I thought you mean the Class.

This works:

drawValue(g, functionData["text"], functionData["value"].getIntValue());

I thought (int)myString is the same like myString.getIntValue().

It doesn’t draw to the Display as expected. Without any Change Listener inside the Display Component it seems not to work.

You are looking at it the wrong way round. The paint() routine is not a call, it is a callback.
The system goes: “Can you draw me a sheep? Here is a canvas”. When your drawing code returns, you returned the canvas, otherwise the job wasn’t done properly. The canvas is a metaphor for the Graphics context.

What you can do is, pass the canvas on to subroutines, but the rule stays the same, once the subroutine returns, it returns the canvas (not literally, it’s returning void of course).

1 Like

Hey Daniel, thanks for that! I think I need to impement a Listener to the Display Component. Like here: https://docs.juce.com/master/tutorial_listeners_and_broadcasters.html

I will try to implement it.

When a value changes, you need to indirectly trigger a paint() by calling repaint().

For when to call this there exist different approaches:

  • In a Timer event, which makes sense e.g. for constantly changing values like a level meter
  • From a ChangeListener callback
  • using the SliderAttachments, that takes care of synchronising parameters and sliders bidirectionally
1 Like

Thanks Daniel. I already saw that I need to use repaint() to trigger the Components paint() Method. Unfortunetly my Knob Component is not using the Slider class because for me it is better to implement my Interactions inside my own Knob Component. But it seems without the Slider Implementation I can not implement a Listener. Is there a way to write my own Listener in a pure Component derived Class ?

Yes absolutely. The ListenerList and the call method there are templated to allow new Listener types.

But maybe that is not even needed. Only if you need that kind of loose coupling creating an extra API makes sense.

A great class for your use case is the juce::Value

Your knob could have a Value member and the extra display would listen to this Value member. This is often used in juce code itself.

1 Like

I added the Value Member to my Knob Comonent:

AulusKnob.h

class AulusKnob: public Component, public Timer
{
public:
    ...
    Value value;
...
};

And to my Display Component I derived from Value::Listener and implemented the virtual valueChanged(...) Method:

AulusDisplay.h

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

In my Main Component I try to add the Listener but it seems not to accept the Display’s Value Listener:

PluginEditor.cpp

EightAudioProcessorEditor::EightAudioProcessorEditor(EightAudioProcessor& p)
    : AudioProcessorEditor(&p),
    processor(p),
    keyboardComponent(p.keyboardState, MidiKeyboardComponent::horizontalKeyboard),
    menuBar(this)
{
    ...
    //mKnobA.onValueChange = [&]() { processor.changeSamplerRelease(); };

    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);
                                       ^
    addAndMakeVisible(mKnobA);
    ...
}

I get an Error:

no suitable conversion function from "AulusDisplay" to "juce::Value::Listener *" exists

Maybe mDisplay is not a pointer? Did you try &mDisplay?

1 Like

Yes with &mDisplay it works:

PluginEditor.cpp

mKnobA.value.addListener(&mDisplay);

But somehow the repaint() Method doesn’t trigger paint().

I put a Breakpoint into the Display Component paint() Method.

The last Call I can follow is:

AulusDisplay.cpp

void AulusDisplay::drawValue(String text, int value)
{
    functionData["text"] = text;
    functionData["value"] = (String)value;
    drawValueFunction = true;
    repaint();
    ^
}

The repaint() get called but it doesn’t invoke the paint() Method.

Sorry, mixed up the methods.

Instead of inspecting AulusDisplay::paint() I was inspecting another Function AulusDisplay::drawText() which holds already a lot of Graphic Stuff so I was thinking this is my paint Method. Sorry for that.

Now it works perfectly. Thank you all so much especially you Daniel!

EightBeta2B

How can I get the Source Components Name ( Component.getName() ) inside the valueChanged() Method, I have overriden from Value::Listener inside the Display Component ?

Here is the Solution to get the Source Components Values: Is there a way to access the Object which triggers a Value Change to a Listener?