Button not shown when use in ListBoxModel

Hello.

I try to draw button in a ListBox. But it doesn’t show. Area prepared for it, when I use removeFromLeft, but no button shown. Other elements like AudioThumbnail or rectangles are drawn.

It shows if I do not use ListBox.

Model:

class TracksListModel  : public ListBoxModel
{
public:
    virtual int getNumRows() override;
    void paintListBoxItem (int rowNumber, Graphics& g,
                           int width, int height, bool rowIsSelected) override
    {
        tracks[rowNumber]->setBounds(0, rowNumber * height, width, height);
        tracks[rowNumber]->paint(g);
    }
    void add(TrackComponent *track);
private:
    OwnedArray<TrackComponent> tracks;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TracksListModel)
};

Component:

class TrackComponent : public Component 
{
public:
    void paint(Graphics &g) override
    {
        auto area = getLocalBounds();
        auto top = area.removeFromTop(20);
        g.setColour (Colours::green);
        g.fillRect (top);

        playButton.setBounds(area.removeFromLeft(100));
        addAndMakeVisible(&playButton);

        paintIfFileLoaded(g, area);
    }
private:
    AudioThumbnail* thumbnail;
    TextButton playButton;

    void paintIfFileLoaded(Graphics& g, Rectangle<int> area)
    {
        g.setColour (Colours::red);
        thumbnail->drawChannels (g,
                                area.removeFromTop(80),
                                0.0,
                                thumbnail->getTotalLength(),
                                1.0f);
    }

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TrackComponent)
};

Is there any mistake I made?

This is what I have now:
2020-07-02-084727_601x426_scrot

Ok, if I change this:

tracks[rowNumber]->paint(g);

to this:

tracks[rowNumber]->paintEntireComponent (g, false);

button drawn.

But there is no interaction with button on hover, and on click.

Any ideas how to make it work?

The rule written in stone is: never alter the UI from within your paint methods.

In your code, for example, don’t call setBounds() and neither addAndMakeVisible() from within paint().

If you need to react to changes in position or size, override the moved() or the resized() methods of Component instead.

But what if I want to react to mouse events?

If you want to react to mouse events, you capture the event in its callback, change whatever you need to change, and call repaint(). The painting mechanism is asynchronous. You call repaint() on a component to mark it as dirty, so that soon, but later, the message loop will call paint() on it. You never call paint() directly, and you never call things from paint() that would call repaint(). Think about it -paint() calls something that calls repaint(), that makes paint() be called later, that calls something that calls repaint(), that makes paint() be called later… and so on. paint() should only draw whatever is in the component at any moment, without changing its content.

In addition to what @kamedin said, also note that Component, which is the base class of all JUCE widgets, is also derived from MouseListener which has all sorts of callbacks for mouse events.

If you want to react to mouse events on your own class derived from Component, you can simply override those callbacks coming from MouseListener.

If you want to react to mouse events coming also from other widgets, you can register yours as the listener for those events too via the other Component's addMouseListener() method, and inside the callbacks you can determine which Component received the event by looking at the eventComponent member of their MouseEvent argument

Actually I’m not want to react, I need reaction from children components as it work if I just add TextButton to top MainComponent.

Yes, I tried, but mouseMove is not triggered in ListBox’s components.

I see your point. I can devise two approaches

  1. Add a mouse listener for your ListBox, catching also events for child components (it’s an argument given to addMouseListener() ) then every time you receive a mouse event you can find which row component it affects using ListBox::getRowContainingPosition() and ListBox::getComponentForRowNumber(), then act accordingly

  2. Depending on the amount of mouse interaction that you want to implement, the approach above can become tricky, in which case I’d abandon the idea of having tracks as rows of a ListBox, and implement them as Components that you directly manage inside a parent panel that serves as their “container”, which you can embed in a Viewport to make it scrollable

1 Like

I think I will try second approach you suggested.

Thank you for your help.

1 Like