Введение
Виджеты Qt имеют несколько визуальных состояний — обычное, под курсором мыши (hover), нажатое, отключённое, и так далее — и QSS позволяет задать разный стиль для каждого состояния через псевдосостояния. Кроме того, сложные виджеты (чекбоксы, радиокнопки, скроллбары) состоят из нескольких визуальных подэлементов, стилизуемых отдельно через псевдоэлементы. В статье разберём, как использовать оба механизма для тонкой настройки внешнего вида.
Концепция
Псевдосостояние записывается через двоеточие после селектора (:hover, :pressed, :disabled, :checked, :focus) и отражает текущее динамическое состояние виджета — Qt автоматически переключает применяемые стили при изменении состояния, без необходимости в JavaScript-подобной логике, как в вебе. Псевдоэлемент записывается через два двоеточия (::indicator для индикатора чекбокса/радиокнопки, ::handle для ручки слайдера, ::drop-down для выпадающей части комбобокса) и позволяет стилизовать конкретную визуальную часть сложного составного виджета отдельно от остальной его части.
Пример кода: Псевдосостояния и псевдоэлементы в QSS (
app.setStyleSheet(R"(
/* Базовый стиль кнопки */
QPushButton {
background-color: #f3f4f6;
border: 1px solid #d1d5db;
border-radius: 4px;
padding: 8px 16px;
}
/* Псевдосостояние :hover — стиль при наведении курсора мыши */
QPushButton:hover {
background-color: #e5e7eb;
border-color: #9ca3af;
}
/* Псевдосостояние :pressed — стиль в момент нажатия */
QPushButton:pressed {
background-color: #d1d5db;
}
/* Псевдосостояние :disabled — стиль для неактивной кнопки */
QPushButton:disabled {
background-color: #f9fafb;
color: #9ca3af;
border-color: #e5e7eb;
}
/* Комбинация псевдосостояний — фокус И hover одновременно */
QPushButton:focus:hover {
border: 2px solid #2563eb;
}
/* Псевдоэлемент ::indicator — квадратик чекбокса как отдельная стилизуемая часть */
QCheckBox::indicator {
width: 18px;
height: 18px;
border: 2px solid #9ca3af;
border-radius: 3px;
}
/* Псевдоэлемент в сочетании с псевдосостоянием — индикатор именно отмеченного чекбокса */
QCheckBox::indicator:checked {
background-color: #2563eb;
border-color: #2563eb;
image: url(:/icons/checkmark.svg);
}
/* Псевдоэлемент ::handle слайдера — стилизация именно перемещаемой "ручки" */
QSlider::handle:horizontal {
background-color: #2563eb;
width: 16px;
border-radius: 8px;
margin: -4px 0; /* выравнивание ручки относительно желобка слайдера */
}
/* Псевдоэлемент ::groove слайдера — стилизация желобка, по которому движется ручка */
QSlider::groove:horizontal {
background-color: #e5e7eb;
height: 4px;
border-radius: 2px;
}
/* Псевдоэлемент ::drop-down комбобокса — выпадающая стрелка справа */
QComboBox::drop-down {
border: none;
width: 24px;
}
)");
Пояснения к коду
Правила :hover, :pressed, :disabled показывают типичный набор интерактивных состояний кнопки — Qt автоматически отслеживает реальное состояние виджета (наведён ли курсор, нажата ли кнопка мышью, отключена ли через setEnabled(false)) и применяет соответствующий блок стилей без какого-либо дополнительного кода в C++. Комбинация :focus:hover показывает, что псевдосостояния можно объединять цепочкой — стиль применяется только когда оба условия выполняются одновременно (виджет имеет фокус клавиатуры И курсор находится над ним).
QCheckBox::indicator и его комбинация с :checked показывают разделение составного виджета на части — у чекбокса есть текстовая метка и квадратик-индикатор, и эти две части можно стилизовать совершенно независимо. Аналогично QSlider::handle/QSlider::groove разделяют слайдер на подвижную ручку и неподвижный желобок — оба псевдоэлемента нужно стилизовать согласованно (как в примере, где высота желобка и отрицательный margin ручки подобраны так, чтобы ручка визуально центрировалась по высоте желобка), иначе визуальное расположение частей может выглядеть некорректно.
Подводные камни
- Стилизация только псевдоэлемента
::indicatorбез базового стиля самогоQCheckBoxможет привести к тому, что чекбокс будет выглядеть рассогласованно — например, текстовая метка останется в нативном системном стиле, а индикатор — в кастомном, что визуально может смотреться неаккуратно; для целостного вида обычно нужно продумывать стиль составного виджета как единое целое, а не стилизовать только одну его часть в изоляции. - Подбор
margin/paddingдля согласования::handleи::grooveслайдера требует ручной, эмпирической настройки — нет автоматического способа гарантировать идеальное визуальное совмещение ручки с желобком только на основе их размеров; значения, использованные в примере (margin: -4px 0для ручки высотой 16px и желобка высотой 4px), подбирались опытным путём для конкретных размеров, и при изменении размеров эту настройку нужно пересчитывать вручную. - Не все виджеты поддерживают одинаковый набор псевдоэлементов — список доступных псевдоэлементов специфичен для конкретного класса виджета и описан в документации Qt для каждого виджета отдельно; попытка использовать псевдоэлемент, не поддерживаемый конкретным виджетом (например,
::indicatorдляQPushButton, у которого такого псевдоэлемента нет), просто тихо не даёт никакого эффекта, без явной ошибки или предупреждения. - Различие в поддержке псевдосостояний между платформами при использовании нативного стиля поверх QSS. Если приложение использует платформенный стиль (Windows Vista style, macOS style) и применяет лишь частичный QSS поверх него, некоторые псевдосостояния могут визуально конфликтовать с уже встроенной в нативный стиль логикой отображения состояний — для полностью предсказуемого, единообразного на всех платформах вида часто проще использовать кросс-платформенный базовый стиль (Fusion) как основу, поверх которого QSS работает наиболее предсказуемо.