Preliminary information: I don't want to use stylesheets.
I use QPalette
to change the text (foreground) color of a QLabel
as follows:
QLabel * label = new QLabel("foobar", parent);
QPalette pal = label->palette();
pal.setColor(label->foregroundRole(), Qt::blue);
label->setPalette(pal);
And it works as expected.
Now I insert this label into a QListWidget
:
QListWidget * list_widget = new QListWidget(parent);
QListWidgetItem * item = new QListWidgetItem(list_widget);
list_widget->addItem(item);
list_widget->setItemWidget(item, label);
item->setSizeHint(label->sizeHint());
Issue: Then the text color is ignored and the default (black) is used.
Question: Is there a way to force the QListWidget
to display the given widgets with their dedicated QPalette
? If so, how ?
I tried to replace the call to the label's backgroundRole()
with QPalette::WindowText
directly, but it didn't change anything.
Of course, in my real use-case, I have a custom widget which contains QLabel
s. It would be a non-sense to use QListWidget::setItemWidget()
with a single QLabel
since QListWidgetItem
already supports colored text (normally).
You need to explicitly set the foreground role:
label->setForegroundRole(label->foregroundRole());
Only a few widgets have explicitly set background and foreground roles (for instance QPushButton sets QPalette::Button
and QPalette::ButtonText
respectively), otherwise their internal values are QPalette::NoRole
.
When the related backgroundRole()
and foregroundRole()
functions are called, they will return the internal role set (eventually explicitly set through the setter functions) unless it's NoRole
; if it is, then they will return a suitable role (but not NoRole
).
From the backgroundRole()
docs:
If no explicit background role is set, the widget inherts its parent widget's background role.
The above means that if the widget has a NoRole
background role, it will walk the parent tree until any parent returns a role that is not NoRole
, or until it reaches the top level window and eventually return QPalette::Window
. Here is the source of that function:
QPalette::ColorRole QWidget::backgroundRole() const
{
const QWidget *w = this;
do {
QPalette::ColorRole role = w->d_func()->bg_role;
if (role != QPalette::NoRole)
return role;
if (w->isWindow() || w->windowType() == Qt::SubWindow)
break;
w = w->parentWidget();
} while (w);
return QPalette::Window;
}
From the foregroundRole()
docs:
If no explicit foreground role is set, the function returns a role that contrasts with the background role.
This means that if a NoRole
foreground is set it will call backgroundRole()
(doing everything explained above) and return a suitable role that contrasts with it. Here is how it works:
QPalette::ColorRole QWidget::foregroundRole() const
{
Q_D(const QWidget);
QPalette::ColorRole rl = QPalette::ColorRole(d->fg_role);
if (rl != QPalette::NoRole)
return rl;
QPalette::ColorRole role = QPalette::WindowText;
switch (backgroundRole()) {
case QPalette::Button:
role = QPalette::ButtonText;
break;
case QPalette::Base:
role = QPalette::Text;
break;
case QPalette::Dark:
case QPalette::Shadow:
role = QPalette::Light;
break;
case QPalette::Highlight:
role = QPalette::HighlightedText;
break;
case QPalette::ToolTipBase:
role = QPalette::ToolTipText;
break;
default:
;
}
return role;
}
Since QLabel is among those widgets that have no default roles set, then the following happens when used standalone:
NoRole
foreground, it calls backgroundRole()
;NoRole
background, it looks up in the widget tree until it finds a parent with a suitable valid role;QPalette::Window
;QPalette::WindowText
, and since the switch()
above doesn't find any other match, it returns that;That assumed role is not only the same you get when doing pal->setColor(label->foregroundRole(), Qt::blue);
, but also and most importantly the one used by QPainter when it draws the label.
When you add the label to the view, things change:
QPalette::Base
, and the viewport is the parent of all widgets set for items (including delegate editors);backgroundRole()
of the label will therefore return the backgroundRole()
of the viewport, which was set to QPalette::Base
;foregroundRole()
will then return the matching role for that background, which is QPalette::Text
;In order to override that, the solution looks unintuitive other than pointless, but it makes sense considering what explained above: you need to explicitly set the palette role for the foreground, based on the "assumed" one:
label->setForegroundRole(label->foregroundRole());
You can do the above before or after setting the label to the view, but it's mandatory that you then do pal.setColor()
and label->setPalette(pal)
after doing setForegroundRole()
, because backgroundRole()
(and, therefore, foregroundRole()
) will have a different result depending on the parent.