Dynamic 2D array of TextButtons

maybe the question is a little trivial, but my c++ skills are pretty rough at the moment.
I’m trying to create a dynamic 2D array of TextButtons (or any other button) and fill it with TextButton instances and just can’t get it to work.

Which container type is recommended for this and what would the syntax look like? I already tried with std::vector and with juce::OwnedArray but I fail with both.

thanks in advanc

How is it failing?

I usually use juce::OwnedArray<...> for this sort of thing, the alternative would be std::vector<std::unique_ptr<...>>.

As for the layout, a juce::Grid is ideal for 2D arrays of widgets.

This is currently failing code:

juce::OwnedArray<juce::OwnedArray<juce::TextButton>> stepCoreButtons;

and

stepCoreGridComponent::stepCoreGridComponent(int nColumns, int nRows) 
    : coreGridDimensions(nColumns, nRows)
{
    for (int row = 0; row < coreGridDimensions.second; row++)
    {   
        juce::OwnedArray<juce::TextButton> rowButtons;

        for (int column = 0; column < coreGridDimensions.first; column++)
        {
            auto newTextButton = new juce::TextButton("test");
            addAndMakeVisible(newTextButton);
            rowButtons.add(&juce::TextButton());
        }
       stepCoreButtons.add(&rowButtons);
    }
}

and as a result after building it the code triggers a breakpoint in “delete_scalar.cpp” that is some kind of Microsoft code

Looks like your problem is in the inner for loop. You create the TextButton and assign it to newTextButton, but you don’t actually add that button to your OwnedArray. Instead, you add a reference to a default-constructed temporary (which is a bad idea!):

rowButtons.add(&juce::TextButton());

If you change that line to:

rowButtons.add(newTextButton);

Then everything should work.

1 Like

Oh, i see, that was exceptional dumb. Thank you!

You should avoid using raw pointers like this altogether really… You should be using std::unique_ptr instead:

auto newTextButton = std::make_unique<juce::TextButton> ("test");
addAndMakeVisible (*newTextButton);
rowButtons.add (std::move (newTextButton));
2 Likes

Now the code runs but unfortunately the buttons don’t show up after addAndMakeVisible

After calling addAndMakeVisible(), you need to call setBounds() on each button in order to set their position. This is often done in the container component’s resized() method. For example:

// Inside your Component class that contains the rowButtons:
void resized() override
{
    auto bounds = getLocalBounds();

    for (auto* button : rowButtons)
        button->setBounds (bounds.removeFromTop (50));
}
1 Like

@connorreviere
I already did this in the creation loop and also tried your suggestion but it didn’t make a difference. It seems like in the creation loop the reference to the created TextButtons is lost. E.g. after 2 rows I see this in the debugger:

Again sorry for my lack of knowledge regarding modern c++, I had lived the last years in the somewhat friendly python world (and the not so friendly java world), but when it comes to real-time audio there seems to be no way around c++.

Doesn’t seem right to me…

You’re passing a raw pointer to a local variable. At the end of the for-loop, rowButtons is deleted so that pointer you added to stepCoreButtons is null.

Instead you should do something like:

auto rowButtons = std::make_unique<juce::OwnedArray<juce::TextButton>> ();

// ...

stepCoreButtons.add (std::move (rowButtons));

I finally made it work by switching from OwnedArrays to vector<unique_ptr>> like this:

stepCoreGridComponent::stepCoreGridComponent(int nColumns, int nRows) 
    : coreGridDimensions(nColumns, nRows)
{
    for (int row = 0; row < coreGridDimensions.second; row++)
    {   
        std::vector<std::unique_ptr<juce::TextButton>> rowButtons;

        for (int column = 0; column < coreGridDimensions.first; column++)
        {
            auto newTextButton = std::make_unique<juce::TextButton>("test");
            newTextButton->setBounds(column * 100, row * 100, 100, 100);
            addAndMakeVisible(*newTextButton);
            rowButtons.push_back(std::move(newTextButton));
        }

       stepCoreButtons.push_back(std::move(rowButtons));
    }
}

are there any strong reasons to use an OwnedArray and not use a vector?

juce::OwnedArray is functionally identical to std::vector<std::unique_ptr>. Internally juce::OwnedArray using a juce::Array<std::unique_ptr>.

If you’re more comfortable using the vector approach then go for that - the biggest advantage I’ve found to using JUCE’s arrays is that it’s much easier to remove items than with STL containers.

My comment wasn’t quite right. juce::OwnedArray uses a juce::ArrayBase internally, as does juce::Array.

juce::ArrayBase uses a juce::HeapBlock to store elements.

1 Like