If I use a QScrollArea
in a normal window, all the subwidgets inside will look the same as if I had used a QWidget
instead.
However, if I use a QScrollArea
in a QTabWidget
, it won't retain the color scheme, and will be darker, as it inherits from QFrame
.
In the example below, on the left side, the widget and the scroll area look the same. On the right side, the widget inherits the color scheme of the tab widget, but the scroll area doesn't.
My goal is to make the scroll area inherit the color scheme, just as it if was a QWidget
, so that it doesn't become darker.
(this example uses the "Fusion" style)
What I tried:
Setting the stylesheet to background: transparent;
Problem: it makes checkboxes and radio buttons invisible. Using a CSS selector doesn't seem to work, QScrollArea { background: transparent; }
keeps the same color and only removes the border.
Setting the palette to background: palette(base);
Problem: it makes the color scheme too bright, like the inside of the text editor, and it removes the styling from the scroll bar. (The latter can be avoided if I apply it only to the viewport instead of the whole scroll area)
I tried various other palettes from QPalette
, none of them achieve the result I want. The basic QPalette:Window
looks like the original darker version, QPalette::Light
is too bright (just like the "base"), QPalette::MidLight
is darker than it was originally, all others are even darker.
Inheriting the palette
ui->scrollArea->viewport()->setPalette(ui->tabWidget->palette());
ui->scrollArea->setPalette(ui->tabWidget->palette());
These seemed intuitive but have no effect.
Another solution would be to create a custom palette, and define every single color manually, but I would like to avoid this, as it makes everything too inflexible, especially when using it on different systems.
There is a recent discussion about this issue on the Qt forums: https://forum.qt.io/topic/161488/when-setting-the-background-of-a-qscrollarea-to-transparent-widgets-inside-lack-their-frames/4?_=1742988450491&lang=en-US
The suggested solution is
auto scrollPalette = ui->scrollArea->palette();
scrollPalette.setColor(QPalette::Window, Qt::transparent);
ui->scrollArea->setPalette(scrollPalette);
But it does not work. It sets the colors properly, but it also erases the borders of buttons and checkboxes.
You need to explicitly set the autoFillBackground
property of both the viewport and the widget to false
.
All Qt scroll areas inherit from QAbstractScrollArea, which automatically calls setBackgroundRole()
of the viewport with the QPalette::Base
color role, and also sets its autoFillBackground
property to true
, forcing the widget to always draw its own background. This is what we normally see in item views (usually, a white background).
QScrollArea overrides that behavior by calling setBackgroundRole(QPalette::NoRole)
on the viewport; the backgroundRole()
(which is a function, not a property) is then used by the QStyle functions called by the painter to draw its background if the autoFillBackground
property is true, and that function always eventually walks the whole object's parent tree until a valid (not NoRole
) role is found.
Note that only few widgets call setBackgroundRole()
: many complex widgets do it only on their specific children (like scroll areas), and only a few actually do it on themselves (eg: QLineEdit). Despite the look, QTabWidget does not do that; I'll explain later the difference.
See the QWidget::backgroundRole
implementation in the sources:
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;
}
Also, there's an important hint in the setWidget()
documentation of QScrollArea:
The widget's autoFillBackground property will be set to
true
.
First of all, we should remember the purpose of a palette: it's simply a hint.
Exactly like a physical palette for a human painter, the "digital palette" is just the basic group of colors that the "painter" may choose to use at their own discretion. It's an instrument that may be used as expected, considered as reference, completely misused or ignored at all, just like any "instrument" or "tool": a piano normally has 88 keys, but you don't need to push all of them to play a beautiful piece; you may use a hammer as a doorstop, which makes it useful even if you're not "hammering" anything.
In Qt, like in many palette-based toolkits, the style may decide on its own which color of the palette use and how, or even completely ignore it.
Specifically, in the QStyles used on windows, tab widgets use a different color than the normal window background, which explains why QTabWidget has an almost white background despite not setting any background role for itself: when the style has to draw the background of a tab widget, it chooses to use a color that follows the default behavior of that system.
Setting the style sheet to an arbitrary background: transparent;
is not a valid approach. In general, generic properties (using QWidget
or *
selectors as well as not using a selector at all) should never be set in container widgets, because they will always be inherited by all child widgets. This is very important and Qt indirectly warns about doing it (as it's implied) in the "Sub-Controls" section of the QSS syntax docs:
Note: With complex widgets [...] if one property or sub-control is customized, all the other properties or sub-controls must be customized as well.
The above is also partially the cause for which simply doing QScrollArea {}
may not work as expected.
Trying to do background: palette(base)
is also inappropriate for the same reason.
Also, the related QPalette::Base
role is in fact what's being used for input widgets and item views, and any palette()
syntax in QSS will always use the QApplication palette, not the widget's one, nor the inherited one, and that's because setting a color property in QSS actually results in changing the widget's internal palette (see the warning at the bottom of the palette
property documentation).
Setting the palette based on another widget is completely pointless. Unless you explicitly set a palette on the "source" widget, the palette will still be the same, resulting in absolutely no change at all. The palette is normally shared (or propagated to children), and by default all widgets share the same QApplication palette.
Considering the "physical palette" metaphor above, it's like loaning the palette of another painter, hoping to be able to use colors like they do, even though you both bought the same identical colors.
Creating a custom palette is also inappropriate, as it may potentially affect other widgets unexpectedly. As said, the palette is just a hint of the possible color that may be used, and the same role could be used by other widgets for other purposes.
Also, a different style may use a different role (or color) for the same widget type, so changing the palette would not only be ineffective and inappropriate, but also just pointless.
While the OP solution of setting the style sheet on the viewport may be acceptable, it may not be desirable: whenever a widget is potentially affected by a style sheet (including inherited ones, and also whenever the widget itself isn't actually affected due to selector usage), Qt always implicitly sets an internal QStyleSheetStyle for it, based on the actual/default/inherited style of that widget.
It is common to use a custom QProxyStyle to override the default style behavior, but whenever QStyleSheetStyle is used, it almost completely ignores the proxy overrides eventually used in the proxy.
So, unless we're completely fine with using style sheets and their implications, that solution is not acceptable.
In reality, it should be enough to just consider what initially explained: since both the viewport and the widget get explicitly set their autoFillBackground
property, it should be enough to reset it:
ui->scrollArea->viewport()->setAutoFillBackground(false);
ui->scrollArea->widget()->setAutoFillBackground(false);
In case you're not using a Designer UI or you need to change the widget of the scroll area at runtime, then the second line should be either hardcoded after explicitly setting the new widget. Alternatively, for dynamic behavior, then you should check for child events, either subclassing QScrollArea and overriding viewportEvent()
or by installing an event filter on the viewport itself. Note, though, that the internal widget
member (used by widget()
) and the setAutoFillBackground()
call are always done after reparenting, so you may need to use a delayed behavior (probably by means of QTimer::singleShot()
).
The above should work as expected in both Qt5 and Qt6 and in compliant styles.
Still, consider that the relatively new "Windows11Style" has had issues since its introduction in Qt 6.7 (with some still being addressed at the time of this writing), so it is possible that you may still have issues with that, and it may be worth reporting it.