Qt:Qt界面优化
目录
QSS
QSS背景介绍
QSS基本语法
QSS设置方法
指定控件样式设置
全局样式设置
从文件加载样式表
使用Qt Designer编辑样式
选择器
选择器概况
子控件选择器
伪类选择器
样式属性(盒模型)
控件样式
按钮
复选框
绘图
基础概念
绘制各种形状
设置画笔
设置画刷
绘制图片
其他设置
移动画家设置
保存/加载画家的状态
特殊的绘图设备
QPixmap
QImage
QPicture
QSS
QSS背景介绍
在网页前端开发领域中,CSS是⼀个至关重要的部分,描述了⼀个网页的"样式",从起到对网页美化的作用。
- 所谓样式,包括不限于大小,位置,颜色,背景,间距,字体等等。
- 现在的网页很难找到没有CSS的,可以说让"界面好看"是⼀个刚需。
- 网页开发作为GUI的典型代表,也对于其他客户端GUI开发产⽣了影响.Qt也是其中之⼀.
Qt 仿照CSS的模式,引入了QSS,来对Qt中的控件做出样式上的设定,从而允许程序员写出界面更好看的代码。
- 同样受到HTML的影响,Qt还引⼊了QML来描述界面,甚至还可以直接把⼀个原生的html页面加载到界面上,这部分内容这里不做讨论。
当然,由于Qt本身的设计理念和网页前端还是存在一定差异的,因此QSS中只能支持部分CSS属性,整体来说QSS要比CSS更简单⼀些。
注意:如果通过QSS设置的样式和通过C++代码设置的样式冲突,则QSS优先级更高。
QSS基本语法
对于CSS来说,基本的语法结构非常简单
选择器
{
属性名: 属性值;
}
QSS沿用户了这样的设定
选择器
{
属性名: 属性值;
}
其中:
- 选择器:描述了"哪个widget要应用样式规则"
- 属性:是一个键值对,属性名表示要设置哪种样式,属性值表示了设置的样式的值
例如:
QPushButton { color: red; }
或者:
QPushButton {
color: red;
}
QSS设置方法
指定控件样式设置
QWidget 中包含了 setStyleSheet 方法,可以直接设置样式。给指定控件设置样式之后,该控件的子元素也会受到影响
代码示例:子控件受影响
在界面上创建一个按钮
修改widget.cpp,不给按钮设置样式,而是给 Widget 设置样式(Widget是QPushButton的父控件)
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->setStyleSheet("QPushButton { color:red }");
}
Widget::~Widget()
{
delete ui;
}
运行程序,可以看到样式对于子控件按钮同样会生效
全局样式设置
可以通过 QApplication 的 setStyleSheet 方法设置整个程序的全局样式
全局样式优点:
- 使同一个样式针对多个控件生效,代码更简洁
- 所有控件样式内聚在一起,便于维护和问题排查
代码示例:使用全局样式
在界面上创建三个按钮
编辑main.cpp,设置全局样式
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
a.setStyleSheet("QPushButton { color:red; }");
Widget w;
w.show();
return a.exec();
}
此时三个按钮的颜色都设置为红色了
代码示例:样式的层叠性
若通过全局样式给某个控件设置了属性1,通过指定控件样式给控件设置属性2,那么这两个属性都会产生作用
在界面上创建两个按钮
编写main.cpp,设置全局样式,把按钮文本设置为红色。
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 设置全局样式
a.setStyleSheet("QPushButton { color: red; }");
Widget w;
w.show();
return a.exec();
}
编写widget.cpp,给第⼀个按钮设置字体大小
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置指定控件样式
ui->pushButton->setStyleSheet("QPushButton { font-size: 50px} ");
}
运行程序,可以看到,对于第⼀个按钮来说,同时具备了颜色和字体大小样式,而第⼆个按钮只有颜色样式,说明针对第⼀个按钮,两种设置方式设置的样式,叠加起来了。
形如上述这种属性叠加的效果,我们称为"层叠性",CSS全称为CascadingStyleSheets,其中Cascading就是"层叠性"的意思,QSS也继承了这样的设定,实际上把QSS叫做QCSS也许更合适⼀些。
代码示例:样式的优先级
如果果全局样式,和指定控件样式冲突,则指定控件样式优先展示,在界面上创建两个按钮
编辑main.cpp,把全局样式设置为红色
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 设置全局样式
a.setStyleSheet("QPushButton { color: red; }");
Widget w;
w.show();
return a.exec();
}
编辑widget.cpp,把第⼀个按钮样式设为绿色
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 设置第⼀个按钮颜⾊为绿⾊
ui->pushButton->setStyleSheet("QPushButton { color: green; }");
}
运行程序,观察效果,可以看到第⼀个按钮已经成为绿色了,但是第⼆个按钮仍然是红色。
在CSS中也存在类似的优先级规则,通常而言都是"局部"优先级高于"全局"优先级,相当于全局样式先"奠定基调",再通过指定控件样式来"特事特办"。
从文件加载样式表
上述代码都是把样式通过硬编码的方式设置的,这样使QSS代码和C++代码耦合在一起了,并不方便代码的维护。因此更好的做法是把样式放到单独的文件中,然后通过读取文件的方式来加载样式
代码示例:从文件加载全局样式
在界面上创建一个按钮
创建 resource.qrc 文集,并设定前缀为 /
创建 style.qss 文件,并添加到 resource.qrc 中
style.qss 是需要程序运行时加载的,为了规避绝对路径的问题,仍然使用 qrc 的方式来组织(即把资源文件内容打包到cpp代码中)。Qt Creator没有提供创建 qss 文件的选项,直接右键->新建文件->手动设置文件扩展名为qss即可。
使用Qt Creator打开 style.qss 编写内容
QPushButton {
color: red;
}
修改main.cpp,新增一个函数加载样式,并在main函数中调用上述函数设置样式
#include "widget.h"
#include <QApplication>
#include <QFile>
QString loadQSS()
{
QFile file(":/style.qss");
file.open(QFile::ReadOnly);
QString style = file.readAll();
file.close();
return style;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QString style = loadQSS();
a.setStyleSheet(style);
Widget w;
w.show();
return a.exec();
}
使用Qt Designer编辑样式
QSS也可以通过Qt Designer直接编辑,实时预览,同时也能免C++和QSS代码的耦合
代码示例:使用Qt Designer编辑样式
右键按钮,选择"改变样式表"
在弹出的样式表编辑器中,可以直接填写样式。填写完毕点击OK即可
此时Qt Designer的预览界面就会实时显示出样式的变化
这种方式设置样式,样式内容会被以xml格式记录到ui文件中
<property name="styleSheet">
<string notr="true">QPushButton { color: red; }</string>
</property>
同时在控件的 styleSheet 属性中也会体现
选择器
选择器概况
选择器 | 示例 | 说明 |
全局选择器 | * | 选择所有的widget |
类型选择器(type selector) | QPushButton | 选择所有的QPushButton和其子类的控件 |
类选择器 | .QPushButton | 选择所有的QPushButton,不会选择子类 |
ID选择器 | #pushButton_2 | 选择objectName为pushButton_2的控件 |
后代选择器 | QDialog QPushButton | 选择QDialog的所有后代中的QPushButton(子控件、孙子控件等) |
子选择器 | QDialog>QPushButton | 选择QDialog的所有子控件中的QPushButton |
并集选择器 | QPushButton,QLineEdit | 选择QPushButton、QLineEdit这两种控件 |
属性选择器 | QPushButton[flat="false"] | 选择所有QPushButton中,flat属性为false的控件 |
总体来说,QSS选择器的规则和CSS选择器基本⼀致
代码示例:使用类型选择器选中子类控件
在界面上创建⼀个按钮
修改main.cpp,设置全局样式
注意,此处选择器使用的是QWidget,QPushButton也是QWidget的子类,所以会受到QWidget选择器的影响。
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 设置全局样式
a.setStyleSheet("QWidget { color: red; }");
Widget w;
w.show();
return a.exec();
}
运行程序,可以看到按钮的文本颜色已经是红色了
如果把上述样式代码修改为下列代码
a.setStyleSheet(".QWidget { color: red; }");
此时按钮的颜色不会发生改变,此时只是选择QWidget类,而不会选择QWidget的子类QPushButton了。
代码示例:使用id选择器
在界面上创建3个按钮,objectName为pushButton,pushButton_2,pushButton_3
编写main.cpp,设置全局样式
- 先通过QPushButton设置所有的按钮为黄色
- 再通过#pushButton和#pushButton_2分别设置这两个按钮为红色和绿色
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();
}
执行程序,观察效果
当某个控件,通过 类型选择器 和 ID选择器 设置了冲突的样式时,ID选择器样式优先级更高。同理,若是其他的多种选择器作用同一个控件时出现冲突的样式,也会涉及到优先级问题。Qt文档上有具体的优先级规则介绍(参见The Style Sheet Syntax的Conflict Resolution章节)。
实践中可以简单的认为,选择器描述的范围越精准,则优先级越高。⼀般来说,ID选择器优先级是最高的。
子控件选择器
有些控件内部包含了多个"子控件",如QComboBox的下拉后的面板,如QSpinBox的上下按钮等。可以通过子控件选择器::,针对上述子控件进行样式设置
注意:哪些控件拥有哪些子控件,参考文档Qt Style Sheets Reference中List of Sub-Controls章节
代码示例:设置下拉框的下拉按钮样式。
在界面上创建一个下拉框并创建几个选项
创建 resource.qrc 并导入图片 down.png
使用子控件选择器 QComboBox::down-arrow 选中 QComboBox 的下拉按钮,再通过 image 属性设置图片。
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QString style = "QComboBox::down-arrow { image:url(:/down.png); }";
a.setStyleSheet(style);
Widget w;
w.show();
return a.exec();
}
执行程序,观察效果
伪类选择器
伪类选择器是根据控件所处的某个状态被选择的。如按钮被按下、输入框获取到焦点、鼠标移动到某个控件上等
- 当状态具备时,控件被选中,样式生效
- 当状态不具备时,控件不被选中,样式失效
- 使用:的方式定义伪类选择器
常用的伪类选择器:
伪类选择器 | 说明 |
:hover | 鼠标放在控件上 |
:pressed | 鼠标左键按下时 |
:focus | 获取输入焦点时 |
:enabled | 元素处于可用状态时 |
:checked | 被勾选时 |
:read-only | 元素为只读状态时 |
这些状态可以使用 ! 来取反,如 :!hover 就是鼠标离开控件时,:!pressed 就是鼠标松开时,等等
更多伪类选择器的详细情况,参考Qt Style Sheets Reference 的 Pseudo-States 章节。
代码示例:设置按钮的伪类样式
在界面上创建⼀个按钮
编写main.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();
}
运行程序,可以看到,默认情况下按钮文字是红色,鼠标移动上去是绿色,鼠标按下按钮是蓝色。
样式属性(盒模型)
文档的 Qt Style Sheets Reference 章节详细介绍了哪些控件可以设置属性,每个控件都能设置哪些属性等。
在文档的 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 |
代码示例:设置边框和内边距
在界面上创建⼀个label
修改main.cpp,设置全局样式
- border: 5px solid red 相当于border-style: solid; border-width: 5px; border-color: red; 三个属性的简写形式
- padding-left: 10px; 是给左侧设置内边距
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();
}
运行程序,可以看到样式发生了变化
控件样式
按钮
代码示例:自定义按钮
界面上创建⼀个按钮
右键->改变样式表,使用QtDesigner设置样式
QPushButton {
font-size: 20px;
border: 2px solid #8f8f91;
border-radius: 15px;
background-color: #dadbde;
}
QPushButton:pressed {
background-color: #f6f7fa;
}
执行程序,可以看到效果
属性小结
属性 | 说明 |
font-size | 设置文字大小 |
border-radius | 设置圆角矩形. 数值设置的越大,角就"越圆" |
background-color | 设置背景颜色 |
形如 #dadbde 是计算机中通过十六进制表示颜色的方式,这里在"常用控件"章节已经介绍过了,此处不再赘述。
另外,在实际开发中,颜色具体使用哪种好看,是需要⼀定的"艺术细菌"的,往往会有更专业的美工/设计人员来负责,程序员只需要按照设计稿实现程序即可。
复选框
代码示例:自定义复选框
创建⼀个 resource.qrc 文件,并导入以下图片
使用黑色作为默认形态,使用黑色作为hover形态,使用红色作为pressed形态
创建⼀个复选框
编辑复选框的样式
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);
}
运行程序,可以看到此时的复选框就变的丰富起来了
除了上述之外,还有包括像单选框、列表、输入框等各种控件,在这里就不一一演示了。
QSS本身给Qt提供了更丰富的样式设置的能力,但是整体来说QSS的功能是不如CSS的。
在CSS中,整个网页的样式,都是CSS一手负责,CSS功能更强大,并且也更可控。
相⽐之下,Qt中是以原生api为主,来控制控件之间的尺寸、位置等,QSS只是起到辅助的作用。
而且Qt中提供的⼀些"组合控件"(像QComboBox,QSpinBox等)内部的结构是不透明的,此时进行一些样式设置也会存在⼀定的局限性。
另外,做出好看的界面,光靠QSS是不够的,更重要的是需要专业美工做出设计稿,这里主要是了解这个技术。
绘图
基础概念
Qt提供了画图相关的API,可以允许在窗口上绘制任意的图形形状,来完成更复杂的界面设计
注意:所谓的"控件",本质上也是通过画图的方式画上去的。画图 API 和 控件 之间的关系,可以类比成机器指令和高级语言之间的关系。控件是对画图 API 的进一步封装,画图 API 是控件的底层实现。
绘图API核心类
类 | 说明 |
QPainter | "绘画者"或者"画家" 用来绘图的对象,提供了⼀系列drawXXX方法,可以允许我们绘制各种图形 |
QPaintDevice | "画板" 描述了QPainter把图形画到哪个对象上,像咱们之前⽤过的QWidget也是⼀种QPaintDevice(QWidget是QPaintDevice的子类) |
QPen | "画笔" 描述了QPainter画出来的线是什么样的 |
QBrush | "画刷" 描述了QPainter填充⼀个区域是什么样的 |
绘图 API 的使用,一般不会在 QWidget 的构造函数中使用,而是要放到 paintEvent 事件中
paintEvent 事件会在以下情况下被触发:
- 控件首次创建
- 控件被遮挡,再解除遮挡
- 窗口最小化,再恢复
- 控件大小发生变化时
- 主动调用 repaint() 或者 update() 方法(这两个方法都是 QWidget 的方法)
- ......
绘制各种形状
绘制线段
void drawLine(const QPoint &p1, const QPoint &p2);
//p1:绘制起点坐标
//p2:绘制终点坐标
void drawLine (int x1, int y1, int x2, int y2);
//x1,y1:绘制起点坐标
//x2,y2:绘制终点坐标
绘制矩形
void QPainter::drawRect(int x, int y, int width, int height);
//x:窗⼝横坐标
//y:窗⼝纵坐标
//width:所绘制矩形的宽度
//height:所绘制矩形的⾼度
绘制圆形
void QPainter::drawEllipse(const QPoint ¢er, int rx, int ry)
//center:中⼼点坐标
//rx:外接矩形横坐标
//ry:外接矩形纵坐标
绘制文本
设置画笔
QPainter在绘制时,是有一个默认的画笔的。在使用时也可以自定义画笔。在Qt中,QPen类中定义了 QPainter 应该如何绘制形状、线条和轮廓。同时通过 QPen类 可以设置画笔的线宽、颜色、样式等
- 设置画笔颜色:QPen::QPen(const QColor &color) 画笔的颜色主要是通过 QColor 类设置
- 设置画笔宽度:void QPen::setWidth(int width)
- 设置画笔风格:void QPen::setStyle(Qt::PenStyle style)
设置画刷
在 Qt 中,画刷是使用 QBrush类 来描述,画刷大多用于填充。QBrush定义了QPainter的填充模式,具有样式、颜色、渐变以及纹理等属性
画刷的格式中定义了填充的样式,使用 Qt::BrushStyle 枚举,默认值是 Qt::NoBrush,也就是不进行任何填充。可以通过 Qt 助手 查找画刷的格式
设置画刷主要通过void QPen::setBrush(const QBrush &brush)方法,其参数为画刷的格式
绘制图片
Qt提供了四个类来处理图像数据:QImage、QPixmap、QBitmap和QPicture,都是常用的绘图设备。其中 QImage 主要用来进行 I/O 处理,对 I/O 处理操作进行了优化,而且可以用来直接访问和操作像素;QPixmap主要用来在屏幕上显示图像,对在屏幕上显示图像进行了优化;QBitmap是QPixmap的子类,用来处理颜色深度为1的图像,即只能显示黑白两种颜色;QPicture用来记录并重演 QPainter 命令。
绘制简单图片
平移图片
平移图片实际是通过改变坐标来实现。QPainter类提供了 translate()函数 来实现坐标原点的改变
缩放图片
在 Qt 中,图片的放大和缩小可以使用 QPainter类 中的drawPixmap()函数来实现
旋转图片
图片的旋转使用的是 QPainter类 中的 rotate()函数,默认是以原点为中心进行旋转的。若要改变旋转的中心,可以使用translate()函数完成。
其他设置
移动画家设置
有时候在绘制多个图形时,使用同⼀坐标位置,那么绘制出来的图形肯定会重合。可以通过移动画家的位置来使图形不发生重合。
保存/加载画家的状态
在绘制图形的过程中,可以通过 save() 函数来保存画家的状态,使用 restore() 函数还原画家状态
save()函数原型如下:
restore()函数原型如下:
代码示例
上述示例中,在画第三个圆之前,由于还原了画家的状态,所以此时画家的位置坐标会移动到画家状态保存的地方,所以在绘制第三个圆的位置时实际是和第二个圆发生了重叠。
特殊的绘图设备
前⾯的代码中是使用 QWidget 作为绘图设备,在 Qt 中还存在下列三个特殊的绘图设备
- QPixmap 用于在显示器上显示图片
- QImage 用于对图片进行像素级修改
- QPicture 用于对 QPainter 的一系列操作进行存档
QPixmap
QPixmap核心特性
- 使用 QPainter 直接在上面进行绘制图形
- 通过文件路径加载并显示图片
- 搭配 QPainter 的 drawPixmap()函数,可以把这个图片绘制到QLabel、QPushButton等控件上
- 和系统/显示设备强相关,不同系统/显示设备下,QPixmap 的显示可能会有所差别
QImage
QImage核心特性
- 使用 QPainter 直接在上面进行绘制图形
- 通过文件路径加载并显示图片
- 能够针对图片进行像素级别的操作(操作某个指定的像素)
- 独立于硬件的绘制系统,能够在不同系统之上提供一致的显示
QPicture
QPicture核心特性
- 使用 QPainter 直接在上面进行绘制图形
- 通过文件路径加载并显示图片
- 能够记录 QPainter 的操作步骤
- 独立于硬件的绘制系统,能够在不同系统之上提供一致的显示
注意:QPicture加载的必须是自身的存档文件,而不能是任意的png、jpg等图片文件
认识
QPicture类似于很多游戏的Replay功能。如war3经典游戏,即使是一场60分钟的长时间局,生成的replay文件,也不过几百个KB
此处的Replay功能并非是把整个游戏画面都录制保存下来,而是记录了地图中发生的所有事件(地图元素,玩家单位操作,中立生物行为等...)
当回放Replay时,其实就是把上述记录的事件再一条一条的执行一遍,即可还原之前的游戏场景
代码示例
若要记录下 QPainter 的命令,先要使用 QPainter::begin() 函数,将 QPicture 实例作为参数传递进去,以便告诉系统开始记录,记录完毕后使用 QPainter::end() 命令终止。