How to add a GUI component and force repaint

I’m trying to add a GUI button at runtime. I’ve read through many posts about how to do this, but I can’t seem to get it to work. Here is my code:

#pragma once
#include <JuceHeader.h>

class MainComponent  : public juce::Component, public juce::Timer
{
public:
    MainComponent()
    {
        addButtonBtn.onClick = [this] { addButton(); };
        addAndMakeVisible ( addButtonBtn );
        setSize (400, 600);
        startTimerHz(30);
    }

    ~MainComponent() override {}
    
    void paint(juce::Graphics& g) override
    {
        g.fillAll(juce::Colours::black);
        isDirty = false;
    }
    
    void resized() override
    {
        addButtonBtn.setBounds(10, 10, 100, 50);
        for (int i=0; i < buttons.size(); i++)
        {
            int yPos = (i * 110) + 120;
            buttons[i]->setBounds(0, yPos, getWidth(), 100);
        }
    }
    
    void timerCallback() override
    {
        if (isDirty)
        {
            resized();
        }
    }
    
    void addButton()
    {
        buttons.push_back(std::make_unique<juce::TextButton>());
        addAndMakeVisible(*buttons.back());
        isDirty = true;
    }
    
    std::vector<std::unique_ptr<juce::TextButton>> buttons;
    juce::TextButton addButtonBtn{"Add a button"};
    bool isDirty;
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

When I press the “Add a button” button, new buttons only appear if I resize the window. What am I doing wrong?

Fixed. I just moved isDirty = false to after the resized() call in the timerCallback and it works:

void timerCallback() override
{
     if (isDirty)
     {
          resized();
          isDirty = false;
     }
}

What is the reason for the isDirty flag?
The Component manages the dirty areas automatically.
By calling repaint() it is set as dirty and the OS will issue a paint() callback as soon as it sees fit.
You can even mark only parts of the component dirty by calling

juce::Rectangle<int> localRectangle (20, 20, 20, 200);
repaint (localRectangle);

Which will cause the paint call to clip all paint operations outside that rectangle (i.e. does’t bother drawing them). It will still run your whole paint() callback. And the OS might decide to coalesce multiple repaint calls and regions rogether.

I used the isDirty flag to call resized() inside of the timer thread, rather than the main thread. I read that you should never call repaint() or resized() from the main thread.

Also, I have to call resized() rather than repaint() or it does not work in this example. If there is a better way to add the button at runtime and get it to show please let me know :slightly_smiling_face:

I’m curious where you read that you should never be calling repaint()/resized() from the message thread? The message thead is the only thread you should be calling these functions from!

Btw, the Timer class invokes its callbacks on the message thread, so your timerCallback() code is running on the message thread. Essentially, this is just a more roundabout way of calling resized() directly in your addButton() method. I would get rid of the isDirty flag and just call resized() at the end of addButton().

paint() and resized() are both callbacks, i.e. the system calls them to notify you.

You get a resized() callback as consequence of a setBounds() or setSize() call. That is the right place to layout the child components, i.e. calling setBounds() on each of them to react to the changed size/position.

You get a paint() callback whenever the OS thinks the component is dirty and needs to be painted. That can be a consequence of you calling repaint(), but it can also be from resizing the window, dragging another window over it, un-minimising it, etc.

2 Likes

I guess I don’t fully understand which thread is which. After rereading, the forum post (https://forum.juce.com/t/calling-component-repaint-in-a-process-thread/9878) said never to call gui stuff from a process thread. I thought that was the main thread of the mainComponent above. I did not know it was a message thread (or even what a message thread is). I’ve got a lot to learn.

No worries! The message thread is the “main” thread where the GUI drawing occurs. The code in your Component classes like the constructor, resized(), paint(), etc. are called on the message thread.

The post you linked is discussing an audio app/plug-in, where the audio processing code runs on a separate thread. For example, if you are creating a plug-in, the AudioProcessor::processBlock() function is called on the “processing” thread, and your plug-in would crash if you tried calling a Component's resized() function there.

1 Like