跳转到内容
新建笔记

QSS 选择器:对象、状态、作用范围与继承

Qt Style Sheets(QSS)先选择控件,再决定这些控件如何绘制。排查样式时,先确认选择器匹配到哪个对象,再检查状态和样式设置位置。

本文示例使用 Qt 6 Widgets,已在 Qt 6.10.2 验证。这里讨论 QWidget 的 QSS;QML/Qt Quick 使用另一套控件与样式机制。

写法匹配范围常见误会
QPushButton该类及其派生类的按钮不只匹配类名恰好相等的实例
QPushButton#myButton类型匹配且 objectName 为 myButton 的对象# 后写对象名,不是变量名或显示文字
QWidget QPushButtonQWidget 后代中的按钮,包含隔了多层的后代QWidget 很宽泛,不能把它当成某个指定面板
#panel QPushButton名为 panel 的对象之下的所有按钮空格表示后代关系
#panel > QPushButtonpanel 的直接按钮子对象布局不是中间的 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 的绘制支持时,继续阅读 全局样式与自定义背景。