文章目录
- [第六章 界面优化](#第六章 界面优化)
-
- [1. QSS](#1. QSS)
-
- [1.1 背景介绍](#1.1 背景介绍)
- [1.2 基本语法](#1.2 基本语法)
- [1.3 QSS设置方式](#1.3 QSS设置方式)
-
- [1.3.1 指定控件样式设置](#1.3.1 指定控件样式设置)
- [1.3.2 全局样式设置](#1.3.2 全局样式设置)
- [1.3.3 从文件加载样式表](#1.3.3 从文件加载样式表)
- [1.3.4 使用QtDesigner编辑样式](#1.3.4 使用QtDesigner编辑样式)
- [1.4 选择器](#1.4 选择器)
-
- [1.4.1 选择器概况](#1.4.1 选择器概况)
- [1.4.2 子控件选择器(Sub-Controls)](#1.4.2 子控件选择器(Sub-Controls))
- [1.4.3 伪类选择器(Pseudo-States)](#1.4.3 伪类选择器(Pseudo-States))
- [1.5 样式属性](#1.5 样式属性)
-
- [1.5.1 盒模型(BoxModel)](#1.5.1 盒模型(BoxModel))
- [1.6 控件样式示例](#1.6 控件样式示例)
-
- [1.6.1 按钮](#1.6.1 按钮)
- [1.6.2 复选框](#1.6.2 复选框)
- [1.6.3 单选框](#1.6.3 单选框)
- [1.6.4 输入框](#1.6.4 输入框)
- [1.6.5 列表](#1.6.5 列表)
- [1.6.6 菜单栏](#1.6.6 菜单栏)
- [1.6.7 登录界面](#1.6.7 登录界面)
- [1.7 小结](#1.7 小结)
- [2. 绘图](#2. 绘图)
-
- [2.1 基本概念](#2.1 基本概念)
- [2.2 绘制各种形状](#2.2 绘制各种形状)
-
- [2.2.1 绘制线段](#2.2.1 绘制线段)
- [2.2.2 绘制矩形](#2.2.2 绘制矩形)
- [2.2.3 绘制圆形](#2.2.3 绘制圆形)
- [2.2.4 绘制文本](#2.2.4 绘制文本)
- [2.2.5 设置画笔](#2.2.5 设置画笔)
- [2.2.6 设置画刷](#2.2.6 设置画刷)
- [2.3 绘制图片](#2.3 绘制图片)
-
- [2.3.1 绘制简单图片](#2.3.1 绘制简单图片)
- [2.3.2 平移图片](#2.3.2 平移图片)
- [2.3.3 缩放图片](#2.3.3 缩放图片)
- [2.3.4 旋转图片](#2.3.4 旋转图片)
- [2.4 其他设置](#2.4 其他设置)
-
- [2.4.1 移动画家位置](#2.4.1 移动画家位置)
- [2.4.2 保存/加载画家的状态](#2.4.2 保存/加载画家的状态)
- [2.5 特殊的绘图设备](#2.5 特殊的绘图设备)
-
- [2.5.1 QPixmap](#2.5.1 QPixmap)
- [2.5.2 QImage](#2.5.2 QImage)
- [2.5.3 QPicture](#2.5.3 QPicture)
第六章 界面优化
1. QSS
1.1 背景介绍
在网页前端开发领域中,CSS是一个至关重要的部分.描述了一个网页的"样式".从而起到对网页美化的作用.
所谓样式,包括不限于大小,位置,颜色,背景,间距,字体等等.

现在的网页很难找到没有CSS的.可以说让"界面好看"是一个 刚需 .
"好看"这个事情有没有意义呢?是否一个软件,能满足核心功能即可,界面好看无所谓呢?参考知乎上的这个帖子:https://www.zhihu.com/question/30918916/answer/49934463
网页开发作为GUI的典型代表,也对于其他客户端GUI开发产生了影响.Qt也是其中之一.
Qt仿照CSS的模式,引入了QSS,来对Qt中的控件做出样式上的设定,从而允许程序猿写出界面更好看的代码.
同样受到HTML的影响,Qt还引入了QML来描述界面,甚至还可以直接把一个原生的html页面加载到界面上.
当然,由于Qt本身的设计理念和网页前端还是存在一定差异的,因此QSS中只能支持部分CSS属性.
整体来说QSS要比CSS更简单一些.
注意:如果通过QSS设置的样式和通过C++代码设置的样式冲突,则QSS优先级更高.
1.2 基本语法
对于CSS来说,基本的语法结构非常简单.
cpp
选择器 {
属性名 : 属性值 ;
}
QSS沿用了这样的设定.
cpp
选择器 {
属性名 : 属性值 ;
}
其中:
-
选择器 描述了"哪个widget要应用样式规则".
-
属性 则是一个键值对,属性名表示要设置哪种样式,属性值表示了设置的样式的值.
例如:
cpp
QPushButton { color: red; }
或者
cpp
QPushButton {
color: red;
}
上述代码的含义表示,针对界面上所有的QPushButton,都把文本颜色设置为 红色 .
编写QSS时使用单行的格式和多行的格式均可.
代码示例: QSS基本使用
1)在界面上创建一个按钮.

2)编写代码,设置样式
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->pushButton->setStyleSheet("QPushButton { color: red; }");
}
3)运行程序,观察效果.可以看到文本已经是红色了.

注意 :上述代码中,我们是只针对这一个按钮通过 setStyleSheet 方法设置的样式.此时这个样式仅针对该按钮生效.如果创建其他按钮,其他按钮不会受到影响.
1.3 QSS设置方式
1.3.1 指定控件样式设置
QWidget 中包含了 setStyleSheet 方法,可以直接设置样式.
上述代码我们已经演示了上述设置方式.
另一方面,给指定控件设置样式之后,该控件的子元素也会受到影响.
代码示例: 子元素受到影响
1)在界面上创建一个按钮

2)修改widget.cpp,这次我们不再给按钮设置样式,而是给Widget设置样式(Widget是QPushButton的父控件).
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 给 Widget 本身设置样式 .
this->setStyleSheet("QPushButton { color: red;} ");
}
3)运行程序,可以看到样式对于子控件按钮同样会生效.

1.3.2 全局样式设置
还可以通过 QApplication 的 setStyleSheet 方法设置整个程序的全局样式.
全局样式优点:
-
使同一个样式针对多个控件生效,代码更简洁.
-
所有控件样式内聚在一起,便于维护和问题排查.
代码示例: 使用全局样式
1)在界面上创建三个按钮.

2)编辑main.cpp,设置全局样式
cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 设置全局样式
a.setStyleSheet("QPushButton { color: red; }");
Widget w;
w.show();
return a.exec();
}
3)运行程序,可以看到此时三个按钮的颜色都设置为红色了.

代码示例: 样式的层叠特性
如果通过全局样式给某个控件设置了属性1,通过指定控件样式给控件设置属性2,那么这两个属性都会产生作用.
1)在界面上创建两个按钮

2)编写main.cpp,设置全局样式,把按钮文本设置为红色.
cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 设置全局样式
a.setStyleSheet("QPushButton { color: red; }");
Widget w;
w.show();
return a.exec();
}
3)编写widget.cpp,给第一个按钮设置字体大小.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置指定控件样式
ui->pushButton->setStyleSheet("QPushButton { font-size: 50px} ");
}
4)运行程序,可以看到,对于第一个按钮来说,同时具备了颜色和字体大小样式.而第二个按钮只有颜色样式.
说明针对第一个按钮,两种设置方式设置的样式,叠加起来了.

形如上述这种属性叠加的效果,我们称为"层叠性".CSS全称为CascadingStyleSheets,其中Cascading就是"层叠性"的意思.QSS也继承了这样的设定.实际上把QSS叫做QCSS也许更合适一些~
代码示例: 样式的优先级
如果全局样式,和指定控件样式冲突,则指定控件样式优先展示.
1)在界面上创建两个按钮

2)编辑main.cpp,把全局样式设置为红色.
cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 设置全局样式
a.setStyleSheet("QPushButton { color: red; }");
Widget w;
w.show();
return a.exec();
}
3)编辑widget.cpp,把第一个按钮样式设为绿色.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置第一个按钮颜色为绿色
ui->pushButton->setStyleSheet("QPushButton { color: green; }");
}
4)运行程序,观察效果.可以看到第一个按钮已经成为绿色了,但是第二个按钮仍然是红色.

在CSS中也存在类似的优先级规则.通常来说都是"局部"优先级高于"全局"优先级.相当于全局样式先"奠定基调",再通过指定控件样式来"特事特办".
1.3.3 从文件加载样式表
上述代码都是把样式通过硬编码的方式设置的.这样使QSS代码和C++代码耦合在一起了,并不方便代码的维护.
因此更好的做法是把样式放到单独的文件中,然后通过读取文件的方式来加载样式.
代码示例: 从文件加载全局样式
1)在界面上创建一个按钮

2)创建 resource.qrc 文件,并设定前缀为 / .

3)创建 style.qss 文件,并添加到 resource.qrc 中.
- style.qss 是需要程序运行时加载的.为了规避绝对路径的问题,仍然使用 qrc 的方式来组
织.(即把资源文件内容打包到cpp代码中).
- QtCreator没有提供创建 qss 文件的选项.咱们直接右键->新建文件->手动设置文件扩展名
为qss即可.
4)使用QtCreator打开 style.qss ,编写内容
cpp
QPushButton {
color: red;
}
5)修改main.cpp,新增一个函数用来加载样式
cpp
QString loadQSS() {
QFile file(":/style.qss");
// 打开文件
file.open(QFile::ReadOnly);
// 读取文件内容 . 虽然 readAll 返回的是 QByteArray, 但是 QString 提供了
QByteArray 版本的构造函数 .
QString style = file.readAll();
// 关闭文件
file.close();
return style;
}
6)修改main.cpp,在main函数中调用上述函数,并设置样式.
cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 调用上述函数加载样式
const QString& style = loadQSS();
a.setStyleSheet(style);
Widget w;
w.show();
return a.exec();
}
7)运行程序,可以看到样式已经生效了.

这里要小小的吐槽一下.理论上来说Qt应该要提供,直接从文件加载样式表的接口.类似于setStyleSheetFromFile(const QString& path) 这种,在内部把读文件操作封装好.
1.3.4 使用QtDesigner编辑样式
QSS也可以通过QtDesigner直接编辑,从而起到实时预览的效果.同时也能避免C++和QSS代码的耦合.
代码示例: 使用QtDesigner编辑样式
1)在界面上创建一个按钮

2)右键按钮,选择"改变样式表"

3)在弹出的样式表编辑器中,可以直接填写样式.填写完毕,点击OK即可.

4)此时QtDesigner的预览界面就会实时显示出样式的变化.

5)运行程序,可以看到样式确实发生了改变.

这种方式设置样式,样式内容会被以xml格式记录到ui文件中.
cpp
<property name="styleSheet">
<string notr="true">QPushButton { color: red; }</string>
</property>
同时在控件的 styleSheet 属性中也会体现.
当我们发现一个控件的样式不符合预期的时候,要记得排查这四个地方:
-
全局样式
-
指定控件样式
-
qss文件中的样式
-
ui文件中的样式
1.4 选择器
1.4.1 选择器概况
QSS的选择器支持以下几种:
| 选择器 | 示例 | 说明 |
|---|---|---|
| 全局选择器 | * |
选择所有的 widget。 |
| 类型选择器(type selector) | QPushButton |
选择所有的 QPushButton 和其子类控件。 |
| 类选择器(class selector) | .QPushButton |
选择所有 QPushButton 控件, 不会选择子类。 |
| ID选择器 | #pushButton_2 |
选择 objectName 为 pushButton_2 的控件。 |
| 后代选择器 | QDialog QPushButton |
选择 QDialog 的所有后代中的 QPushButton。 |
| 子选择器 | QDialog > QPushButton |
选择 QDialog 的所有子控件中的 QPushButton。 |
| 并集选择器 | QPushButton, QLineEdit, QComboBox |
选择 QPushButton、QLineEdit、QComboBox 这三种控件。 |
| 属性选择器 | QPushButton[flat="false"] |
选择所有 QPushButton 中 flat 属性为 false 的控件。 |
总体来说,QSS选择器的规则和CSS选择器基本一致.
上述选择器咱们也不需要全都掌握,就只熟悉最常用的几个即可(上述加粗的).
代码示例: 使用类型选择器选中子类控件
1)在界面上创建一个按钮.

2)修改main.cpp,设置全局样式
注意,此处选择器使用的是 QWidget . QPushButton 也是 QWidget 的子类,所以会受到QWidget 选择器的影响.
cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 设置全局样式
a.setStyleSheet("QWidget { color: red; }");
Widget w;
w.show();
return a.exec();
}
3)运行程序,可以看到按钮的文本颜色已经是红色了.

5)如果把上述样式代码修改为下列代码
cpp
a.setStyleSheet(".QWidget { color: red; }");
此时按钮的颜色不会发生改变.此时只是选择 QWidget 类,而不会选择 QWidget 的子类QPushButton 了.

代码示例: 使用id选择器
1)在界面上创建3个按钮, objectName 为 pushButton , pushButton_2 , pushButton_3

2)编写main.cpp,设置全局样式
-
先通过QPushButton设置所有的按钮为黄色.
-
再通过 #pushButton 和 #pushButton_2 分别设置这两个按钮为红色和绿色.
cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 设置全局样式
QString style = "";
style += "QPushButton { color: yellow; }";
style += "#pushButton { color: red; }";
style += "#pushButton_2 { color: green; }";
a.setStyleSheet(style);
Widget w;
w.show();
return a.exec();
}
3)执行程序,观察效果

当某个控件身上,通过类型选择器和ID选择器设置了冲突的样式时,ID选择器样式优先级更高.同理,如果是其他的多种选择器作用同一个控件时出现冲突的样式,也会涉及到优先级问题.Qt文档上有具体的优先级规则介绍(参见TheStyleSheetSyntax的ConflictResolution章节).这里的规则计算起来非常复杂(CSS中也存在类似的设定),咱们此处对于优先级不做进一步讨论.实践中我们可以简单的认为, 选择器描述的范围越精准,则优先级越高 .一般来说,ID选择器优先级是最高的.
代码示例: 使用并集选择器
1)创建按钮,label,单行输入框

2)编写main.cpp,设置全局样式
cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 设置全局样式
a.setStyleSheet("QPushButton, QLabel, QLineEdit { color: red; } ");
Widget w;
w.show();
return a.exec();
}
3)运行程序,可以看到这三种控件的文字颜色都设置为了红色

并集选择器是一种很好的代码复用的方式.很多时候我们希望界面上的多个元素风格是统一的,就可以使用并集选择器,把样式属性同时指定给多种控件.
1.4.2 子控件选择器(Sub-Controls)
有些控件内部包含了多个"子控件".比如QComboBox的下拉后的面板,比如QSpinBox的上下按钮等.
可以通过子控件选择器 :: ,针对上述子控件进行样式设置.
哪些控件拥有哪些子控件,参考文档QtStyleSheetsReference中ListofSub-Controls章节.
代码示例: 设置下拉框的下拉按钮样式
1)在界面上创建一个下拉框,并创建几个选项

2)创建 resource.qrc ,并导入图片 down.png


图标可以从阿里巴巴矢量图标库下载.https://www.iconfont.cn/search/index?searchType=icon&q=down
3)修改main.cpp,编写全局样式
-
使用子控件选择器 QComboBox::down-arrow 选中了 QComboBox 的下拉按钮.
-
再通过 image 属性设置图片.
cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QString style = "";
style += "QComboBox::down-arrow { image: url(:/down.png) }";
a.setStyleSheet(style);
Widget w;
w.show();
return a.exec();
}
4)执行程序,观察效果

代码示例: 修改进度条的颜色
1)在界面上创建一个进度条.

2)在QtDesigner右侧的属性编辑器中,找到QWidget的 styleSheet 属性.
编辑如下内容:
- 其中的 chunk 是选中进度条中的每个"块".使用 QProgressBar::text 则可以选中文本.
cpp
QProgressBar::chunk {background-color: #FF0000;}
同时把 QProcessBar 的 alignment 属性设置为垂直水平居中.

此处如果不设置 alignment ,进度条中的数字会跑到左上角.这个怀疑是Qt本身的bug,暂时只能先使用alignment来手动调整下.
3)执行程序,可以看到如下效果.我们就得到了一个红色的进度条.

通过上述方式,也可以修改文字的颜色,字体大小等样式.
1.4.3 伪类选择器(Pseudo-States)
伪类选择器,是根据控件所处的某个状态被选择的.例如按钮被按下,输入框获取到焦点,鼠标移动到某个控件上等.
-
当状态具备时,控件被选中,样式生效.
-
当状态不具备时,控件不被选中,样式失效.
使用 : 的方式定义伪类选择器.
常用的伪类选择器
| 伪类选择器 | 说明 |
|---|---|
:hover |
鼠标放到控件上 |
:pressed |
鼠标左键按下时 |
:focus |
获取输入焦点时 |
:enabled |
元素处于可用状态时 |
:checked |
被勾选时 |
:read-only |
元素为只读状态时 |
这些状态可以使用 ! 来取反.比如 :!hover 就是鼠标离开控件时, :!pressed 就是鼠标松开时,等等.
更多伪类选择器的详细情况,参考QtStyleSheetsReference的Pseudo-States章节.
代码示例: 设置按钮的伪类样式.
1)在界面上创建一个按钮

2)编写main.cpp,创建全局样式
cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QString style = "";
style += "QPushButton { color: red; }";
style += "QPushButton:hover { color: green; }";
style += "QPushButton:pressed { color: blue; }";
a.setStyleSheet(style);
Widget w;
w.show();
return a.exec();
}
3)运行程序,可以看到,默认情况下按钮文字是红色,鼠标移动上去是绿色,鼠标按下按钮是蓝色.



上述代码也可以使用事件的方式来实现.
代码示例: 使用事件方式实现同样效果
1)创建 MyPushButton 类,继承自 QPushButton

2)把生成代码中的构造函数改成带参数 QWidget* 版本的构造函数.(否则无法和QtDesigner生成的代码适配).
mypushbutton.h
cpp
#include <QPushButton>
class MyPushButton : public QPushButton
{
public:
MyPushButton(QWidget* parent);
};
mypushbutton.cpp
cpp
#include "mypushbutton.h"
MyPushButton::MyPushButton(QWidget* parent) : QPushButton(parent)
{
}
3)在界面上创建按钮,并提升为 MyPushButton 类型

右键按钮,选择"提升为"

填写提升的类名和头文件.

提升完毕后,在右侧对象树这里,就可以看到类型的变化.

4)重写MyPushButton的四个事件处理函数
修改mypushbutton.h
cpp
class MyPushButton : public QPushButton
{
public:
MyPushButton(QWidget* parent);
void mousePressEvent(QMouseEvent* e);
void mouseReleaseEvent(QMouseEvent* e);
void enterEvent(QEvent* e);
void leaveEvent(QEvent* e);
};
修改mypushbutton.cpp
-
初始化设为红色
-
鼠标进入时设为绿色,离开是还原红色.
-
鼠标按下时设为蓝色,松开时还原绿色(松开时鼠标还是在按钮里).
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; }");
}
5)运行程序,可以看到效果和上述案例一致.



很明显,实现同样的功能,伪类选择器要比事件的方式简单很多.但是不能就说事件机制就不好.事件可以完成的功能很多,不仅仅是样式的改变,还可以包含其他业务逻辑.这一点是伪类选择器无法替代的.
1.5 样式属性
QSS中的样式属性非常多,不需要都记住.核心原则还是用到了就去查.
大部分的属性和CSS是非常相似的.
文档的 Qt Style Sheets Reference 章节详细介绍了哪些控件可以设置属性,每个控件都能设置哪些属性等.
相关的代码示例,在后面具体介绍.
在翻阅文档的时候涉及到一个关键术语"盒模型"(BoxModel).这里我们需要介绍一下.
1.5.1 盒模型(BoxModel)
在文档的 Customizing Qt Widgets Using Style Sheets 的 The Box Model 章节介绍了盒模型.

一个遵守盒模型的控件,由上述几个部分构成.
-
Content矩形区域:存放控件内容.比如包含的文本/图标等.
-
Border矩形区域:控件的边框.
-
Padding矩形区域:内边距.边框和内容之间的距离.
-
Margin矩形区域:外边距.边框到控件 geometry 返回的矩形边界的距离
默认情况下,外边距,内边距,边框宽度都是0.
可以通过一些QSS属性来设置上述的边距和边框的样式.
| QSS属性 | 说明 |
|---|---|
margin |
设置四个方向的外边距, 复合属性。 |
padding |
设置四个方向的内边距, 复合属性。 |
border-style |
设置边框样式。 |
border-width |
设置边框粗细。 |
border-color |
设置边框颜色。 |
border |
复合属性, 相当于 border-style + border-width + border-color。 |
代码示例: 设置边框和内边距
1)在界面上创建一个label

2)修改main.cpp,设置全局样式
- border: 5px solid red 相当于 border-style: solid; border-width: 5px;
border-color: red; 三个属性的简写形式.
- padding-left: 10px; 是给左侧设置内边距.
cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
a.setStyleSheet("QLabel { border: 5px solid red; padding-left: 10px; }");
Widget w;
w.show();
return a.exec();
}
3)运行程序,可以看到样式发生了变化

代码示例: 设置外边距
为了方便确定控件位置,演示外边距效果,我们使用代码创建一个按钮.
1)修改widget.cpp,创建按钮,并设置样式.
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* btn = new QPushButton(this);
btn->setGeometry(0, 0, 100, 100);
btn->setText("hello");
btn->setStyleSheet("QPushButton { border: 5px solid red; margin: 20px; }");
const QRect& rect = btn->geometry();
qDebug() << rect;
}
2)运行程序,可以看到,当前按钮的边框被外边距挤的缩小了.但是获取到的按钮的 Geometry 是不变的.


1.6 控件样式示例
下面给出一些常用控件的样式示例.
1.6.1 按钮
代码示例: 自定义按钮
1)界面上创建一个按钮

2)右键->改变样式表,使用QtDesigner设置样式
cpp
QPushButton {
font-size: 20px;
border: 2px solid #8f8f91;
border-radius: 15px;
background-color: #dadbde;
}
QPushButton:pressed {
background-color: #f6f7fa;
}
3)执行程序,可以看到效果


按钮点击之前按钮点击之后
属性小结
| 属性 | 说明 |
|---|---|
font-size |
设置文字大小. |
border-radius |
设置圆角矩形. 数值设置的越大,角就"越圆". |
background-color |
设置背景颜色. |
形如 #dadbde 是计算机中通过十六进制表示颜色的方式.这里在"常用控件"章节已经介绍过了.此处不再赘述.另外,在实际开发中,颜色具体使用哪种好看,是需要一定的"艺术细菌"的.往往公司会有专门的美工/设计人员来负责.程序猿只需要按照设计稿实现程序即可.
1.6.2 复选框
代码示例: 自定义复选框
1)创建一个 resource.qrc 文件,并导入以下图片.

使用黑色作为默认形态.
使用蓝色作为hover形态.
使用红色作为pressed形态.
注意这里的文件命名.

使用阿里矢量图标库,可以下载到上述图片.下载的时候可以手动选择颜色.
2)创建一个复选框

3)编辑复选框的样式
cpp
QCheckBox {
font-size: 20px;
}
QCheckBox::indicator {
width: 20px;
height: 20px;
}
QCheckBox::indicator:unchecked {
image: url(:/checkbox-unchecked.png);
}
QCheckBox::indicator:unchecked:hover {
image: url(:/checkbox-unchecked_hover.png);
}
QCheckBox::indicator:unchecked:pressed {
image: url(:/checkbox-unchecked_pressed.png);
}
QCheckBox::indicator:checked {
image: url(:/checkbox-checked.png);
}
QCheckBox::indicator:checked:hover {
image: url(:/checkbox-checked_hover.png);
}
QCheckBox::indicator:checked:pressed {
image: url(:/checkbox-checked_pressed.png);
}
4)运行程序,可以看到此时的复选框就变的丰富起来了.

此处截图看不出来效果.大家可以自行验证.
小结:
| 要点 | 说明 |
|---|---|
::indicator |
子控件选择器, 选中 checkbox 中的对钩部分。 |
:hover |
伪类选择器, 选中鼠标移动上去的状态。 |
:pressed |
伪类选择器, 选中鼠标按下的状态。 |
:checked |
伪类选择器, 选中 checkbox 被选中的状态。 |
:unchecked |
伪类选择器, 选中 checkbox 未被选中的状态。 |
width |
设置子控件宽度。对于普通控件无效, 普通控件使用 geometry 方式设定尺寸。 |
height |
设置子控件高度。对于普通控件无效, 普通控件使用 geometry 方式设定尺寸。 |
image |
设置子控件的图片, 如 QSpinBox、QComboBox 等可以使用这个属性设置子控件图片。 |
1.6.3 单选框
代码示例: 自定义单选框
1)创建 resource.qrc 文件,并导入以下图片.






使用黑色作为默认形态.
使用蓝色作为hover形态.
使用红色作为pressed形态.
注意这里的文件命名.

2)在界面上创建两个单选按钮

3)在QtDesigner中编写样式
此处为了让所有QRadioButton都能生效,把样式设置在 Widget 上了.并且使用后代选择器选中了 QWidget 里面的 QRadioButton .
注意!QSS中有些属性,子元素能继承父元素(例如font-size,color等).但是也有很多属性是不能继承的.具体哪些能继承哪些不能继承,规则比较复杂,咱们不去具体研究.实践中我们编写更精准的选择器是上策.
cpp
QWidget QRadioButton {
font-size: 20px;
}
QWidget QRadioButton::indicator {
width: 20px;
height: 20px;
}
QWidget QRadioButton::indicator:unchecked {
image: url(:/radio-unchecked.png);
}
QWidget QRadioButton::indicator:unchecked:hover {
image: url(:/radio-unchecked_hover.png);
}
QWidget QRadioButton::indicator:unchecked:pressed {
image: url(:/radio-unchecked_pressed.png);
}
QWidget QRadioButton::indicator:checked {
image: url(:/radio-checked.png);
}
QWidget QRadioButton::indicator:checked:hover {
image: url(:/radio-checked_hover.png);
}
QWidget QRadioButton::indicator:checked:pressed {
image: url(:/radio-checked_pressed.png);
}
4)运行程序,观察效果

此处截图看不出来效果.大家可以自行验证.
| 要点 | 说明 |
|---|---|
::indicator |
子控件选择器, 选中 radioButton 中的对钩部分。 |
:hover |
伪类选择器, 选中鼠标移动上去的状态。 |
:pressed |
伪类选择器, 选中鼠标按下的状态。 |
:checked |
伪类选择器, 选中 radioButton 被选中的状态。 |
:unchecked |
伪类选择器, 选中 radioButton 未被选中的状态。 |
width |
设置子控件宽度。对于普通控件无效, 普通控件使用 geometry 方式设定尺寸。 |
height |
设置子控件高度。对于普通控件无效, 普通控件使用 geometry 方式设定尺寸。 |
image |
设置子控件的图片, 如 QSpinBox、QComboBox 等可以使用这个属性设置子控件图片。 |
1.6.4 输入框
代码示例: 自定义单行编辑框
1)在界面上创建一个单行编辑框

2)在QtDesigner中编写样式.
cpp
QLineEdit {
border-width: 1px;
border-radius: 10px;
border-color: rgb(58, 58, 58);
border-style: inset;
padding: 0 8px;
color: rgb(255, 255, 255);
background:rgb(100, 100, 100);
selection-background-color: rgb(187, 187, 187);
selection-color: rgb(60, 63, 65);
}
3)执行程序观察效果.

| 属性 | 说明 |
|---|---|
border-width |
设置边框宽度。 |
border-radius |
设置边框圆角。 |
border-color |
设置边框颜色。 |
border-style |
设置边框风格。 |
padding |
设置内边距。 |
color |
设置文字颜色。 |
background |
设置背景颜色。 |
selection-background-color |
设置选中文字的背景颜色。 |
selection-color |
设置选中文字的文本颜色。 |
1.6.5 列表
代码示例: 自定义列表框
1)在界面上创建一个ListView

2)编写代码
cpp
QListView::item:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #FAFBFE, stop: 1 #DCDEF1);
}
QListView::item:selected {
border: 1px solid #6a6ea9;
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #6a6ea9, stop: 1 #888dd9);
}
3)执行程序,观察效果

| 要点 | 说明 |
|---|---|
| ::item | 选中QListView中的具体条目. |
:hover |
选中鼠标悬停的条目 |
:selected |
选中某个被选中的条目. |
background |
设置背景颜色 |
border |
设置边框. |
qlineargradient |
设置渐变色. |
关于 qlineargradientqlineargradient 有6个参数.x1,y1:标注了一个起点.x2,y2:标注了一个终点.这两个点描述了一个"方向".例如:• x1:0,y1:0,x2:0,y2:1就是垂直方向从上向下进行颜色渐变.
• x1:0,y1:0,x2:1,y2:0就是水平方向从左向右进行颜色渐变.• x1:0,y1:0,x2:1,y2:1就是从左上往右下方向进行颜色渐变.stop0和stop1描述了两个颜色.渐变过程就是从stop0往stop1进行渐变的.
代码例子: 理解渐变色
1)界面不创建任何控件
2)编写样式
cpp
QWidget {
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop: 0 #fff,
stop: 1 #000);
}
当前按照 垂直从上往下 从白色过渡到黑色
执行效果

3)修改代码
cpp
QWidget {
background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop: 0 #fff,
stop: 1 #000);
}
当前按照 水平从左往右 从白色过渡到黑色
执行效果

1.6.6 菜单栏
代码示例: 自定义菜单栏
1)创建菜单栏

创建若干菜单项和一个分隔符.
2)编写样式
cpp
QMenuBar {
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 lightgray, stop:1 darkgray);
spacing: 3px; /* spacing between menu bar items */
}
QMenuBar::item {
padding: 1px 4px;
background: transparent;
border-radius: 4px;
}
QMenuBar::item:selected { /* when selected using mouse or keyboard */
background: #a8a8a8;
}
QMenuBar::item:pressed {
background: #888888;
}
QMenu {
background-color: white;
margin: 0 2px; /* some spacing around the menu */
}
QMenu::item {
padding: 2px 25px 2px 20px;
border: 3px solid transparent; /* reserve space for selection border */
}
QMenu::item:selected {
border-color: darkblue;
background: rgba(100, 100, 100, 150);
}
QMenu::separator {
height: 2px;
background: lightblue;
margin-left: 10px;
margin-right: 5px;
}
3)执行程序,观察效果

| 要点 | 说明 |
|---|---|
QMenuBar::item |
选中菜单栏中的元素。 |
QMenuBar::item:selected |
选中菜单栏中被选中的元素。 |
QMenuBar::item:pressed |
选中菜单栏中鼠标点击的元素。 |
QMenu::item |
选中菜单中的元素。 |
QMenu::item:selected |
选中菜单中被选中的元素。 |
QMenu::separator |
选中菜单中的分割线。 |
1.6.7 登录界面
基于上述学习过的QSS样式,制作一个美化版本的登录界面.
1)在界面上创建元素

2)使用布局管理器,把上述元素包裹一下.

-
使用 QVBoxLayout 来管理上述控件.
-
两个输入框和按钮的minimumHeight均设置为50.(元素在布局管理器中无法直接设置width和
height,使用minimumWidth和minimumHeight代替,此时垂直方向的sizePolicy要设为fixed).
- 右键 QCheckBox ,选择 Layout Alignment 可以设置checkbox的对齐方式(左对齐,居中对
齐,右对齐).
3)设置背景图片.
把上述控件添加一个父元素QFrame,并设置QFrame和窗口一样大.

顶层窗口的 QWidget 无法设置背景图片。因此我们需要再套上一层 QFrame, 背景图片设置到 QFrame 上即可。
创建resource.qrc,并导入图片


编写QSS样式.
- 使用 border-image 设置背景图片,而不是 background-image .主要是因为 border-
image 是可以自动缩放的.这一点在窗口大小发生改变时是非常有意义的.
cpp
QFrame {
border-image: url(:/cat.jpg);
}
此时效果为

4)设置输入框样式
编写QSS代码
cpp
QLineEdit {
color: #8d98a1;
background-color: #405361;
padding: 0 5px;
font-size: 20px;
border-style: none;
border-radius: 10px;
}
运行程序效果:

5)设置checkbox样式
- 背景色使用 transparent 表示完全透明(应用父元素的背景).
cpp
QCheckBox {
color: white;
background-color: transparent;
}
执行效果

6)设置按钮样式
cpp
QPushButton {
font-size: 20px;
color: white;
background-color: #555;
border-style: outset;
border-radius: 10px;
}
QPushButton:pressed {
color: black;
background-color: #ced1db;
border-style: inset;
}
执行程序

最终完整样式代码.这些代码设置到QFrame的属性中即可.
通常我们建议把样式代码集中放置,方便调整和排查.
cpp
QFrame {
border-image: url(:/cat.jpg);
}
QLineEdit {
color: #8d98a1;
background-color: #405361;
padding: 0 5px;
font-size: 20px;
border-style: none;
border-radius: 10px;
}
QCheckBox {
color: white;
background-color: transparent;
}
QPushButton {
font-size: 20px;
color: white;
background-color: #555;
border-style: outset;
border-radius: 10px;
}
QPushButton:pressed {
color: black;
background-color: #ced1db;
border-style: inset;
}
1.7 小结
QSS本身给Qt提供了更丰富的样式设置的能力,但是整体来说QSS的功能是不如CSS的.
在CSS中,整个网页的样式,都是CSS一手负责,CSS功能更强大,并且也更可控.
相比之下,Qt中是以原生api为主,来控制控件之间的尺寸,位置等,QSS只是起到辅助的作用.
而且Qt中提供的一些"组合控件"(像QComboBox,QSpinBox等)内部的结构是不透明的,此时进行一些样式设置也会存在一定的局限性.
另外,做出好看的界面,光靠QSS是不够的.更重要的是需要专业美工做出设计稿.
因此通过QSS的学习,我们的目的是了解这个技术,而不要求大家立即就能做出非常好看的界面.
更多参考内容:
-
官方文档中的 Qt Style Sheets Examples 章节
2. 绘图
2.1 基本概念
虽然Qt已经内置了很多的控件,但是不能保证现有控件就可以应对所有场景.
很多时候我们需要更强的"自定制"能力.
Qt提供了画图相关的API,可以允许我们在窗口上绘制任意的图形形状,来完成更复杂的界面设计.
所谓的"控件",本质上也是通过画图的方式画上去的.画图API和控件之间的关系,可以类比成机器指令和高级语言之间的关系.控件是对画图API的进一步封装;画图API是控件的底层实现.
绘图API核心类
| 类 | 说明 |
|---|---|
QPainter |
"绘画者"或者"画家". 用来绘图的对象,提供了一系列drawXXX方法,可以允许我们绘制各种图 形. |
QPaintDevice |
"画板". 描述了QPainter把图形画到哪个对象上.像咱们之前用过的QWidget也 是一种QPaintDevice(QWidget是QPaintDevice的子类). |
QPen |
"画笔". 描述了QPainter画出来的线是什么样的. |
QBrush |
"画刷". 描述了QPainter填充一个区域是什么样的. |
绘图API的使用,一般不会在QWidget的构造函数中使用,而是要放到 paintEvent 事件中.
关于 paintEventpaintEvent会在以下情况下被触发:• 控件首次创建.• 控件被遮挡,再解除遮挡.窗口最小化, 再恢复.•
窗口最小化,再恢复.•• 控件大小发生变化时.• 主动调用 repaint() 或者 update() 方法.(这两个方法都是 QWidget 的方法).• ...
因此,如果把绘图api放到构造函数中调用,那么一旦出现上述的情况,界面的绘制效果就无法确保符合预期了.
2.2 绘制各种形状
2.2.1 绘制线段
示例1:
cpp
void drawLine(const QPoint &p1, const QPoint &p2);
参数:
p1 :绘制起点坐标
p2 :绘制终点坐标
1、在"widget.h"头文件中声明绘图事件;

2、在"widget.cpp"文件中重写paintEvent()方法;

实现效果如下:

示例2:
cpp
void drawLine ( int x1, int y1, int x2, int y2 );
参数:
x1,y1 :绘制起点坐标
x2,y2 :绘制终点坐标

2.2.2 绘制矩形
cpp
void QPainter::drawRect(int x, int y, int width, int height) ;
参数:
x :窗口横坐标;
y :窗口纵坐标;
width :所绘制矩形的宽度;
height :所绘制矩形的高度;
示例:

2.2.3 绘制圆形
cpp
void QPainter::drawEllipse(const QPoint ¢er, int rx, int ry)
参数:
center :中心点坐标
rx :横坐标
ry :纵坐标
示例:

实现效果:

2.2.4 绘制文本
QPainter类中不仅提供了绘制图形的功能,还可以使用QPainter::drawText()函数来绘制文字,也可以使用QPainter::setFont()设置字体等信息。
示例:

实现效果如下:

2.2.5 设置画笔
QPainter在绘制时,是有一个默认的画笔的。在使用时也可以自定义画笔。在Qt中, QPen类中 定义了QPainter应该如何绘制形状、线条和轮廓。同时通过 QPen类 可以设置画笔的 线宽、颜色、样式、画刷 等。
画笔的颜色可以在实例化画笔对象时进行设置,画笔的宽度是通过setWidth()方法进行设置,画笔的风格是通过setStyle()方法进行设置,设置画刷主要是通过setBrush()方法。
-
设置画笔颜色:QPen::QPen(constQColor&color)画笔的颜色主要是通过QColor类设置;
-
设置画笔宽度:voidQPen::setWidth(intwidth)
-
设置画笔风格:voidQPen::setStyle(Qt::PenStylestyle)
画笔的风格有:

示例:画笔的使用

实现效果如下:

2.2.6 设置画刷
在Qt中,画刷是使用QBrush类来描述,画刷大多用于填充。QBrush定义了QPainter的填充模式,具有样式、颜色、渐变以及纹理等属性。
画刷的格式中定义了填充的样式,使用Qt::BrushStyle枚举,默认值是Qt::NoBrush,也就是不进行任何填充。可以通过Qt助手查找画刷的格式。如下图示:

设置画刷主要通过voidQPen::setBrush(constQBrush&brush)方法,其参数为画刷的格式。
示例:

实现效果:

2.3 绘制图片
Qt提供了四个类来处理图像数据:QImage、QPixmap、QBitmap和QPicture,它们都是常用的绘图设备。其中QImage主要用来进行I/O处理,它对I/O处理操作进行了优化,而且可以用来直接访问和操作像素;QPixmap主要用来在屏幕上显示图像,它对在屏幕上显示图像进行了优化;QBitmap是QPixmap的子类,用来处理颜色深度为1的图像,即只能显示黑白两种颜色;QPicture用来记录并重演QPainter命令。这一节只讲解QPixmap。
2.3.1 绘制简单图片
- 新建Qt项目,基类选择QWidget,项目名称为QPainter。在"widget.h"头文件中声明绘画事件;如下图示:

- 添加资源文件;
首先准备一些图片资源文件,并将这些图片资源文件放在同一个文件夹中,将该文件夹复制到本项目中;

- 选中项目文件,鼠标右键------->addnew...

- 点击"addnew..."之后,出现如下界面:

- 选择"Choose..."之后,给资源文件命名;

- 点击"下一步",出现如下界面,点击"完成";

- 给资源文件添加前缀,并将资源文件添加至项目中;

- 将所有的资源文件添加到项目中,方便后续使用;

- 点击"构建并运行"按钮,将资源文件添加到项目中;

- 在"widget.cpp"文件中实现画图片功能;

实现效果如下:

2.3.2 平移图片
平移图片实际是通过改变坐标来实现。QPainter类中提供了translate()函数来实现坐标原点的改变。
如下示例:

实现效果如下:

2.3.3 缩放图片
在Qt中,图片的放大和缩小可以使用 QPainter类 中的 drawPixmap()函数 来实现。
示例:

实现效果如下:

2.3.4 旋转图片
图片的旋转使用的是 QPainter类 中的 rotate()函数 ,它默认是以原点为中心进行旋转的。如果要改变旋转的中心,可以使用 translate()函数 完成。
示例:

实现效果如下:

2.4 其他设置
2.4.1 移动画家位置
有时候在绘制多个图形时,想使用同一坐标位置,那么绘制出来的图形肯定会重合,此时,可以通过移动画家的位置来使图形不发生重合。
示例1:未移动画家位置

实现效果如下:

示例2:移动画家位置
- 使用 translate 移动画家所在位置.

实现效果如下:

2.4.2 保存/加载画家的状态
在绘制图形的过程中,可以通过save()函数来保存画家的状态,使用restore()函数还原画家状态。
save()函数原型如下:
voidQPainter::save();

restore()函数原型如下:
voidQPainter::restore();

示例:

实现效果如下:

说明:
上述示例中,在画第三个圆之前,由于还原了画家的状态,所以此时画家的位置坐标会移动到画家状态保存的地方,所以在绘制第三个圆的位置时实际是和第二个圆发生了重叠。
2.5 特殊的绘图设备
前面的代码中我们是使用QWidget作为绘图设备.在Qt中还存在下列三个比较特殊的绘图设备.此处我们也简要介绍.
-
QPixmap用于在显示器上显示图片.
-
QImage用于对图片进行像素级修改.
-
QPicture用于对QPainter的一系列操作进行存档.
2.5.1 QPixmap
QPixmap核心特性:
-
使用QPainter直接在上面进行绘制图形.
-
通过文件路径加载并显示图片.
-
搭配QPainter的drawPixmap()函数,可以把这个图片绘制到一个QLabel、QPushButton等控件
上.
- 和系统/显示设备强相关,不同系统/显示设备下,QPixmap的显示可能会有所差别.
示例:

实现效果:


2.5.2 QImage
QImage的核心特性:
-
使用QPainter直接在上面进行绘制图形.
-
通过文件路径加载并显示图片.
-
能够针对图片进行像素级别的操作(操作某个指定的像素).
-
独立于硬件的绘制系统,能够在不同系统之上提供一致的显示.
代码示例: QImage作为绘图设备的使用



代码示例: QImage绘图时对像素的修改
1)新建Qt项目,添加图片资源文件到项目中;如下图示:
2)在widget.h头文件中声明绘图事件;

3)在widget.cpp文件中重写绘图事件,使用QImage对图片像素进行修改;
-
通过 setPixel 设置某个像素的颜色值.
-
使用qRgb表示一个具体的颜色.

4)执行效果如下:
没有修改像素之前:

修改像素之后:

2.5.3 QPicture
QPicture核心特性:
-
使用QPainter直接在上面进行绘制图形.
-
通过文件路径加载并显示图片.
-
能够记录QPainter的操作步骤.
-
独立于硬件的绘制系统,能够在不同系统之上提供一致的显示.
注意:
QPicture加载的必须是自身的存档文件,而不能是任意的png,jpg等图片文件.
QPicture类似于很多游戏的Replay功能.例如像war3这样的经典游戏,即使是一场60分钟的膀胱局,生成的replay文件,也不过几百个KB.此处的Replay功能并非是把整个游戏画面都录制保存下来,而是记录了地图中发生的所有事件(地图元素,玩家单位操作,中立生物行为等...).
当回放Replay的时候其实就是把上述记录的事件再一条一条的执行一遍即可还原之前的游戏场景了.不了解游戏的同学,也可以理解成警察蜀黍录笔录,并通过笔录还原案发现场.
如果要记录下QPainter的命令,首先要使用QPainter::begin()函数,将QPicture实例作为参数传递进去,以便告诉系统开始记录,记录完毕后使用QPainter::end()命令终止。如下示例:
示例:

实现效果:

通过QPicture重现绘图指令后,实现的效果如下:
