How to override just one property:value pair in Qt StyleSheet
Asked Answered
T

2

27

I am writing newbie Qt5 code on OSX Mavericks and would like to override just one property:value pair of the StyleSheet for a given widget.

If I run the following self-contained demonstration code:

#include <QApplication>
#include <QMainWindow>
#include <QtGui>
#include <QPushButton>

int
main( int argc, char *argv[] ) {
    QApplication app( argc, argv );

    QMainWindow* mw = new QMainWindow();

    QPushButton* AButton = new QPushButton( "A Button", mw );

    mw->show();

    return app.exec();
}

I get a nice pushbutton with Macintosh style defaults -- rounded corners, standard OSX-blue color when pressed, etc.:

QPushButton with OSX defaults

If however I set the stylesheet to make the background button color red, it seems I lose all the other OSX style defaults in the process -- no more rounded corners, standard margins, etc.:

#include <QApplication>
#include <QMainWindow>
#include <QtGui>
#include <QPushButton>

int
main( int argc, char *argv[] ) {
    QApplication app( argc, argv );

    QMainWindow* mw = new QMainWindow();

    QPushButton* AButton = new QPushButton( "A Button", mw );

    AButton->setStyleSheet("QPushButton { background-color: red; }");

    mw->show();

    return app.exec();
}

Here's the result:

QPushButton style override, with loss of other OSX defaults

How can I override just one property:value pair while preserving the rest of the style elements for the widget, e.g. in the example above make the background color red but keep all the border rounding, margins etc. the same?

Thanks much

Trig answered 3/3, 2015 at 19:5 Comment(4)
You could try to add the new style to the existing one with AButton->setStyleSheet(AButton->styleSheet() + "...");Donato
Thanks, however that seems still to override all the other platform defaults (I used AButton->setStyleSheet( AButton->styleSheet() + "QPushButton { background-color: red; }");)Trig
Ah wait... What you are talking about are not CSS style attributes which get overriden, but the system style QtWidget's style engine tries to mimic. It can't mimic system style when you specify some attributes manually, AFAIK, and fall back to a "old windows style" when the style engine fails to adapt the CSS attribute you specify. Try to use palettes instead. Have a look at QWidget::setPalette() and palette(), then try to set colors for some roles (play around which is used for button backgrounds, I don't remember it). This will then be considered by the style engine.Donato
It looks like QMacStyle ignores the QPalette::Button color for setting the background. You can use QPalette::Text to set the text color though.Marker
C
13

Your analysis in your comment that a call to QApplication::setStyleSheet() will completely replace the currently active style is incorrect. You are right that a the current style is replaced with QStyleSheetStyle. However, QStyleSheetStyle delegates its drawing to the original style, in your case the Mac style. If you look at the source of QStyleSheetStyle, you'll see this in many places, calls to baseStyle()->drawControl().

This means stylesheets work on any style. Now, it clearly didn't work in your case, so what happened? QStyleSheetStyle falls back to drawing in the Windows style if the style sheet rules can't be applied to the base style. That means that some style sheet rules work nicely, while others will trigger the fallback. Your rules triggered the fallback.

I don't think it's documented which rules trigger the fallback. For that we need to look at the source, in this case QStyleSheetStyle::drawControl(). We'll find the following in there:

case CE_PushButton:
    if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(opt)) {
        if (rule.hasDrawable() || rule.hasBox() || rule.hasPosition() || rule.hasPalette() ||
                ((btn->features & QStyleOptionButton::HasMenu) && hasStyleRule(w, PseudoElement_PushButtonMenuIndicator))) {
            ParentStyle::drawControl(ce, opt, p, w);
            return;
        }
    }

ParentSyle is the fallback Windows style. Your rule background-color makes rule.hasPalette() return true, therefore triggering the fallback.

Charmion answered 18/3, 2016 at 23:40 Comment(0)
T
7

As Illuminated in the parallel post How to obtain entire Qt StyleSheet for QMacStyle, my original question was based on confused premise.

In Qt, all widgets are styled via a QStyle(). By default for Qt code on OSX (as in my first block of demonstration code in the original question), Qt widgets are styled by a subclass of QStyle() called QMacStyle() (actually now apparently a derivative of QProxyStyle(), per the page QMacStyle -- File Not Found).

The setStyleSheet() function call, as in my second block of demonstration code in the original question, creates an instance of QStyleSheetStyle(), which then wholesale replaces whatever QStyle was in use for the widget beforehand -- in this case the nice-looking QMacStyle I had been hoping to preserve. One can see implementation details for this in e.g. qtbase/src/widgets/kernel/qapplication.cpp in the QApplication::setStyleSheet() method. The QStyleSheetStyle() itself is in qtbase/src/widgets/styles/qstylesheetstyle.cpp. As a point of trivia, QStyleSheetStyle() is derived from QWindowsStyle(), hence the windows-ish appearance to the modified pushbutton in my original question.

The short story is that Qt StyleSheets are implemented on top of QStyle(). Calling setStyleSheet() implicitly decides that you're going to use QStyleSheetStyle() instead of QMacStyle().

The take-home message is that when styling widgets you need to choose your approach, with tradeoffs either way. You need to choose what QStyle() you are going to use. If you run default code on the Mac, you have chosen to start from QMacStyle(). You can then modify that QStyle() per extant documentation. If you invoke setStyleSheet(), you have implicitly chosen to start from QStyleSheetStyle(), which by default has the appearance of QWindowsStyle(). Any stylesheet property-values you then apply will modify that appearance.

For my question as posed above, there are two choices. One is that a person could make any and all appearance modifications entirely via the mechanisms available through QStyle(). How to do this is documented elsewhere. The other option would be to generate from scratch a stylesheet that reproduced the Mac look-and-feel for the widget in question, then modify that with the desired tweaks and apply it through setStyleSheet().

In an ideal world someone would have gone through that exercise already -- i.e. digested qtbase/src/widgets/styles/qmacstyle_mac.mm into its stylesheet equivalent -- however that job seems onerous and it doesn't seem as though anyone has done it, at least not with public posting. I suppose another distantly feasible option would be to reimplement QStyleSheetStyle based on QMacStyle instead of QWindowsStyle, then somehow integrate the result back in to the code (maybe have a setStyleSheet( QStyle* baseStyle, QString styleSheet)). That however is far beyond newbie Qt skills, not to mention current mission.

Trig answered 6/3, 2015 at 17:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.