TableListBoxModel::paintCell() called getNumRows() + 1 times when vector size is odd (should be getNumRows() times)

I inherited the TableListBoxModel class and paintCell is giving me trouble. I have found a workaround but I either don’t understand how it works or something is wrong.

I have included my code at the bottom (with some irrelevant bits ommitted). Alternatively here is a copy of my code in pastebin: https://pastebin.com/y63EypUw

I am setting the getNumRows() equal to the length of a vector of strings which i push elements to in my constructor. It is consistently returning the expected values. When the size of my vector of strings is an odd number I get a “vector subscript out of range” error. No error when it’s even. I found that the error was happening in paintCell(). It’s being called from somewhere and must interact with getNumRows(). I checked the documentation which says “Note that the rowNumber value may be greater than the number of rows in your list, so be careful that you don’t assume it’s less than getNumRows.” OK, not entirely sure why that’s important, so I logged these variables in paintCell.

Log when I pushed 2 strings to the vector (no error, all tracks are drawn):

PlaylistComponent::paintCell called
rowNumber: 0
getNumRows: 2
PlaylistComponent::paintCell called
rowNumber: 1
getNumRows: 2

This was the log when I pushed 3 strings to the vector (error):

PlaylistComponent::paintCell called
rowNumber: 0
getNumRows: 3
PlaylistComponent::paintCell called
rowNumber: 1
getNumRows: 3
PlaylistComponent::paintCell called
rowNumber: 2
getNumRows: 3
PlaylistComponent::paintCell called
rowNumber: 3
getNumRows: 3

So rowNumber appears to be the index, which explains why 3 would be out of range for a vector of length 3, but why is paintCell only called twice for a vector of length 2, but 4 times for a vector of length 3? I tried to sift through the call stack but couldn’t really pinpoint where the number of iterations is defined. I read somewhere on the forum it should be defined by getNumRows. Ultimately, remembering the warning of the documentation, I put g.drawText inside an if statement checking that rowNumber < getNumRows(). If I don’t use that if statement, it results in a terminal “vector subscript out of range” error. Why is the number of iterations equal to getNumRows() + 1 when the vector size is odd?

#include <JuceHeader.h>
#include "PlaylistComponent.h"
 
//==============================================================================
PlaylistComponent::PlaylistComponent()
{
    // In your constructor, you should add any child components, and
    // initialise any special settings that your component needs.
    trackTitles.push_back("Track 1");
    trackTitles.push_back("Track 2");
    trackTitles.push_back("Track 3");
    trackTitles.push_back("Track 4");
    trackTitles.push_back("Track 5");
    trackTitles.push_back("Track 6");
    trackTitles.push_back("Track 7");
    trackTitles.push_back("Track 8");
    trackTitles.push_back("Track 9");
 
    tableComponent.getHeader().addColumn("Tracks", 1, 150);
    tableComponent.setModel(this);
 
    addAndMakeVisible(tableComponent);
}
 
int PlaylistComponent::getNumRows()
{
    return trackTitles.size();
}
 
void PlaylistComponent::paintRowBackground(juce::Graphics& g,
                                           int rowNumber,
                                           int width,
                                           int height,
                                           bool rowIsSelected
                                          )
{
    if (rowIsSelected)
    {
        g.fillAll(juce::Colours::orange);
    }
    else
    {
        g.fillAll(juce::Colours::darkgrey);
    }
}
 
void PlaylistComponent::paintCell(juce::Graphics& g,
                                  int rowNumber,
                                  int columnId,
                                  int width,
                                  int height,
                                  bool rowIsSelected
                                 )
{
    DBG("PlaylistComponent::paintCell called");
    DBG("rowNumber: " + std::to_string(rowNumber));
    DBG("getNumRows: " + std::to_string(getNumRows()));
    if (rowNumber < getNumRows())
    {
        g.drawText(trackTitles[rowNumber],
            2,
            0,
            width - 4,
            height,
            juce::Justification::centredLeft,
            true
        );
    }
}

Bump

While I cannot answer to this question directly, let me add that I’ve seen this happen also in situations where it didn’t depend on the row number being even or odd.

In my case the index was consistently less than getNumRows() only if the display scale set in Windows control panel was set to 100%.

For greater values (e.g. 125%), users were having crashes and it turned out to be caused by the out-of-bounds index exactly as in your case. I noticed that the documentation specifically mentioned about that possibility so I added the same check as yours, which seems the only meaningful thing to do.

Perhaps the reason why painting of “out-of-bounds” indices is attempted, is that some of the logic for repainting the rows is delegated to the OS, which sometimes may happen to attempt the painting of an additional row that’s not really there? :man_shrugging: (I haven’t dug the code so I really have no idea)

Yeah, I always need to add the check @yfede mentioned.
I guess this happens because of the way the internal classes used by ListBox calculate what rows need to be updated.

That’s good context. Still a bit puzzling, but I at least feel better about my current solution. Thanks for chiming in.

Thanks to @masshacker as well for chiming in on this.

It is perfectly ok to check if rowNumber is not beyond getNumRows in every paintCell. Please read this message:

1 Like