Пользовательский интерфейс в Qt

Основы Qt Style Sheets: синтаксис и селекторы

Введение

Qt Style Sheets (QSS) — язык стилизации виджетов Qt, синтаксически близкий к CSS, но со своими особенностями, учитывающими специфику виджетной модели Qt. QSS позволяет менять внешний вид стандартных виджетов без написания C++ кода — цвета, шрифты, отступы, рамки — что делает его удобным инструментом для дизайнеров и разработчиков, не погружающихся в детали отрисовки виджетов. В статье разберём базовый синтаксис и основные виды селекторов.

Концепция

Правило QSS состоит из селектора (указывающего, к каким виджетам применяется правило) и блока свойств в фигурных скобках, аналогично CSS. Селекторы могут быть по типу виджета (QPushButton), по имени объекта (#myButton, используя objectName), по классу через свойство (QPushButton[flat="true"]), и комбинированные через иерархию (QDialog QPushButton — все кнопки внутри диалога). Применить QSS можно к конкретному виджету (widget->setStyleSheet(...)) или ко всему приложению (qApp->setStyleSheet(...)), при этом стили наследуются по дереву виджетов, если не переопределены более специфичным правилом.

Пример кода: Основы Qt Style Sheets

#include <QApplication>
#include <QPushButton>
#include <QWidget>
#include <QVBoxLayout>

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

    auto *window = new QWidget();
    window->setObjectName("mainWindow");

    auto *primaryButton = new QPushButton("Сохранить", window);
    primaryButton->setObjectName("primaryButton");

    auto *secondaryButton = new QPushButton("Отмена", window);
    secondaryButton->setProperty("buttonRole", "secondary");

    auto *layout = new QVBoxLayout(window);
    layout->addWidget(primaryButton);
    layout->addWidget(secondaryButton);

    // Применение стилей ко всему приложению — наследуется всеми виджетами,
    // если не переопределено более специфичным правилом
    app.setStyleSheet(R"(
        /* Селектор по имени класса виджета — применяется ко всем QPushButton */
        QPushButton {
            padding: 8px 16px;
            border-radius: 4px;
            font-size: 14px;
        }

        /* Селектор по objectName (через #) — самый специфичный, переопределяет общий стиль */
        #primaryButton {
            background-color: #2563eb;
            color: white;
            border: none;
        }

        #primaryButton:hover {
            background-color: #1d4ed8;
        }

        /* Селектор по динамическому свойству — полезно для логических групп виджетов
           без необходимости задавать уникальное имя каждому */
        QPushButton[buttonRole="secondary"] {
            background-color: transparent;
            color: #2563eb;
            border: 1px solid #2563eb;
        }

        /* Иерархический селектор — кнопки именно внутри #mainWindow */
        #mainWindow QPushButton {
            min-width: 100px;
        }
    )");

    window->show();
    return app.exec();
}

Пояснения к коду

Правило QPushButton { ... } применяется ко всем кнопкам приложения без исключения — это базовый, наименее специфичный уровень стилизации. Правило #primaryButton { ... } нацелено только на конкретный виджет с заданным через setObjectName() именем и переопределяет более общие свойства для этого конкретного виджета — это поведение прямо аналогично специфичности селекторов в обычном CSS, где более конкретный селектор побеждает более общий при конфликте свойств.

Селектор QPushButton[buttonRole="secondary"] демонстрирует использование произвольного динамического свойства (setProperty("buttonRole", "secondary")), не являющегося стандартным свойством Qt — это удобный способ группировать виджеты по логической роли без необходимости задавать каждому уникальное objectName, особенно когда таких «вторичных» кнопок в приложении много. Иерархический селектор #mainWindow QPushButton показывает контекстную стилизацию — правило применяется только к кнопкам, находящимся где-то внутри виджета с именем mainWindow, а не ко всем кнопкам приложения вообще.

Подводные камни

  • Применение setStyleSheet() к родительскому виджету полностью переопределяет стиль для всех потомков, в том числе сбрасывая платформенный нативный вид виджетов на полностью кастомный — даже минимальный QSS, заданный для одного-единственного свойства (например, только цвета фона), на некоторых платформах может неожиданно изменить и другие визуальные аспекты виджета (форму рамки, поведение фокуса), которые до этого предоставлялись нативным стилем ОС.
  • Дублирование и конфликт правил при использовании одновременно глобального стиля приложения (qApp->setStyleSheet) и локальных стилей отдельных виджетов (widget->setStyleSheet) — оба применяются одновременно, и итоговый эффект определяется правилами специфичности и порядком наложения, что может приводить к неожиданным результатам, особенно по мере роста количества правил в проекте; для предсказуемости стоит придерживаться единой, осознанной стратегии (например, весь основной стиль — на уровне приложения, а локальные стили — только для редких, точечных исключений).
  • Производительность парсинга QSS не бесплатна — при каждом вызове setStyleSheet() Qt заново разбирает всю переданную строку стилей; вызов этого метода в цикле или слишком часто (например, при каждом изменении состояния виджета вместо использования псевдосостояний из статьи 261) создаёт ненужную нагрузку, которая особенно заметна на слабом embedded-железе (основной цикл статей про embedded, статья 242).
  • QSS-синтаксис похож на CSS, но не идентичен ему — не все CSS-свойства и селекторы, привычные веб-разработчикам, поддерживаются в QSS, и наоборот, в QSS есть специфичные для Qt-виджетов псевдоэлементы (статья 261), не имеющие прямого аналога в CSS — разработчики с опытом веб-разработки иногда интуитивно пытаются использовать веб-специфичный синтаксис (flexbox, grid), который в QSS просто не существует.