CatPlusPlus CatPlusPlus - 2 months ago 86
C++ Question

Qt (C++): dynamically style an individual tab in QTabWidget

I know, that this question has been discussed here and on other sites, but there has not been a really solution yet, although I think, that I'm not the only one with this problem:

How can I individually and dynamically access a single tab (not its content resp. the widget in the tab) for styling purposes, such as changing the background color or adding graphical effects to it?
An application could be to notify the user, that a tab requires his attention, by letting it flash in another color (like in the windows task bar, if a window wants to get focus).
There is a function to change the text color, why not more?
Stylesheets can be used to access the selected, the first etc. tab, but not a specific one by its index.
Some people talked about subclassing QTabBar, but I do not know, how this will lead to the desired solution.
Is there any possibility to achieve this and if yes, please provide an example.

Answer Source

In order to access each style of each QTabBar tab you must overwrite the paintEvent() method of it.

The generic way of doing this should have the following structure:

void paintEvent(QPaintEvent *event){
    QStylePainter painter(this);
    QStyleOptionTab opt;

    for(int index = 0; index < count(); index++)
    {
        initStyleOption(&opt,index);
        /*Here make the changes*/
        painter.drawControl(QStyle::CE_TabBarTabShape, opt);
        painter.drawControl(QStyle::CE_TabBarTabLabel,opt);
    }
}

In this part I show an example of how to create a QTabWidget where it shows a tab that blinks and only ends the blinking if we click on that tab

TabBarAlert:

class TabBarAlert : public QTabBar
{
    int indexAlert = -1;
    QColor mColor;
    Q_OBJECT
public:
    TabBarAlert(QWidget *parent = Q_NULLPTR):QTabBar(parent)
    {
        mColor = Qt::red;
    }
    void setIndexAlert(int index){
        if(indexAlert == index)
            return;
        indexAlert = index;
        update();
    }

    int getIndexAlert() const{
        return indexAlert;
    }

    QColor getColor() const{
        return mColor;
    }
    void setColor(const QColor &color){
        if(color == mColor)
            return;
        mColor = color;
        update();
    }

protected:
    void paintEvent(QPaintEvent *event){

        if(indexAlert> -1 && indexAlert < count()){
            QStylePainter painter(this);
            QStyleOptionTab opt;

            for(int i = 0;i < count();i++)
            {
                initStyleOption(&opt,i);

                if(indexAlert == i)
                    opt.palette.setColor(QPalette::Button, mColor);
                painter.drawControl(QStyle::CE_TabBarTabShape, opt);
                painter.drawControl(QStyle::CE_TabBarTabLabel,opt);
            }
        }
        else{
            QTabBar::paintEvent(event);
        }
    }

};

TabWidgetAlert:

class TabWidgetAlert : public QTabWidget
{
    TabBarAlert *tb;
    QTimer *timer;
    bool on = false;
    int indexAlert = -1;

    Q_OBJECT
public:
    TabWidgetAlert(QWidget *parent = Q_NULLPTR):QTabWidget(parent)
    {
        tb = new TabBarAlert(this);
        setTabBar(tb);
        tb->setColor(Qt::black);

        /*
        *Disable the alert if the current tab matches the alert tab.
        */
        connect(this, &TabWidgetAlert::currentChanged, [this](int index){
            if(index == tb->getIndexAlert()){
                tb->setIndexAlert(-1);
                on = false;
                timer->stop();
           }
        });

        timer = new QTimer(this);

        /*
        *Create the blink
        */
        connect(timer, &QTimer::timeout, [this](){
            tb->setIndexAlert(on? indexAlert: -1);
            on = !on;
        });
    }

    void setAlert(int index){
        indexAlert = index;
        timer->start(100);
    }
};

The complete example can be found at the following link