2
votes

I want to change the color of a single tab, please see screenshot below. The FOO_SUPtab should be red (right now only the button is), all others not.

Example

For the text color there is bar->setTabTextColor(index, QColor(Qt::red)), but not the whole tab. Setting the tab stylesheet for the tab widget changes the color of all tabs.

I have found a stylesheet to change the tab color here: https://stackoverflow.com/a/21687821/356726 but not for a single tab, also I need to be able to decide at runtime if the tab is red or not.

Just to make clear, the widget below shall remain black, the tab only red.

1
I assume that QTabWidget uses QTabBar to manage the tabs. (QTabWidget on woboq.org) Hence, I looked into QTabBar::paintEvent() on woboq. So, a solution could be to overload QTabBar and override QTabBar::paintEvent() tweaking the painting to your intention.Scheff's Cat

1 Answers

3
votes

One option would be to implement your own tab bar (as explained here).

Anyway, I find more useful and cleaner the use of a proxy style, since it allows you to partially override the painting without need to use inheritance for the tab bar. It will allow you also to easily apply the new style to existing controls.

It can be something like:

class TabBackgroundProxyStyle : public QProxyStyle {
public:
  TabBackgroundProxyStyle(const QString& base_style_name, const QMap<QString, QBrush>& backgrounds)
    : QProxyStyle(base_style_name),
      m_backgrounds(backgrounds) {
  }

  void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override {
    if (element == CE_TabBarTab) {
      if (auto tab = qstyleoption_cast<const QStyleOptionTab*>(option)) {
        if (m_backgrounds.contains(tab->text)) {
          QStyleOptionTab opt(*tab);
          opt.palette.setBrush(QPalette::Background, m_backgrounds[tab->text]);
          return QProxyStyle::drawControl(element, &opt, painter, widget);
        }
      }
    }
    QProxyStyle::drawControl(element, option, painter, widget);
  }

private:
  const QMap<QString, QBrush> m_backgrounds;
};

To use it, just create the style with the appropriate tabs-color mapping (examples using C++11):

auto theTabWidget = new QTabWidget();
for (int ii = 0; ii < 10; ++ii) theTabWidget->addTab(new QWidget(), QString("Tab %1").arg(ii + 1));
const QMap<QString, QBrush> backgrounds = {
  {"Tab 2", QBrush(Qt::red)},
  {"Tab 3", QBrush("#c0b050")},
};
theTabWidget->tabBar()->setStyle(new TabBackgroundProxyStyle("", backgrounds));

If your user interface allows the tab's text to change on runtime (e.g., on-the-fly translations, or the text is a filename...) then you must modify the map accordingly.

The use of the tab's label for indexing is because the style option doesn't store any other direct information about the tab (not even the associated widget, because QTabBar is in charge of rendering only the tabs, it is not the container).

Another option would be to check the tab's rectangle, not much time-consuming for tab bars with just a few dozens of tabs, and more versatile if you don't want to deal with labels:

class TabBackgroundProxyStyle : public QProxyStyle {
public:
  TabBackgroundProxyStyle(const QString& base_style_name, const QMap<int, QBrush>& backgrounds)
    : QProxyStyle(base_style_name),
      m_backgrounds(backgrounds) {
  }

  void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override {
    if (element == CE_TabBarTab) {
      if (auto tab = qstyleoption_cast<const QStyleOptionTab*>(option)) {
        auto tabBar = qobject_cast<const QTabBar*>(widget);
        for (auto index : m_backgrounds.keys()) {
          if (tab->rect == tabBar->tabRect(index)) {
            QStyleOptionTab opt(*tab);
            opt.palette.setBrush(QPalette::Background, m_backgrounds[index]);
            return QProxyStyle::drawControl(element, &opt, painter, widget);
          }
        }
      }
    }
    QProxyStyle::drawControl(element, option, painter, widget);
  }

private:
  const QMap<int, QBrush> m_backgrounds;
};

Use:

auto theTabWidget = new QTabWidget();
for (int ii = 0; ii < 10; ++ii) theTabWidget->addTab(new QWidget(), QString("Tab %1").arg(ii + 1));
const QMap<int, QBrush> backgrounds = {
  {1, QBrush(Qt::red)},
  {4, QBrush("#c0b050")},
};
theTabWidget->tabBar()->setStyle(new TabBackgroundProxyStyle("", backgrounds));

Full source code can be downloaded from https://github.com/cbuchart/stackoverflow/tree/master/54070408-change-color-of-single-qtabwidget-tab


IMPORTANT: The main drawback of this solution is that it doesn't mix well with existing stylesheet for tabs: you have to disable/comment the stylesheets for QTabBar::tab in order to be able to apply the style.