TabbedComponent and non default LookAndFeel

Has anyone used a TabbedComponent with a non-default LookAndFeel? It crashes with a SIGABRT on OSX at

TabbedButtonBar::updateTabPositions() line 409

const int overlap = lf.getTabButtonOverlap (depth) + lf.getTabButtonSpaceAroundImage() * 2;

The address of lf is the same as my LookAndFeel object.

My LookAndFeel is derived from LookAndFeel_V2 and I’ve implemented all the methods:

    int  getTabButtonOverlap (int tabDepth) override;
    int  getTabButtonSpaceAroundImage() override;
    int  getTabButtonBestWidth (TabBarButton&, int tabDepth) override;
    
    Rectangle<int> getTabButtonExtraComponentBounds (const TabBarButton& button, Rectangle<int>& textArea, Component& comp) override;
    void createTabButtonShape (TabBarButton& button, Path& p, bool /*isMouseOver*/, bool /*isMouseDown*/) override;
    void fillTabButtonShape (TabBarButton& button, Graphics& g, const Path& path, bool /*isMouseOver*/, bool /*isMouseDown*/) override;
    
    void drawTabButton (TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown) override;
    void drawTabButtonText (TabBarButton&, Graphics&, bool isMouseOver, bool isMouseDown) override;
    
    void drawTabbedButtonBarBackground (TabbedButtonBar&, Graphics&) override;
    void drawTabAreaBehindFrontButton (TabbedButtonBar& bar, Graphics& g, const int w, const int h) override;
    
    Button* createTabBarExtrasButton() override;

which are all currently implemented using the same code as in LookAndFeel_V2 for each method.

I’ve run into this before where I got around it by using a default LookAndFeel, but this time that’s not a viable option.

You can reproduce the issue in the JUCE demo by changing the Widgets demo:

class WidgetsDemo   : public Component
{
public:
    WidgetsDemo()
    {
        setOpaque (true);
        tabs.setLookAndFeel (&testLF);
    
        addAndMakeVisible (tabs);
    }

    void paint (Graphics& g) override
    {
        g.fillAll (Colours::white);
    }

    void resized() override
    {
        tabs.setBounds (getLocalBounds().reduced (4));
    }

private:
    DemoTabbedComponent tabs;
    LookAndFeel_V2      testLF;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WidgetsDemo);
};

Thanks,

Rail

Most likely casue would be that you've deleted your L+F object somehow, so it's using a dangling pointer..

Nope, did you test using the Widget demo as I described?

As I mentioned, I checked the address of lf and it was correct.

Rail

And it happens when the TabbedComponent is destroyed:

Rail

Seems to be an issue with the cleanup code when you destroy the TabbedComponent… by adding a destructor like this I can fix the problem:

class WidgetsDemo   : public Component
{
public:
    WidgetsDemo()
    {
        setOpaque (true);
        tabs.setLookAndFeel (&testLF);
    
        addAndMakeVisible (tabs);
    }
    
    ~WidgetsDemo()
    {
        tabs.setLookAndFeel (&LookAndFeel_V1::getDefaultLookAndFeel());  // Change the L&F before the TabbedComponent is destroyed.
    }

    void paint (Graphics& g) override
    {
        g.fillAll (Colours::white);
    }

    void resized() override
    {
        tabs.setBounds (getLocalBounds().reduced (4));
    }

private:
    DemoTabbedComponent tabs;
    LookAndFeel_V2      testLF;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WidgetsDemo);
};

Rail

Try swappng the order of these two lines:


DemoTabbedComponent tabs;
LookAndFeel_V2 testLF;

Hi Dave,

Yes that works by changing the order of destruction… very seldom run into that kind of issue.

Cheers,

Rail

The order of your member variables influences the order with which they are constructed and deconstructed. The objects are constructed in order of ascending line number and deconstructed in reverse order. If you destroy your lookAndFeel before destroying the tab component then the tab component will have a reference to a dangling pointer. 

ha! just had the very same bug, thanks for the coding lesson!