最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

css - How to make a QScrollArea inherit its color scheme in a QTabWidget as if it was a QWidget? - Stack Overflow

programmeradmin6浏览0评论

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:

  1. 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.

  2. 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.

  3. 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.

Update:

There is a recent discussion about this issue on the Qt forums: ;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.

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:

  1. 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.

  2. 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.

  3. 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.

Update:

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.

Share Improve this question edited Mar 26 at 12:47 Chait 1,3693 gold badges22 silver badges37 bronze badges asked Mar 25 at 7:54 vszvsz 4,9557 gold badges43 silver badges88 bronze badges 16
  • Setting the palette is pointless: unless you explicitly changed the palette, children will always inherit that of the parent, and since the scroll area (and its viewport) are children of the tab widget, it wouldn't change anything. Besides attempts 1 and 2, did you set some stylesheets anywhere else (including the application)? I know that there is some painting inconsistency depending on when widgets are created and [re]parented under certain circumstances (and styles), and using QSS may create further problems, as they assume background painting based on the "embedded" widgets. – musicamante Commented Mar 25 at 18:25
  • @musicamante No, there are no stylesheets, and I created the simple example in the image specifically for the test, so that I can be sure that there is absolutely nothing else influencing it. About inheriting the palette: my problem is that in this specific case the scrollarea does not inherit from the parent, despite me wanting it to. – vsz Commented Mar 26 at 5:06
  • I'm afraid that you're making a wrong assumption. The palette does not rule the colors any widget should use. It's just a hint, just like a physical palette for a human painter. The painter may then decide to follow those colors for certain elements of the painting, mix them based on their own taste, or even completely ignore them. Also, as the QPalette documentation clearly explains, some styles do not use the palette at all. The style is the only one that finally decides what colors are actually used, eventually considering their roles based on its own rules. – musicamante Commented Mar 26 at 5:16
  • But in this case, every single UI element does inherit the style of the tabwidget, except for the scrollarea (and QFrame which it is based on). Is there really no way around it, except manually adding a scrollbar to a QWidget and implement all the mechanisms around scrolling? – vsz Commented Mar 26 at 5:19
  • So, no, just relying on the palette is irrelevant, as the style eventually decides what to do with it (including completely ignoring it), therefore trying to explicitly set a palette when no other one has been altered (including the possibility of selector-based color changes in QSS) is, as written, pointless: if you're not using style sheets nor changing any palette at all, setPalette() on a child with the palette of the parent has absolutely no effect at all. – musicamante Commented Mar 26 at 5:20
 |  Show 11 more comments

2 Answers 2

Reset to default 1

tl;dr

You need to explicitly set the autoFillBackground property of both the viewport and the widget to false.

Explanation

How Qt scroll areas use the palette and draw their background

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.

Why the suggested attempts didn't work

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.

Appropriate solutions

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.

It turns out QScrollArea does not work as a selector. Neither does QFrame work, despite the scroll area being based on it. However, setting QWidget as a selector affects all children.

Setting the palette does not work, because in some styles, the same color for the window background is used as parts of the shadows of some elements, and they are affected as well.

However, using QWidget as a selector while banning its children does work.

ui->scrollArea->viewport()->setStyleSheet(".QWidget {background: transparent;}");

Targeting the viewport is important, otherwise the scroll bar itself will be affected.

So the solution is just a single dot in front of QWidget as a CSS selector.

发布评论

评论列表(0)

  1. 暂无评论