Qt Style Sheets(QSS)先选择控件,再决定这些控件如何绘制。排查样式时,先确认选择器匹配到哪个对象,再检查状态和样式设置位置。
本文示例使用 Qt 6 Widgets,已在 Qt 6.10.2 验证。这里讨论 QWidget 的 QSS;QML/Qt Quick 使用另一套控件与样式机制。
选择器决定作用对象
跳转到“选择器决定作用对象”| 写法 | 匹配范围 | 常见误会 |
|---|---|---|
QPushButton | 该类及其派生类的按钮 | 不只匹配类名恰好相等的实例 |
QPushButton#myButton | 类型匹配且 objectName 为 myButton 的对象 | # 后写对象名,不是变量名或显示文字 |
QWidget QPushButton | QWidget 后代中的按钮,包含隔了多层的后代 | QWidget 很宽泛,不能把它当成某个指定面板 |
#panel QPushButton | 名为 panel 的对象之下的所有按钮 | 空格表示后代关系 |
#panel > QPushButton | panel 的直接按钮子对象 | 布局不是中间的 QWidget;实际 parentWidget() 才决定关系 |
QPushButton:pressed | 正处于按下状态的按钮 | :pressed 是状态,和持续的 :checked 不同 |
QComboBox::drop-down | 组合框的下拉按钮子控件 | ::drop-down 是样式绘制部分,不是任意子 QWidget 的名称 |
Qt Designer 中先选控件,在属性编辑器设置 objectName,再在窗口或控件的 styleSheet 属性里使用选择器。运行时以 setObjectName() 和 setStyleSheet() 完成相同工作。对象名应在需要区分的范围内保持唯一;Qt 不替你强制这个约束。
换行和逗号会改变含义
跳转到“换行和逗号会改变含义”下面三种写法不能一概视为等价:
| 原有写法 | 实际含义 |
|---|---|
QLabel 换行后紧接 #deviceTypeHeaderTitleLabel | 中间的换行属于空白:匹配 QLabel 后代里名为 deviceTypeHeaderTitleLabel 的对象 |
QLabel#deviceTypeHeaderTitleLabel, #aPlaneHeaderTitleLabel | 第一项限制 QLabel 类型;第二项只限制对象名 |
QLabel#deviceTypeHeaderTitleLabel, QLabel#aPlaneHeaderTitleLabel | 两项分别限制 QLabel 类型和对象名 |
逗号不会把第一项的 QLabel 自动带到后面的项。这个区别同样适用于原示例中的 bPlaneHeaderTitleLabel 和 testAdjustmentHeaderTitleLabel。以下完整程序对四项逐一写出类型,并让这些标签的背景透明。
用一个窗口观察匹配与状态
跳转到“用一个窗口观察匹配与状态”保存为 main.cpp。面板的直接按钮是蓝色,因为对象名规则胜出;嵌套按钮是黄色;面板外按钮保持红色。按住蓝色按钮时背景变灰。组合框右侧下拉区域为绿色。
#include <QApplication>#include <QComboBox>#include <QFrame>#include <QLabel>#include <QPushButton>#include <QVBoxLayout>
QWidget *makeWindow(){ auto *window = new QWidget; window->setObjectName("demo"); window->setWindowTitle("QSS selector scope"); auto *outer = new QVBoxLayout(window); auto *panel = new QFrame(window); panel->setObjectName("panel"); auto *layout = new QVBoxLayout(panel); auto *button = new QPushButton("myButton: hold to turn gray", panel); button->setObjectName("myButton"); layout->addWidget(button); auto *nested = new QFrame(panel); auto *nestedLayout = new QVBoxLayout(nested); auto *nestedButton = new QPushButton("Nested: yellow", nested); nestedButton->setObjectName("nestedButton"); nestedLayout->addWidget(nestedButton); layout->addWidget(nested); for (const char *name : {"deviceTypeHeaderTitleLabel", "aPlaneHeaderTitleLabel", "bPlaneHeaderTitleLabel", "testAdjustmentHeaderTitleLabel"}) { auto *label = new QLabel(QString::fromLatin1(name), panel); label->setObjectName(QString::fromLatin1(name)); layout->addWidget(label); } auto *combo = new QComboBox(panel); combo->setObjectName("mode"); combo->addItems({"Mode A", "Mode B"}); layout->addWidget(combo); outer->addWidget(panel); auto *outside = new QPushButton("Outside panel: red", window); outside->setObjectName("outsideButton"); outer->addWidget(outside); window->setStyleSheet(R"QSS( QWidget#demo { background-color: white; } QFrame#panel { background-color: rgb(122, 132, 131); } QPushButton { background-color: red; color: white; border: 1px solid black; padding: 8px; } #panel QPushButton { background-color: yellow; color: black; } #panel > QPushButton { background-color: green; } QPushButton#myButton { background-color: blue; color: white; } QPushButton#myButton:pressed { background-color: gray; } QLabel { background-color: red; color: black; } QLabel#deviceTypeHeaderTitleLabel, QLabel#aPlaneHeaderTitleLabel, QLabel#bPlaneHeaderTitleLabel, QLabel#testAdjustmentHeaderTitleLabel { background-color: transparent; } QComboBox { background: white; color: black; border: 1px solid black; padding: 4px 28px 4px 4px; min-height: 20px; } QComboBox::drop-down { background: green; width: 24px; border-left: 1px solid black; } )QSS"); window->resize(440, 400); return window;}
int main(int argc, char *argv[]){ QApplication app(argc, argv); QWidget *window = makeWindow(); window->show(); const int result = app.exec(); delete window; return result;}配套 CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)project(QssExample LANGUAGES CXX)set(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_AUTOMOC ON)find_package(Qt6 REQUIRED COMPONENTS Widgets)add_executable(qss-example main.cpp)target_link_libraries(qss-example PRIVATE Qt6::Widgets)用已配置好 Qt Kit 的 Qt Creator 打开 CMake 工程并构建。程序不依赖外部图片或第三方控件。
匹配、优先级和继承分开判断
跳转到“匹配、优先级和继承分开判断”同一层样式中,对象名规则比纯类型规则更具体;具体程度相同时,后面的规则生效。C++ 继承层级不会给类型选择器额外权重。例如两个纯类型规则分别选择 QPushButton 和 QAbstractButton,后者放在后面时也能覆盖前者。
控件自己的样式表优先于父对象和应用的样式表,不能只比较两个选择器的长短。QSS 不实现 !important,应调整规则位置和作用范围。规则说明见 Qt Style Sheet Syntax。
还要区分以下三种现象:
- 父对象的样式表可以包含匹配子对象的规则,这属于样式级联。
- 只给父控件匹配到的 QSS
color或font,默认不会让所有子控件像浏览器 CSS 那样自动继承这些值。 - 子标签背景透明时能看到父对象绘制的颜色,这是透明合成,不是子标签取得了同一个背景色值。
需要全局字体、背景或自定义 QWidget 的绘制支持时,继续阅读 全局样式与自定义背景。