[Qt] QSS | Qt Designer | 选择器
目录
一、CSS
1、背景介绍
2、基本语法
3、QSS 设置方式
(1)指定控件样式设置
(2)全局样式设置
(3)从文件加载样式表
(4)使用 Qt Designer 编辑样式
4、选择器
(1)选择器概况
(2)子控件选择器(Sub-Controls)
(3)伪类选择器(Pseudo-States)
一、CSS
1、背景介绍
在网页前端开发领域中,CSS 是一个至关重要的部分,描述了一个网页的 “样式”,从而起到对网页美化的作用。
- 所谓样式,包括不限于大小、位置、颜色、背景、间距、字体等等。
- 现在的网页很难找到没有 CSS 的,可以说让 “界面好看” 是一个刚需。
- 对于针对特定专业领域用户的软件产品,界面设计是否重要?
- 可参考:(11 封私信 / 80 条消息) 对于针对特定专业领域用户的软件产品,界面设计是否重要? - 知乎 (zhihu.com)
网页开发作为 GUI 的典型代表,也对于其他客户端 GUI 开发产生了影响,Qt 也是其中之一。
- Qt 仿照 CSS 的模式,引入了 QSS,来对 Qt 中的控件做出样式上的设定,从而允许我们写出界面更好看的代码。
- 同样受到 HTML 的影响,Qt 还引入了 QML 来描述界面,甚至还可以直接把一个原生的 html 页面加载到界面上。
- 当然,由于 Qt 本身的设计理念和网页前端还是存在一定差异的,因此 QSS 中只能支持部分 CSS 属性。整体来说 QSS 要比 CSS 更简单一些。
注意:如果通过 QSS 设置的样式和通过 C++ 代码设置的样式冲突,则 QSS 优先级更高。
2、基本语法
对于 CSS 来说,基本的语法结构非常简单。
QSS 沿用了其设定:
选择器 {
属性名: 属性值;
}
其中:
- 选择器 描述了 “哪个 widget 要应用样式规则”。
- 属性则是一个键值对,属性名表示要设置哪种样式,属性值表示了设置的样式的值。
例如:
QPushButton { color: red; }
//或者:
QPushButton {
color: red;
}
上述代码的含义表示,针对界面上所有的 QPushButton,都把文本颜色设置为红色。
编写 QSS 时使用单行 和多行的格式均可。
【QSS 基本使用】
(1)在界面上创建一个按钮
(2)编写代码,设置样式
在.cpp 文件中设置:
ui->setupUi(this);
ui->pushButton->setStyleSheet("QPushButton {color:green;}");
(3)运行程序
观察效果,可以看到文本已经是绿色了:
注意:上述代码中,只针对这一个按钮通过 setStyleSheet 方法设置的样式,此时这个样式仅针对该按钮生效。如果创建其他按钮,其他按钮不会受到影响。
其它格式:
3、QSS 设置方式
(1)指定控件样式设置
QWidget 中包含了 setStyleSheet 方法,可以直接设置样式。
另一方面,给指定控件设置样式之后,该控件的子元素也会受到影响。
【子元素受到影响】
A. 在界面上创建两个按钮和一个单行编辑框
B. 修改 widget.cpp
这次不再给按钮设置样式,而是给 Widget 设置样式(Widget 是 QPushButton 的父控件):
C. 运行程序
可以看到样式对于 this 的子控件按钮同样会生效,但是必须是和选择器相关的。
(2)全局样式设置
可以通过 QApplication 的 setStyleSheet 方法设置整个程序的全局样式。
全局样式优点:
- 使同一个样式针对多个控件生效,代码更简洁。
- 把界面上所有控件样式内聚在⼀起,便于维护和问题排查。
【使用全局样式】
A. 在界面上创建三个按钮
B. 编辑 main.cpp,设置全局样式
C. 运行程序
可以看到此时三个按钮的颜色都设置为红色了:
【样式的层叠特性】
如果通过全局样式给某个控件设置了属性 1,通过指定控件样式给控件设置属性 2,那么这两个属性都会产生作用
A. 在界面上创建三个按钮
B. 编写 main.cpp,设置全局样式,把按钮文本设置为红色
C. 编写 widget.cpp,给第一个按钮设置字体大小
ui->pushButton->setStyleSheet("QPushButton{font-size:50px;}");
D. 运行程序
可以看到,对于第一个按钮来说,同时具备了颜色和字体大小样式,而第二个按钮只有颜色样式。
说明针对第一个按钮,两种设置方式设置的样式叠加起来了。
形如上述这种属性叠加的效果被称为 “层叠性”。
CSS 全称为 Cascading Style Sheets,其中 Cascading 就是 “层叠性” 的意思,QSS 也继承了这样的设定。实际上把 QSS 叫做 QCSS 也许更合适一些。
【样式的优先级】
如果全局样式和指定控件样式冲突,则指定控件样式优先展示。
A. 在界面上创建三个按钮
B. 编辑 main.cpp,把全局样式设置为红色
C. 编辑 widget.cpp,把第二个按钮样式设为绿色
ui->pushButton_2->setStyleSheet("QPushButton{color:green;}");
D. 运行程序
观察效果,可以看到第二个按钮已经成为绿色了,但是第一个按钮和第三个按钮仍然是红色。
在 CSS 中也存在类似的优先级规则。通常来说都是 “局部” 优先级高于 “全局” 优先级,相当于全局样式先 “奠定基调”,再通过指定控件样式来 “特事特办”。
- 实际开发中,可以在全局样式中设置比较通用的样式来统一整个程序的界面风格。
- 如果需要针对某个控件进行微调,可以使用局部样式来做出调整。
(3)从文件加载样式表
上述代码都是把样式通过硬编码的方式设置的,这样使 QSS 代码和 C++ 代码耦合在一起了,并不方便代码的维护。
因此更好的做法是把样式放到单独的文件中,然后通过读取文件的方式来加载样式。
【从文件加载全局样式】
A. 在界面上创建一个按钮
B. 创建 resource.qrc 文件,并设定前缀为 /
C. 创建 style.qss 文件,并添加到 resource.qrc 中
- style.qss 是需要程序运行时加载的。为了规避绝对路径的问题,仍然使用 qrc 的方式来组织(即把资源文件内容打包到 cpp 代码中)。
- Qt Creator 没有提供创建 qss 文件的选项,直接 “右键” -> “新建” -> “文本文档”,手动设置文件扩展名为 qss 即可。
D. 使用 Qt Creator 打开 style.qss,编写内容
E. 修改 main.cpp,新增一个函数用来加载样式
#include <QFile>
QString loadQSS() {
QFile file(":/style.qss");
file.open(QFile::ReadOnly);
QString style = file.readAll();
file.close();
return style;
}
F. 修改 main.cpp,在 main 函数中调用上述函数,并设置样式
G. 运行程序
可以看到样式已经生效了:
理论上来说 Qt 应该要提供直接从文件加载样式表的接口。
类似于 setStyleSheetFromFile(const QString& path) 这种,在内部把读文件操作封好。
(4)使用 Qt Designer 编辑样式
QSS 也可以通过 Qt Designer 直接编辑,从而起到实时预览的效果
同时也能把 C++ 和 QSS 代码的解耦合。
【使用 Qt Designer 编辑样式】
A. 在界面上创建一个按钮
B. 选择最外层的窗口,右键按钮,选择 “改变样式表”
C. 在弹出的样式表编辑器中,可以直接填写样式,填写完毕点击 OK 即可
- 这里进行的修改都会记录到 ui 文件中,并且在程序运行时自动生效,还能进行实时预览。
D. 此时 Qt Designer 的预览界面就会实时显示出样式的变化
E. 运行程序
可以看到样式确实发生了改变
这种方式设置样式,样式内容会被以 xml 格式记录到 ui 文件中。
同时在控件的 styleSheet 属性中也会体现:
由于设置样式太灵活,有很多地方都能设置,所以当我们发现一个控件的样式不符合预期的时候,要记得排查这四个地方:
- 全局样式(QAppplication 设置的)
- 指定控件样式(这个控件是否设置了样式)
- qss 文件中的样式
- ui 文件中的样式
- 指定控件的父控件的样式(可能是从父控件继承过来的)
在实际开发中,如果需要设置样式,建议最好统一使用某一种方式来设置。
4、选择器
(1)选择器概况
QSS 的选择器支持以下几种:
选择器类型 | 示例 | 说明 |
全局选择器 |
| 选择所有的 widget。 |
类型选择器 (type selector) |
| 选择所有的 QPushButton 和其子类的控件。 |
类选择器 (class selector) |
| 选择所有的 QPushButton 的控件。不会选择子类。 |
ID 选择器 |
| 选择 objectName 为 pushButton_2 的控件。 |
后代选择器 |
| 选择 QDialog 的所有后代(子控件、孙子控件等等)中的 QPushButton。 |
子选择器 |
| 选择 QDialog 的所有子控件中的 QPushButton。 |
并集选择器 |
| 选择 QPushButton, QLineEdit, QComboBox 这三种控件。(即接下来的样式会针对这三种控件都生效)。 |
属性选择器 |
| 选择所有 QPushButton 中,flat 属性为 false 的控件。 |
总体来说,QSS 选择器的规则和 CSS 选择器基本一致。
【使用类型选择器选中子类控件】
A. 在界面上创建一个按钮
B. 修改 main.cpp,设置全局样式
a.setStyleSheet("QWidget{color:red;}");
- 注意 :此处选择器使用的是 QWidget。QPushButton 也是 QWidget 的子类,所以会受到 QWidget 选择器的影响。
C. 运行程序
- 可以看到按钮的文本颜色已经是红色了
D. 如果把上述样式代码修改为下列代码
a.setStyleSheet(".QWidget{color:red;}");
- 此时按钮的颜色不会发送改变。此时只是选择 QWidget 类,而不会选择 QWidget 的子类 QPushButton 了。
【使用 id 选择器】
A. 在界面上创建 3 个按钮,objectName 为 pushButton、pushButton_2、pushButton_3
B. 编写 main.cpp,设置全局样式
- 先通过 QPushButton 设置所有的按钮为红色。
- 再通过 #pushButton 和 #pushButton_2 分别设置这两个按钮为绿色和黄色。
QString style = "QPushButton { color: red; }";
style += "#pushButton_2 { color: green; }";
style += "#pushButton_3 { color: yellow; }";
a.setStyleSheet(style);
C. 执行效果
当某个控件身上,通过类型选择器和 ID 选择器设置了冲突的样式时,ID 选择器样式优先级更高(遵循局部优先)。
同理,如果是其他的多种选择器作用同一个控件时出现冲突的样式,也会涉及到优先级问题。Qt 文档上有具体的优先级规则介绍(参见 The Style Sheet Syntax 的 Conflict Resolution 章节)
这里的规则计算起来非常复杂(CSS 中也存在类似的设定)。可以简单的认为,选择器描述的范围越精准,则优先级越高。一般来说,ID 选择器优先级是最高的。
如果属性不冲突,还是会同时生效
【使用并集选择器】
A. 创建三个按钮、一个 label、一个单行输入框
B. 编写 main.cpp,设置全局样式
QString style="QPushButton,QLineEdit,QLabel{color:red;}";
a.setStyleSheet(style);
C. 运行程序
可以看到这三种控件的文字颜色都设置为了红色:
- 并集选择器是一种很好的代码复用的方式,很多时候我们希望界面上的多个元素风格是统一的,就可以使用并集选择器,把样式属性同时指定给多种控件。
也可以指定 id 选择器:
展示效果:
(2)子控件选择器(Sub-Controls)
有些控件内部包含了多个 “子控件”,比如 QComboBox 的下拉后的面板,比如 QSpinBox 的上下按钮等。
可以通过子控件选择器 ::,针对上述子控件进行样式设置。
哪些控件拥有哪些子控件,参考文档 Qt Style Sheets Reference 中 List of Sub-Controls 章节。
【设置下拉框的下拉按钮样式】
A. 在界面上创建一个下拉框,并创建几个选项
B. 创建 resource.qrc,并导入图片 down.png
C. 修改 main.cpp,编写全局样式
- 使用子控件选择器 QComboBox::down-arrow 选中了 QComboBox 的下拉按钮。
- 再通过 image 属性设置图片。
QString style="QComboBox::down-arrow{ image:url(:/downPull.png)}";
a.setStyleSheet(style);
D. 执行程序
【修改进度条的颜色】
A. 在界面上创建一个进度条
B. 在 Qt Designer 右侧的属性编辑器中,找到 QWidget 的 styleSheet 属性
编辑如下内容:
- 其中的 chunk 是选中进度条中的每个 “块”,使用 QProgressBar::text 则可以选中文本。
- 同时把 QProcessBar 的 alignment 属性设置为垂直水平居中。
- 此处如果不设置 alignment,进度条中的数字会跑到左上角(这个怀疑是 Qt 本身的 bug,暂时只能先使⽤ alignment 来手动调整一下)。
C. 执行程序
可以看到如下效果,就得到了一个红色的进度条:
通过上述方式,也可以修改文字的颜色,字体大小等样式。
(3)伪类选择器(Pseudo-States)
伪类选择器,是根据控件所处的某个状态被选择的。
例如按钮被按下,输入框获取到焦点,鼠标移动到某个控件上等。
- 当状态具备时,控件被选中,样式生效。
- 当状态不具备时,控件不被选中,样式失效。
使用 : 的方式定义伪类选择器。
常用的伪类选择器:
伪类选择器 | 说明 |
:hover | 鼠标放到控件上 |
:pressed | 鼠标左键按下时 |
:focus | 获取输入焦点时 |
:enabled | 元素处于可用状态时 |
:checked | 被勾选时 |
:read-only | 元素为只读状态时 |
这些状态可以使用 ! 来取反,比如 :!hover 就是鼠标离开控件时,:!pressed 就是鼠标松开时,等等。更多伪类选择器的详细情况可以参考 Qt Style Sheets Reference 的 Pseudo-States 章节。
【设置按钮的伪类样式】
A. 在界面上创建一个按钮
B. 编写 main.cpp,创建全局样式
QString style = "QPushButton { color: red; }";
style += "QPushButton:hover { color: green; }";
style += "QPushButton:pressed { color: blue; }";
a.setStyleSheet(style);
C. 运行程序
可以看到默认情况下按钮文字是红色,鼠标移动上去是绿色,鼠标按下按钮是蓝色:
上述代码也可以使用事件的方式来实现。
【使用事件方式实现同样效果】
A. 创建 MyPushButton 类,继承自 QPushButton
B. 把生成代码中的构造函数 改成带参数 QWidget* 版本的构造函数(否则无法和 Qt Designer 生成的代码适配)
// mypushbutton.h
#include <QPushButton>
class MyPushButton : public QPushButton
{
public:
MyPushButton(QWidget* parent);
};
// mypushbutton.cpp
#include "mypushbutton.h"
MyPushButton::MyPushButton(QWidget* parent) : QPushButton(parent)
{
}
C. 在界面上创建按钮,并提升为 MyPushButton 类型
- 右键按钮,选择 “提升为...”
- 填写提升的类名和头文件:
提升完毕后,在右侧对象树这里,就可以看到类型的变化。
D. 重写 MyPushButton 的四个事件处理函数
a. 修改 mypushbutton.h
class MyPushButton : public QPushButton
{
public:
MyPushButton(QWidget* parent);
void mousePressEvent(QMouseEvent* e);
void mouseReleaseEvent(QMouseEvent* e);
void enterEvent(QEvent* e);
void leaveEvent(QEvent* e);
};
b. 修改 mypushbutton.cpp
- 初始化设为红色
- 鼠标进⼊时设为绿色,离开是还原红色
- 鼠标按下时设为蓝色,松开时还原绿色(松开时鼠标还是在按钮里)
MyPushButton::MyPushButton(QWidget* parent) : QPushButton(parent)
{
this->setStyleSheet("QPushButton { color: red; }");
}
void MyPushButton::mousePressEvent(QMouseEvent *e)
{
this->setStyleSheet("QPushButton { color: blue; }");
}
void MyPushButton::mouseReleaseEvent(QMouseEvent *e)
{
this->setStyleSheet("QPushButton { color: green; }");
}
void MyPushButton::enterEvent(QEvent *e)
{
this->setStyleSheet("QPushButton { color: green; }");
}
void MyPushButton::leaveEvent(QEvent *e)
{
this->setStyleSheet("QPushButton { color: red; }");
}
E. 运行程序
可以看到效果和上述案例一致
很明显,实现同样的功能,伪类选择器要比事件的方式简单很多。
但是不能就说事件机制就不好,事件可以完成的功能很多,不仅仅是样式的改变,还可以包含其他业务逻辑,这一点是伪类选择器无法替代的。