setStyleSheet

Если вы зашли на эту страницу, то похоже вас достало почему все эти css стили когда-то работают, а где-то не работают.

Настало время разобраться, что скрывается за setStyleSheet.

Сразу пример такой: вроде бы не влияет порядок описания css объектов. А оказывается влияет. У нас большой получается css текст (много объектов) и стили для QPushButton вначале срабатывает, а в конце почему-то нет. Что за ерунда, надо разбираться, а оно надо...

Вот состав функции setStyleSheet для приложения QApplication:

void QApplication::setStyleSheet(const QString& styleSheet)
{
    QApplicationPrivate::styleSheet = styleSheet;
    QStyleSheetStyle *proxy = qobject_cast(QApplicationPrivate::app_style);
    if (styleSheet.isEmpty()) { // application style sheet removed
        if (!proxy)
            return; // there was no stylesheet before
        setStyle(proxy->base);
    } else if (proxy) { // style sheet update, just repolish
        proxy->repolish(qApp);
    } else { // stylesheet set the first time
        QStyleSheetStyle *newProxy = new QStyleSheetStyle(QApplicationPrivate::app_style);
        QApplicationPrivate::app_style->setParent(newProxy);
        setStyle(newProxy);
    }
}

Сам параметр styleSheet это просто строка, она сохраняется в объекте QApplicationPrivate, но это не принципиально. 

А вот главное, что здесь происходит это создание объекта QStyleSheetStyle. Его линия наследования от предков такая:

фотка 1

Именно объект QStyleSheetStyle наследует от предков (QStyle,..) функции отрисовки такие как:

drawControl
drawItemText
drawItemPixmap

которые вы используете в той самой функции paint, которая отрисовывает визуальный объекты.  Например вы переопределяете pain в своих делегатах отображения.

Надо понимать, что app_style это:

static QStyle *app_style; 

 в иерархии наследования QStyle первый от QOject (на картинке)


Главный нюанс использования setStyleSheet в отсутствии проверки валидности. И это действительно задалбывает. И еще не понимание какие теги работают, а какие еще не поддерживаются в вашей версии Qt.

Другой нюанс, который не связан с styleSheet это самописные делегаты - они игнорируют стили, точнее у них стиль по умолчанию. И это надо не забывать.

Да где же устанавливается этот злосчастный styleSheet?  Вот здесь:

QStyle* QApplication::setStyle(const QString& style)
{
    QStyle *s = QStyleFactory::create(style);
    if (!s)
        return 0;

    setStyle(s);
    return s;
}

На входе строка, на выходе QStyle. Далее изучим QStyleFactory::create: 

QStyle *QStyleFactory::create(const QString& key)
{
    QStyle *ret = 0;
    QString style = key.toLower();
#ifndef QT_NO_STYLE_WINDOWS
    if (style == QLatin1String("windows"))
        ret = new QWindowsStyle;
    else
#endif
#ifndef QT_NO_STYLE_WINDOWSCE
    if (style == QLatin1String("windowsce"))
        ret = new QWindowsCEStyle;
    else
#endif
#ifndef QT_NO_STYLE_WINDOWSMOBILE
    if (style == QLatin1String("windowsmobile"))
        ret = new QWindowsMobileStyle;
    else
#endif
#ifndef QT_NO_STYLE_WINDOWSXP
    if (style == QLatin1String("windowsxp"))
        ret = new QWindowsXPStyle;
    else
#endif
#ifndef QT_NO_STYLE_WINDOWSVISTA
    if (style == QLatin1String("windowsvista"))
        ret = new QWindowsVistaStyle;
    else
#endif
#ifndef QT_NO_STYLE_MOTIF
    if (style == QLatin1String("motif"))
        ret = new QMotifStyle;
    else
#endif
#ifndef QT_NO_STYLE_CDE
    if (style == QLatin1String("cde"))
        ret = new QCDEStyle;
    else
#endif
#ifndef QT_NO_STYLE_S60
    if (style == QLatin1String("s60"))
        ret = new QS60Style;
    else
#endif
#ifndef QT_NO_STYLE_PLASTIQUE
    if (style == QLatin1String("plastique"))
        ret = new QPlastiqueStyle;
    else
#endif
#ifndef QT_NO_STYLE_CLEANLOOKS
    if (style == QLatin1String("cleanlooks"))
        ret = new QCleanlooksStyle;
    else
#endif
#ifndef QT_NO_STYLE_GTK
    if (style == QLatin1String("gtk") || style == QLatin1String("gtk "))
        ret = new QGtkStyle;
    else
#endif
#ifndef QT_NO_STYLE_MAC
    if (style.startsWith(QLatin1String("macintosh"))) {
        ret = new QMacStyle;
#  ifdef Q_WS_MAC
        if (style == QLatin1String("macintosh"))
            style  = QLatin1String(" (aqua)");
#  endif
    } else
#endif
    { } // Keep these here - they make the #ifdefery above work
#if !defined(QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS)
    if(!ret) {
        if (QStyleFactoryInterface *factory = qobject_cast(loader()->instance(style)))
            ret = factory->create(style);
    }
#endif
    if(ret)
        ret->setObjectName(style);
    return ret;
}

Тут есть нюанс этот setStyleSheet для объекта QApplication и как оказывается если вы устанавливаете для QTableView (в его конструкторе), то ва не сюда.

А вам надо вот сюда QWidget::setStyleSheet и к счастью больше вариантов setStyleSheet  в исходниках Qt не наблюдается:

void QWidget::setStyleSheet(const QString& styleSheet)
{
    Q_D(QWidget);
    d->createExtra();

    QStyleSheetStyle *proxy = qobject_cast(d->extra->style);
    d->extra->styleSheet = styleSheet;
    if (styleSheet.isEmpty()) { // stylesheet removed
        if (!proxy)
            return;

        d->inheritStyle();
        return;
    }

    if (proxy) { // style sheet update
        proxy->repolish(this);
        return;
    }

    if (testAttribute(Qt::WA_SetStyle)) {
        d->setStyle_helper(new QStyleSheetStyle(d->extra->style), true);
    } else {
        d->setStyle_helper(new QStyleSheetStyle(0), true);
    }
}

Здесь устанавливается d->extra->styleSheet нашей строкой стилей и вызывается  d->setStyle_helper 

void QWidgetPrivate::setStyle_helper(QStyle *newStyle, bool propagate, bool
#ifdef Q_WS_MAC
        metalHack
#endif
        )
{
    Q_Q(QWidget);
    QStyle *oldStyle  = q->style();
#ifndef QT_NO_STYLE_STYLESHEET
    QWeakPointer origStyle;
#endif

#ifdef Q_WS_MAC
    // the metalhack boolean allows Qt/Mac to do a proper re-polish depending
    // on how the Qt::WA_MacBrushedMetal attribute is set. It is only ever
    // set when changing that attribute and passes the widget's CURRENT style.
    // therefore no need to do a reassignment.
    if (!metalHack)
#endif
    {
        createExtra();

#ifndef QT_NO_STYLE_STYLESHEET
        origStyle = extra->style.data();
#endif
        extra->style = newStyle;
    }

    // repolish
    if (q->windowType() != Qt::Desktop) {
        if (polished) {
            oldStyle->unpolish(q);
#ifdef Q_WS_MAC
            if (metalHack)
                macUpdateMetalAttribute();
#endif
            q->style()->polish(q);
#ifdef Q_WS_MAC
        } else if (metalHack) {
            macUpdateMetalAttribute();
#endif
        }
    }

    if (propagate) {
        for (int i = 0; i < children.size();   i) {
            QWidget *c = qobject_cast(children.at(i));
            if (c)
                c->d_func()->inheritStyle();
        }
    }

#ifndef QT_NO_STYLE_STYLESHEET
    if (!qobject_cast(newStyle)) {
        if (const QStyleSheetStyle* cssStyle = qobject_cast(origStyle.data())) {
            cssStyle->clearWidgetFont(q);
        }
    }
#endif

    QEvent e(QEvent::StyleChange);
    QApplication::sendEvent(q, &e);
#ifdef QT3_SUPPORT
    q->styleChange(*oldStyle);
#endif

#ifndef QT_NO_STYLE_STYLESHEET
    // dereference the old stylesheet style
    if (QStyleSheetStyle *proxy = qobject_cast(origStyle.data()))
        proxy->deref();
#endif
}

Далее процесс установки нового набора стилем идет через метод inheritStyle, то есть применительно ко всем виджетам потомкам.

void QWidgetPrivate::inheritStyle()
{
#ifndef QT_NO_STYLE_STYLESHEET
    Q_Q(QWidget);

    QStyleSheetStyle *proxy = extra ? qobject_cast(extra->style) : 0;

    if (!q->styleSheet().isEmpty()) {
        Q_ASSERT(proxy);
        proxy->repolish(q);
        return;
    }

    QStyle *origStyle = proxy ? proxy->base : (extra ? (QStyle*)extra->style : 0);
    QWidget *parent = q->parentWidget();
    QStyle *parentStyle = (parent && parent->d_func()->extra) ? (QStyle*)parent->d_func()->extra->style : 0;
    // If we have stylesheet on app or parent has stylesheet style, we need
    // to be running a proxy
    if (!qApp->styleSheet().isEmpty() || qobject_cast(parentStyle)) {
        QStyle *newStyle = parentStyle;
        if (q->testAttribute(Qt::WA_SetStyle))
            newStyle = new QStyleSheetStyle(origStyle);
        else if (QStyleSheetStyle *newProxy = qobject_cast(parentStyle))
            newProxy->ref();

        setStyle_helper(newStyle, true);
        return;
    }

    // So, we have no stylesheet on parent/app and we have an empty stylesheet
    // we just need our original style back
    if (origStyle == (extra ? (QStyle*)extra->style : 0)) // is it any different?
        return;

    // We could have inherited the proxy from our parent (which has a custom style)
    // In such a case we need to start following the application style (i.e revert
    // the propagation behavior of QStyleSheetStyle)
    if (!q->testAttribute(Qt::WA_SetStyle))
        origStyle = 0;

    setStyle_helper(origStyle, true);
#endif // QT_NO_STYLE_STYLESHEET
}

Выше происходит просто вызов опять setStyle_helper(newStyle, true); , но уже с нашим устанавливаемым набором стилей newStyle : setStyle_helper(newStyle, true); и мы опять входим в  setStyle_helper, но уже с не пустыми руками. setStyle_helper смотрите уже была выше.

В какой-то момент мы попадаем на метод proxy->repolish(q);  класса QStyleSheetStyle и это и будет установка новый стилей. 

void QStyleSheetStyle::repolish(QWidget *w)
{
    QList children = w->findChildren(QString());
    children.append(w);
    styleSheetCaches->styleSheetCache.remove(w);
    updateWidgets(children);
}

И вот наконец мы находим конкретную функцию updateWidgets , которая непосредственно уже работает с набором стилей.

static void updateWidgets(const QList& widgets)
{
    if (!styleSheetCaches->styleRulesCache.isEmpty() || !styleSheetCaches->hasStyleRuleCache.isEmpty() || !styleSheetCaches->renderRulesCache.isEmpty()) {
        for (int i = 0; i < widgets.size();   i) {
            const QWidget *widget = widgets.at(i);
            styleSheetCaches->styleRulesCache.remove(widget);
            styleSheetCaches->hasStyleRuleCache.remove(widget);
            styleSheetCaches->renderRulesCache.remove(widget);
        }
    }
    for (int i = 0; i < widgets.size();   i) {
        QWidget *widget = const_cast(widgets.at(i));
        if (widget == 0)
            continue;
        widget->style()->polish(widget);
        QEvent event(QEvent::StyleChange);
        QApplication::sendEvent(widget, &event);
        widget->update();
        widget->updateGeometry();
    }
}

Сначала в updateWidgets удаляются ранее примененные стили (если они конечно были) и происходит вызов метода widget->style()->polish(widget); , который применяет новые стили.

И вот мы уже добрались до функции StyleSheetStyle::polish. Кстати polish виртуальная функция, то есть polish вызываться будет у того объекта, с которым мы работаем.

void QStyleSheetStyle::polish(QWidget *w)
{
    baseStyle()->polish(w);
    RECURSION_GUARD(return)

    if (!initWidget(w))
        return;

    if (styleSheetCaches->styleRulesCache.contains(w)) {
        // the widget accessed its style pointer before polish (or repolish)
        // (exemple: the QAbstractSpinBox constructor ask for the stylehint)
        styleSheetCaches->styleRulesCache.remove(w);
        styleSheetCaches->hasStyleRuleCache.remove(w);
        styleSheetCaches->renderRulesCache.remove(w);
    }
    setGeometry(w);
    setProperties(w);
    unsetPalette(w);
    setPalette(w);

    //set the WA_Hover attribute if one of the selector depends of the hover state
    QVector rules = styleRules(w);
    for (int i = 0; i < rules.count(); i  ) {
        const Selector& selector = rules.at(i).selectors.at(0);
        quint64 negated = 0;
        quint64 cssClass = selector.pseudoClass(&negated);
        if ( cssClass & PseudoClass_Hover || negated & PseudoClass_Hover) {
            w->setAttribute(Qt::WA_Hover);
            embeddedWidget(w)->setAttribute(Qt::WA_Hover);
        }
    }


#ifndef QT_NO_SCROLLAREA
    if (QAbstractScrollArea *sa = qobject_cast(w)) {
        QRenderRule rule = renderRule(sa, PseudoElement_None, PseudoClass_Enabled);
        if ((rule.hasBorder() && rule.border()->hasBorderImage())
            || (rule.hasBackground() && !rule.background()->pixmap.isNull())) {
            QObject::connect(sa->horizontalScrollBar(), SIGNAL(valueChanged(int)),
                             sa, SLOT(update()), Qt::UniqueConnection);
            QObject::connect(sa->verticalScrollBar(), SIGNAL(valueChanged(int)),
                             sa, SLOT(update()), Qt::UniqueConnection);
        }
    }
#endif

#ifndef QT_NO_PROGRESSBAR
    if (QProgressBar *pb = qobject_cast(w)) {
        QWindowsStyle::polish(pb);
    }
#endif

    QRenderRule rule = renderRule(w, PseudoElement_None, PseudoClass_Any);
    if (rule.hasDrawable() || rule.hasBox()) {
        if (w->metaObject() == &QWidget::staticMetaObject
#ifndef QT_NO_ITEMVIEWS
              || qobject_cast(w)
#endif
#ifndef QT_NO_TABBAR
              || qobject_cast(w)
#endif
#ifndef QT_NO_FRAME
              || qobject_cast(w)
#endif
#ifndef QT_NO_MAINWINDOW
              || qobject_cast(w)
#endif
#ifndef QT_NO_MDIAREA
              || qobject_cast(w)
#endif
#ifndef QT_NO_MENUBAR
              || qobject_cast(w)
#endif
              || qobject_cast(w)) {
            w->setAttribute(Qt::WA_StyledBackground, true);
        }
        QWidget *ew = embeddedWidget(w);
        if (ew->autoFillBackground()) {
            ew->setAutoFillBackground(false);
            styleSheetCaches->autoFillDisabledWidgets.insert(w);
            if (ew != w) { //eg. viewport of a scrollarea
                //(in order to draw the background anyway in case we don't.)
                ew->setAttribute(Qt::WA_StyledBackground, true);
            }
        }
        if (!rule.hasBackground() || rule.background()->isTransparent() || rule.hasBox()
            || (!rule.hasNativeBorder() && !rule.border()->isOpaque()))
            w->setAttribute(Qt::WA_OpaquePaintEvent, false);
    }
}

И вот первая функция, которая будет что-то делать с нашим виджетом это setGeometry, следующая setProperties, потом setPalette.

void QStyleSheetStyle::setGeometry(QWidget *w)
{
    QRenderRule rule = renderRule(w, PseudoElement_None, PseudoClass_Enabled | extendedPseudoClass(w));
    const QStyleSheetGeometryData *geo = rule.geometry();
    if (w->property("_q_stylesheet_minw").toBool()
        && ((!rule.hasGeometry() || geo->minWidth == -1))) {
            w->setMinimumWidth(0);
            w->setProperty("_q_stylesheet_minw", QVariant());
    }
    if (w->property("_q_stylesheet_minh").toBool()
        && ((!rule.hasGeometry() || geo->minHeight == -1))) {
            w->setMinimumHeight(0);
            w->setProperty("_q_stylesheet_minh", QVariant());
    }
    if (w->property("_q_stylesheet_maxw").toBool()
        && ((!rule.hasGeometry() || geo->maxWidth == -1))) {
            w->setMaximumWidth(QWIDGETSIZE_MAX);
            w->setProperty("_q_stylesheet_maxw", QVariant());
    }
   if (w->property("_q_stylesheet_maxh").toBool()
        && ((!rule.hasGeometry() || geo->maxHeight == -1))) {
            w->setMaximumHeight(QWIDGETSIZE_MAX);
            w->setProperty("_q_stylesheet_maxh", QVariant());
    }


    if (rule.hasGeometry()) {
        if (geo->minWidth != -1) {
            w->setProperty("_q_stylesheet_minw", true);
            w->setMinimumWidth(rule.boxSize(QSize(qMax(geo->width, geo->minWidth), 0)).width());
        }
        if (geo->minHeight != -1) {
            w->setProperty("_q_stylesheet_minh", true);
            w->setMinimumHeight(rule.boxSize(QSize(0, qMax(geo->height, geo->minHeight))).height());
        }
        if (geo->maxWidth != -1) {
            w->setProperty("_q_stylesheet_maxw", true);
            w->setMaximumWidth(rule.boxSize(QSize(qMin(geo->width == -1 ? QWIDGETSIZE_MAX : geo->width,
                                                       geo->maxWidth == -1 ? QWIDGETSIZE_MAX : geo->maxWidth), 0)).width());
        }
        if (geo->maxHeight != -1) {
            w->setProperty("_q_stylesheet_maxh", true);
            w->setMaximumHeight(rule.boxSize(QSize(0, qMin(geo->height == -1 ? QWIDGETSIZE_MAX : geo->height,
                                                       geo->maxHeight == -1 ? QWIDGETSIZE_MAX : geo->maxHeight))).height());
        }
    }
}

В setGeometry у нас например ничего не произошло, никаких установок, но тем не менее все понятно, где искать ответы на вопросы, связанные с размером виджета.

Далее вызывается метод setProperties:

void QStyleSheetStyle::setProperties(QWidget *w)
{
    QHash propertyHash;
    QVector decls = declarations(styleRules(w), QString());

    // run through the declarations in order
    for (int i = 0; i < decls.count(); i  ) {
        const Declaration &decl = decls.at(i);
        QString property = decl.d->property;
        if (!property.startsWith(QLatin1String("qproperty-"), Qt::CaseInsensitive))
            continue;
        property.remove(0, 10); // strip "qproperty-"
        const QVariant value = w->property(property.toLatin1());
        const QMetaObject *metaObject = w->metaObject();
        int index = metaObject->indexOfProperty(property.toLatin1());
        if (index == -1) {
            qWarning() << w << " does not have a property named " << property;
            continue;
        }
        QMetaProperty metaProperty = metaObject->property(index);
        if (!metaProperty.isWritable() || !metaProperty.isDesignable()) {
            qWarning() << w << " cannot design property named " << property;
            continue;
        }
        QVariant v;
        switch (value.type()) {
        case QVariant::Icon: v = decl.iconValue(); break;
        case QVariant::Image: v = QImage(decl.uriValue()); break;
        case QVariant::Pixmap: v = QPixmap(decl.uriValue()); break;
        case QVariant::Rect: v = decl.rectValue(); break;
        case QVariant::Size: v = decl.sizeValue(); break;
        case QVariant::Color: v = decl.colorValue(); break;
        case QVariant::Brush: v = decl.brushValue(); break;
#ifndef QT_NO_SHORTCUT
        case QVariant::KeySequence: v = QKeySequence(decl.d->values.at(0).variant.toString()); break;
#endif
        default: v = decl.d->values.at(0).variant; break;
        }
        propertyHash[property] = v;
    }
    // apply the values
    const QList properties = propertyHash.keys();
    for (int i = 0; i < properties.count(); i  ) {
        const QString &property = properties.at(i);
        w->setProperty(property.toLatin1(), propertyHash[property]);
    }
}

В отладчике мы видим например, что наш виджет w это например "qt_scrollarea_hcontainer", к которому будут применены 3 декларации.

Первая декларация например у нас "font-size" со значением 6. Кстати говоря в setProperties мы тоже ничего не установили, пропустили по причине отсутствия в названии в начале "qproperty-". Идем дальше.

Функция setPalette:

void QStyleSheetStyle::setPalette(QWidget *w)
{
    struct RuleRoleMap {
        int state;
        QPalette::ColorGroup group;
    } map[3] = {
        { int(PseudoClass_Active | PseudoClass_Enabled), QPalette::Active },
        { PseudoClass_Disabled, QPalette::Disabled },
        { PseudoClass_Enabled, QPalette::Inactive }
    };

    QPalette p = w->palette();
    QWidget *ew = embeddedWidget(w);

    for (int i = 0; i < 3; i  ) {
        QRenderRule rule = renderRule(w, PseudoElement_None, map[i].state | extendedPseudoClass(w));
        if (i == 0) {
            if (!w->property("_q_styleSheetWidgetFont").isValid()) {
                saveWidgetFont(w, w->font());
            }
            updateStyleSheetFont(w);
            if (ew != w)
                updateStyleSheetFont(ew);
        }

        rule.configurePalette(&p, map[i].group, ew, ew != w);
    }

    styleSheetCaches->customPaletteWidgets.insert(w, w->palette());
    w->setPalette(p);
    if (ew != w)
        ew->setPalette(p);
}

И доходит наконец до некоторого массива QVector<StyleRule> rules = styleRules(w); , у нас тут 3 штуки StyleRule. И опять данная часть кода связана как-то с установкой атрибута окна WA_Hover.

Идем далее, видим что как-то отдельно обрабатывается ситуация с виджетом типа QAbstractScrollArea. Потом QProgressBar рассматривается.

И наконец доходим до QRenderRule rule = renderRule(w, PseudoElement_None, PseudoClass_Any); и видим, что для некоторых классов (QHeaderView, QTabBar, QFrame, QMainWindow, QMdiSubWindow, QMenuBar, QDialog) виджетов и еще по условию
if (w->metaObject() == &QWidget::staticMetaObject
устанавливается атрибут виджета WA_StyledBackground:

w->setAttribute(Qt::WA_StyledBackground, true); 

У нас по условию if (w->metaObject() == &QWidget::staticMetaObject сработало для класса QTableView. 

Еще далее сработало w->setAttribute(Qt::WA_OpaquePaintEvent, false); 

Таким образом в StyleSheetStyle::polish главным образом идет установка атрибутов виджету (окну).

Но важно понимать, как выяснилось в процессе отладки (довольно случайно) polish вызывается для виджета, не только при установке setStyleSheet, а еще и при событии QEvent::Polish, возникающим постоянно при работе приложения.

Вот небольшой участок кода метода bool QWidget::event(QEvent *event):

    case QEvent::Polish: {
        style()->polish(this);
        setAttribute(Qt::WA_WState_Polished);
        if (!QApplication::font(this).isCopyOf(QApplication::font()))
            d->resolveFont();
        if (!QApplication::palette(this).isCopyOf(QApplication::palette()))
            d->resolvePalette();
#ifdef QT3_SUPPORT
        if(d->sendChildEvents)
            QApplication::sendPostedEvents(this, QEvent::ChildInserted);
#endif
    }
        break;

Ну в общем-то пока на этом все.Цель достигнута, мы уже подошли совсем близко к решению вопроса как работают стили в Qt.

Далее надо бы поизучать class QRenderRule.