【Qt】界面优化

发布于:2024-04-20 ⋅ 阅读:(19) ⋅ 点赞:(0)

目录

一、QSS

1.1 基本语法

1.2 QSS设置方法

1.2.1 指定控件样式设置

1.2.2 全局样式设置

1.2.3 从文件加载样式表

1.2.4 使用Qt Designer编辑样式

1.3 选择器

1.3.1 介绍

1.3.2 子控件选择器

1.3.3 伪类选择器

1.4 样式属性(盒模型)

1.5 代码示例(登录界面)

二、绘图

2.1 基础概念

2.2 绘制各种形状

2.3 设置画笔

2.4 设置画刷

2.5 绘制图片

2.6 画家设置

2.6.1 移动画家设置

2.6.2 保存/加载画家的状态

2.7 特殊的绘图设备

2.7.1  QPixmap

2.7.2  QImage

2.7.3 QPicture


一、QSS

1.1 基本语法

选择器 {
    属性名: 属性值;
}
  • 选择器:描述了"哪个widget要应用样式规则"
  • 属性:是一个键值对,属性名表示要设置哪种样式,属性值表示了设置的样式的值
QPushButton {
    color: red;
}

注意:若通过 QSS 设置的样式和通过 C++ 代码设置的样式冲突,则QSS优先级更高

1.2 QSS设置方法

1.2.1 指定控件样式设置

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;
}

运行程序,可以看到样式对于子控件按钮同样会生效

1.2.2 全局样式设置

可以通过 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,那么这两个属性都会产生作用

在上一个示例代码的基础上,编写widget.cpp,给第一个按钮设置字体大小

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    ui->pushButton->setStyleSheet("QPushButton { font-size:30px; }");
}

Widget::~Widget()
{
    delete ui;
}

运行程序可以看到,对于第一个按钮而言,同时具备了颜色和字体大小样式,而第二、三个按钮只有颜色样式,说明针对第一个按钮,两种设置方式设置的样式叠加了

代码示例:样式的优先级

若全局样式和指定控件样式冲突,则指定控件样式优先展示

在上一个示例代码的基础上,编写widget.cpp,给第二个按钮设置绿色

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    ui->pushButton->setStyleSheet("QPushButton { font-size:30px; }");
    ui->pushButton_2->setStyleSheet("QPushButton { color:green }");
}

Widget::~Widget()
{
    delete ui;
}

在CSS中也存在类似的优先级规则,通常而言都是"局部"优先级高于"全局"优先级,相当于全局样式先"奠定基调",再通过指定控件样式来"特事特办"

1.2.3 从文件加载样式表

上述代码都是把样式通过硬编码的方式设置的,这样使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();
}

1.2.4 使用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 属性中也会体现

1.3 选择器

1.3.1 介绍

选择器 示例 说明
全局选择器 * 选择所有的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的控件

当某个控件,通过 类型选择器 和 ID选择器 设置了冲突的样式时,ID选择器样式优先级更高。同理,若是其他的多种选择器作用同一个控件时出现冲突的样式,也会涉及到优先级问题。Qt文档上有具体的优先级规则介绍(参见The Style Sheet Syntax的Conflict Resolution章节)

实践中可以简单的认为,选择器描述的范围越精准,则优先级越高。⼀般来说,ID选择器优先级是最高的

1.3.2 子控件选择器

有些控件内部包含了多个"子控件",如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();
}

1.3.3 伪类选择器

伪类选择器是根据控件所处的某个状态被选择的。如按钮被按下、输入框获取到焦点、鼠标移动到某个控件上等

  • 当状态具备时,控件被选中,样式生效
  • 当状态不具备时,控件不被选中,样式失效
  • 使用:的方式定义伪类选择器

常用的伪类选择器:

伪类选择器 说明
:hover 鼠标放在控件上
:pressed 鼠标左键按下时
:focus 获取输入焦点时
:enabled 元素处于可用状态时
:checked 被勾选时
:read-only 元素为只读状态时

这些状态可以使用 ! 来取反,如 :!hover 就是鼠标离开控件时,:!pressed 就是鼠标松开时,等等

更多伪类选择器的详细情况,参考Qt Style Sheets Reference 的 Pseudo-States 章节

1.4 样式属性(盒模型)

文档的 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

1.5 代码示例(登录界面)

在界面上创建元素

使用布局管理器,把上述元素包裹

两个输入框和按钮的minimumHeight均设置为50(元素在布局管理器中无法直接设置width和height,使用minimumWidth和minimumHeight代替,此时垂直方向的sizePolicy要设为fixed)

右键 QCheckBox,选择 Layout Alignment 可以设置checkbox的对齐方式(左对齐、居中对齐、右对齐)

上述控件添加一个父元素 QFrame 并设置 QFrame 和 窗口一样大

注意:顶层窗口的 QWidget 无法设置背景图片,因此需要再套上一层 QFrame。背景图片就设置到 QFrame 上即可

创建 resource.qrc并导入图片

编写QSS样式,使用 border-image 设置背景图片,而不是 background-image。主要是因为 border-image 是可以自动缩放的,这一点在窗口大小发生改变时是非常有意义的

背景色使用 transparent 表示完全透明(应用父元素的背景)

代码设置到QFrame的属性中即可

QFrame {
	border-image: url(:/background.png);
}

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;
}

二、绘图

2.1 基础概念

Qt提供了画图相关的API,可以允许在窗口上绘制任意的图形形状,来完成更复杂的界面设计

注意:所谓的"控件",本质上也是通过画图的方式画上去的。画图 API 和 控件 之间的关系,可以类比成机器指令和高级语言之间的关系。控件是对画图 API 的进一步封装,画图 API 是控件的底层实现

绘图 API 的使用,一般不会在 QWidget 的构造函数中使用,而是要放到 paintEvent 事件中

paintEvent 事件会在以下情况下被触发:

  • 控件首次创建
  • 控件被遮挡,再解除遮挡
  • 窗口最小化,再恢复
  • 控件大小发生变化时
  • 主动调用 repaint() 或者 update() 方法(这两个方法都是 QWidget 的方法)
  • ......

2.2 绘制各种形状

绘制线段 

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 &center, int rx, int ry)
//center:中⼼点坐标
//rx:外接矩形横坐标
//ry:外接矩形纵坐标

绘制文本

2.3 设置画笔

QPainter在绘制时,是有一个默认的画笔的。在使用时也可以自定义画笔。在Qt中,QPen类中定义了 QPainter 应该如何绘制形状、线条和轮廓。同时通过 QPen类 可以设置画笔的线宽、颜色、样式等

  • 设置画笔颜色:QPen::QPen(const QColor &color) 画笔的颜色主要是通过 QColor 类设置
  • 设置画笔宽度:void QPen::setWidth(int width)
  • 设置画笔风格:void QPen::setStyle(Qt::PenStyle style)

2.4 设置画刷

在 Qt 中,画刷是使用 QBrush类 来描述,画刷大多用于填充。QBrush定义了QPainter的填充模式,具有样式、颜色、渐变以及纹理等属性

画刷的格式中定义了填充的样式,使用 Qt::BrushStyle 枚举,默认值是 Qt::NoBrush,也就是不进行任何填充。可以通过 Qt 助手 查找画刷的格式

2.5 绘制图片

Qt提供了四个类来处理图像数据:QImage、QPixmap、QBitmap和QPicture,都是常用的绘图设备。其中 QImage 主要用来进行 I/O 处理,对 I/O 处理操作进行了优化,而且可以用来直接访问和操作像素;QPixmap主要用来在屏幕上显示图像,对在屏幕上显示图像进行了优化;QBitmap是QPixmap的子类,用来处理颜色深度为1的图像,即只能显示黑白两种颜色;QPicture用来记录并重演 QPainter 命令

绘制图片

平移图片

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

缩放图片

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

旋转图片

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

2.6 画家设置

2.6.1 移动画家设置

有时候在绘制多个图形时,使用同⼀坐标位置,那么绘制出来的图形肯定会重合。可以通过移动画家的位置来使图形不发生重合

2.6.2 保存/加载画家的状态

在绘制图形的过程中,可以通过 save() 函数来保存画家的状态,使用 restore() 函数还原画家状态

代码示例 

上述示例中,在画第三个圆之前,由于还原了画家的状态,所以此时画家的位置坐标会移动到画家状态保存的地方,所以在绘制第三个圆的位置时实际是和第二个圆发生了重叠


 

2.7 特殊的绘图设备

前⾯的代码中是使用 QWidget 作为绘图设备,在 Qt 中还存在下列三个特殊的绘图设备

  • QPixmap 用于在显示器上显示图片
  • QImage 用于对图片进行像素级修改
  • QPicture 用于对 QPainter 的一系列操作进行存档

2.7.1  QPixmap

QPixmap核心特性

  • 使用 QPainter 直接在上面进行绘制图形
  • 通过文件路径加载并显示图片
  • 搭配 QPainter 的 drawPixmap()函数,可以把这个图片绘制到QLabel、QPushButton等控件上
  • 和系统/显示设备强相关,不同系统/显示设备下,QPixmap 的显示可能会有所差别

2.7.2  QImage

QImage核心特性

  • 使用 QPainter 直接在上面进行绘制图形
  • 通过文件路径加载并显示图片
  • 能够针对图片进行像素级别的操作(操作某个指定的像素)
  • 独立于硬件的绘制系统,能够在不同系统之上提供一致的显示

2.7.3 QPicture

QPicture核心特性

  • 使用 QPainter 直接在上面进行绘制图形
  • 通过文件路径加载并显示图片
  • 能够记录 QPainter 的操作步骤
  • 独立于硬件的绘制系统,能够在不同系统之上提供一致的显示

注意:QPicture加载的必须是自身的存档文件,而不能是任意的png、jpg等图片文件

认识

QPicture类似于很多游戏的Replay功能。如war3经典游戏,即使是一场60分钟的长时间局,生成的replay文件,也不过几百个KB

此处的Replay功能并非是把整个游戏画面都录制保存下来,而是记录了地图中发生的所有事件(地图元素,玩家单位操作,中立生物行为等...)

当回放Replay时,其实就是把上述记录的事件再一条一条的执行一遍,即可还原之前的游戏场景

代码示例

若要记录下 QPainter 的命令,先要使用 QPainter::begin() 函数,将 QPicture 实例作为参数传递进去,以便告诉系统开始记录,记录完毕后使用 QPainter::end() 命令终止