Qt 界面优化 --- 绘图
绘图
Qt 虽已内置诸多控件,但无法保证适配所有场景。很多时候,我们需要更灵活的 “自定义” 能力,在窗口上绘制任意图形形状,以实现复杂界面设计。Qt 提供了相关绘图 API,支持在窗口绘制任意图形。
基本概念
所谓 “控件” 本质上也是通过绘图方式生成的。画图是对控件的一种封装,可以类比成机器语言和高级语言的关系;控件是对画图 API 的进一步封装,能让画图 API 更简单实现。
绘图 API 核心类
类 | 说明 |
---|---|
QPoint | “绘图者” 或 “画笔” 位置,用来绘图时,提供了点(x 和 y 坐标),可在坐标系(多种相关图形类)里描述点。 |
QPointF | 同 QPoint,精度更高(用到浮点数),QPainter 绘制时,QPoint 会被隐式转换为 QPointF。 |
QPainter | 描述了 “绘图者” 要画到哪个对象上,绘制用的 QWidget 也属于 QPaintDevice(QWidget 是 QPaintDevice 的子类)。 |
QPen | 描述了 QPainter 画出来的线是什么样的。(线的属性) |
QBrush | 描述了 QPainter 填充一个区域是什么样的。(区域,边界的属性) |
注意:绘图 API 的使用,一般不会在 QWidget
的构造函数中使用,而是要放到 paintEvent
事件处理函数中。
关于 paintEvent
paintEvent
会在以下情况被触发:
- 控件首次创建;(比如往 QWidget 上画画,QWidget 创建之前,画的东西当然不生效,首次创建 QWidget 就能显示出来画的东西)
- 控件被遮挡后,再恢复显示/接触遮挡;(这个时机,我们进行绘制也是很重要的,否则就会在被遮挡之后就没了)
- 窗口大小变化;
- 窗口最小化、再恢复;
- 主动调用
repaint()
或者update()
方法(这两个方法都是QWidget
的方法); - ……
因此,若把绘图 API 放到构造函数中调用,一旦出现上述情况,界面绘制效果无法确保符合预期。
绘制各种形状
绘制线段
示例 1:
void drawLine(const QPoint &p1, const QPoint &p2);
// p1:绘制起点坐标
// p2:绘制终点坐标
在 “widget.h” 头文件中声明事件:
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();// 声明绘画事件void paintEvent(QPaintEvent *event);
};#endif // WIDGET_H
在 “widget.cpp” 文件中编写 paintEvent()
方法:
void Widget::paintEvent(QPaintEvent *event)
{(void)event;//绘图工作就会放在这里被执行QPainter painter(this);//此处指定的this,不是父对象,而是指定绘制的设备(往啥东西上画)//画一个线段painter.drawLine(20, 20, 200, 20);//水平线段painter.drawLine(QPoint(20, 100), QPoint(200, 100));painter.drawLine(20, 20, 20, 300);//垂直线段painter.drawLine(20, 20, 100, 300);//斜画
}
实现效果:
绘制矩形
void QPainter::drawRect(int x, int y, int width, int height);
// 参数:
// x,y:窗口横坐标;
// width:所绘制矩形的宽度;
// height:所绘制矩形的高度。
示例:
void Widget::paintEvent(QPaintEvent *event)
{// 实例化画家对象 this:表示的是在当前窗口中绘图,即绘图设备QPainter painter(this);// 绘制矩形painter.drawRect(20, 20, 100, 50);
}
绘制圆形
void QPainter::drawEllipse(const QPoint ¢er, int rx, int ry);
// 参数:
// center:中心坐标;
// rx:横坐标;
// ry:纵坐标。
示例:
void Widget::paintEvent(QPaintEvent *event)
{// 实例化画家对象 this:表示的是在当前窗口中绘图,即绘图设备QPainter painter(this);// 绘制圆painter.drawEllipse(QPoint(100, 100), 50, 50);
}
实现效果:在窗口中绘制出一个以 (100, 100)
为中心,横纵半径均为 50 的圆形。
绘制文字
QPainter
类中不仅提供了绘制图形的功能,还可以用 QPainter::drawText()
函数来绘制文字,也可以用 QPainter::setFont()
设置字体等信息。示例:
#include "widget.h"
#include <QPainter>
#include <QFont>Widget::Widget(QWidget *parent): QWidget(parent)
{resize(400, 300);
}void Widget::paintEvent(QPaintEvent *event)
{QPainter painter(this); // 实例化画家// 设置字体QFont font;font.setFamily("华文行楷");font.setPointSize(20);painter.setFont(font);// 设置文字颜色painter.setPen(Qt::red);// 绘制文字painter.drawText(QRect(100, 200, 400, 150), "天行健,君子以自强不息");
}
实现效果:在窗口中用红色、华文行楷字体(字号 20)绘制出 “天行健,君子以自强不息” 文字。
注意:
有时候在使用 painter.drawText(0, 100, "hello")
绘制文字时,要留意坐标的含义:
- 横坐标
0
,代表文字最左侧的位置,文字会从这个水平位置开始绘制。 - 纵坐标
100
,表示的是文字的 ** 基线(baseline)** 位置,基线是文字排版中用于对齐的一条假想线,像字母 “h”“l” 等有升部的字符,会在基线之上延伸,而 “g”“p” 等有降部的字符,会在基线之下延伸。
设置画笔
QPainter
在绘制时,要有一个形状、线条粗细的 “画笔”。在绘图时,QPen
可定义 “画笔”。在 Qt 中,QPen
类定义了 QPainter
绘制的 “笔” 的形状、线条粗细等。在使用时也可以自定义 “画笔”。QPen
的 “画笔” 的风格、颜色以及宽度等属性进行设置,“设置画笔” 需要通过 setPen()
方法进行设置,“画笔” 的颜色是 QColor
类实现方法进行设置,“设置画笔” 的宽度通过 setWidth()
方法进行设置。
- 设置画笔颜色:
QPen::setBrush(const QColor &color)
,画笔的颜色主要通过QColor
类设置; - 设置画笔宽度:
void QPen::setWidth(int width)
; - 设置画笔风格:
void QPen::setStyle(Qt::PenStyle style)
。
画笔的风格有:
风格 | 说明 |
---|---|
Qt::NoPen | 没有线。例如,QPainter::drawRect() 填充但不绘制任何线条。 |
Qt::SolidLine | 一条简单的线。 |
Qt::DashLine | 由一些像素分隔的短线。 |
Qt::DotLine | 由一些像素分隔的点。 |
Qt::DashDotLine | 交替的点和短线。 |
Qt::DashDotDotLine | 一个点,一个短线,一个点。 |
Qt::CustomDashLine | 使用 QPainterPath 定义的自定义模式。 |
Qt::BDiagPattern | 从右上到左下的对角线。 |
Qt::FDiagPattern | 从左上到右下的对角线。 |
Qt::CrossPattern | 交叉对角线。 |
Qt::DiagCrossPattern | 双交叉对角线。 |
Qt::LinearGradientPattern | 线性渐变填充。 |
Qt::ConicalGradientPattern | 锥形渐变填充。 |
Qt::RadialGradientPattern | 径向渐变填充。 |
Qt::TexturePattern | 纹理填充。 |
示例:画笔的使用
#include "widget.h"
#include <QPainter>
#include <QPen>
#include <QColor>Widget::Widget(QWidget *parent): QWidget(parent)
{resize(400, 300);
}void Widget::paintEvent(QPaintEvent *event)
{// 实例化画家对象 this:表示的是在当前窗口中绘图,即绘图设备QPainter painter(this);// 设置画笔QPen pen(QColor(255, 0, 0));// 设置画笔宽度pen.setWidth(5);// 设置画笔风格pen.setStyle(Qt::DashLine);// 让画家使用画笔painter.setPen(pen);// 绘制圆painter.drawEllipse(QPoint(100, 100), 50, 50);
}
实现效果:在窗口中绘制出一个红色、虚线(线宽 5)的圆形。
设置画刷
画刷,顾名思义,是用 QBrush
类来描述,画刷大多用于填充。QBrush
定义了 QPainter
的填充模式,具有样式、颜色、渐变以及纹理等属性。QBrush
的 style
枚举中,默认值是 Qt::NoBrush
,也就是不进行任何填充。可以通过 Qt
的样式,使用 QBrush
。
设置画刷主要通过 void QPainter::setBrush(const QBrush &brush)
方法,其参数为画刷格式。
示例:
#include "widget.h"
#include <QPainter>
#include <QBrush>
#include <QColor>Widget::Widget(QWidget *parent): QWidget(parent)
{resize(400, 300);
}void Widget::paintEvent(QPaintEvent *event)
{// 实例化画家对象 this:表示的是在当前窗口中绘图,即绘图设备QPainter painter(this);// 设置画刷QBrush brush(QColor(0, 255, 255));// 设置画刷风格brush.setStyle(Qt::Dense4Pattern);// 让画家使用画刷painter.setBrush(brush);// 设置画笔QPen pen(QColor(255, 0, 0));pen.setWidth(5);painter.setPen(pen);// 绘制椭圆painter.drawEllipse(QPoint(100, 100), 50, 50);// 绘制矩形painter.drawRect(200, 20, 100, 80);
}
绘制图片
Qt 提供了四个类来处理图像:QImage
、QPixmap
、QBitmap
和 QPicture
,它们都是常用的绘图设备。其中 QImage
主要用来进行 I/O 处理,它对 I/O 处理操作进行了优化,而且可以用来直接访问和操作像素;QPixmap
主要用来在屏幕上显示图像,它对在屏幕上显示图像进行了优化;QBitmap
是 QPixmap
的子类,用来处理颜色深度为 1 的图像,即只能显示黑白两种颜色;QPicture
用来记录并重放 QPainter
命令。这一节只讲解 QPixmap
。
绘制简单图片
新建 Qt 项目:基类选择 QWidget
,项目名称为 QPainter
。在 “widget.h” 头文件中声明绘图事件,如下图示:
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();// 声明绘画事件void paintEvent(QPaintEvent *event);
};#endif // WIDGET_H
添加资源文件:首先准备一些图片资源文件,并将这些图片资源文件放在同一个文件夹中,将该文件夹复制到项目中。
选中项目文件,鼠标右键 -----> add new...
点击 “add new...” 之后,出现如下界面:在弹出的窗口中,选择 “Qt” 分类下的 “Qt Resource File”,点击 “Choose...”。
选择 “Choose...” 之后,给资源文件重命名:输入资源文件名称,点击 “下一步”。
点击 “下一步”,出现如下界面,点击 “完成”。
给资源文件添加前缀,并将资源文件添加至项目中。
将所有的资源文件添加到项目中,方便后续使用。
点击 “构建并运行” 按钮,将资源文件添加到项目中。
在 “widget.cpp” 文件中实现图片功能:
void Widget::paintEvent(QPaintEvent *event)
{// 实例化画家对象QPainter painter(this);// 画图片painter.drawPixmap(0, 0, QPixmap(":/mei.webp"));
}
实现效果:在窗口中显示指定的图片。
平移图片
平移图片通过改变坐标来实现,QPainter
类中提供了 translate()
函数来实现坐标原点的改变。示例:
void Widget::paintEvent(QPaintEvent *event)
{// 实例化画家对象QPainter painter(this);// 平移painter.translate(100, 100);// 画图片painter.drawPixmap(0, 0, QPixmap(":mei.jpg"));
}
实现效果:图片相对于原来的位置发生了平移。
缩放图片
在 Qt 中,图片的放大和缩小可以使用 QPainter
类中的 drawPixmap
函数来实现。示例:
void Widget::paintEvent(QPaintEvent *event)
{(void)event;QPainter painter(this);QPixmap pixmap(":/mei.jpg");painter.drawPixmap(0, 0, pixmap);painter.drawPixmap(100, 100, 500, 300, pixmap);
}
实现效果:窗口中显示了原图和缩放后的图片。
旋转图片
图片的旋转使用 QPainter
类中的 rotate
函数,它默认以原点为中心进行旋转。如果要改变旋转的中心,可以用 QPainter
的 translate
函数。示例:
void Widget::paintEvent(QPaintEvent *event)
{// 实例化画家对象QPainter painter(this);// 让旋转的中心为(100,100)painter.translate(100, 100);painter.rotate(90); // 顺时针旋转90度painter.translate(-100, -100); // 恢复坐标// 画.drawPixmap(图片QPixmap pixmap(":/mei.jpg");painter.drawPixmap(0, 0, pixmap);
}
实现效果:图片发生了旋转。
其他设置
移动画家位置
有时在绘制多个图形时,若使用同一坐标位置,绘制出的图形可能会重合。此时,可通过移动画家位置来避免图形重合。
示例 1:未移动画家位置
#include "widget.h"
#include <QPainter>Widget::Widget(QWidget *parent): QWidget(parent)
{
}void Widget::paintEvent(QPaintEvent *event)
{QPainter painter(this); // 实例化画家对象// 画圆painter.drawEllipse(QPoint(100, 200), 50, 50); // 画圆,使用同一坐标位置,两个圆会重合,保证就只有一个圆painter.drawEllipse(QPoint(100, 200), 50, 50);
}
实现效果:窗口中只有一个圆。
示例 2:移动画家位置使用 translate
移动画家所在位置。
#include "widget.h"
#include <QPainter>Widget::Widget(QWidget *parent): QWidget(parent)
{
}void Widget::paintEvent(QPaintEvent *event)
{QPainter painter(this); // 实例化画家对象// 画圆painter.drawEllipse(QPoint(100, 200), 50, 50); // 移动画家painter.translate(200, 0);// 画圆,此时有两个圆painter.drawEllipse(QPoint(100, 200), 50, 50);
}
实现效果:窗口中有两个圆。
保存 / 加载画家的状态
在绘制的过程中,可以通过 save()
函数来保存画家的状态,使用 restore()
函数还原画家状态。
save()
函数原型如下:
void QPainter::save();
restore()
函数原型如下:
void QPainter::restore();
示例:
void Widget::paintEvent(QPaintEvent *event)
{QPainter painter(this); // 实例化画家对象// 画圆painter.drawEllipse(QPoint(100, 200), 50, 50); // 往画家移动painter.translate(200, 0);painter.save(); // 保存画家状态// 画圆painter.drawEllipse(QPoint(100, 200), 50, 50); painter.translate(200, 0); // 往画家移动painter.restore(); // 恢复画家状态// 画圆,第二个和第三个重合为一个painter.drawEllipse(QPoint(100, 200), 50, 50);
}
实现效果:第二个和第三个圆重合为一个。
说明:上述示例中,在画第三个圆之前,由于还原了画家的状态,所以此时画家的位置坐标会移动到画家状态保存的地方,所以在绘制第三个圆的位置时其实是和第二个圆发生了重叠。
特殊的绘图设备
前面的代码中我们是使用 QWidget
作为绘图设备,在 Qt 中还存在下列三个比较特殊的绘图设备,此处我们只做个简单的介绍。
QPixmap
用于在显示器上显示图片。QImage
用于对图片进行像素级操作。QPicture
用于对QPainter
的一系列操作进行存档。
QPixmap
QPixmap
特点:
- 使用
QPainter
直接在上面进行绘制图形。 - 通过文件路径加载并显示图片。
- 插上
QPainter
的drawPixmap()
函数,可以把这个图片绘制到一个QLabel
、QPushButton
等控件上。 - 和系统 / 显示设备相关,不同系统 / 显示设备下,
QPixmap
的显示可能会有所差别。
示例:
#include "widget.h"
#include <QPainter>
#include <QPixmap>Widget::Widget(QWidget *parent): QWidget(parent)
{this->resize(600, 500);
}void Widget::paintEvent(QPaintEvent *event)
{// /xp/pic.png(给图片各段仅尺寸为 50*50)QPixmap pixmap(":/xp/pic.png");QPainter painter(this); // 实例化画家对象painter.drawEllipse(QPoint(100, 100), 50, 50); // 设置圆心和半径// 保存绘制的图片pixmap.save("C:\\Users\\lenovo\\Desktop\\Test\\pic.png");
}
QImage
QImage
特点:
- 使用
QPainter
直接在上面进行绘制图形。 - 能对文件路径加载并显示图片。
- 能通过文件对图片进行像素级操作(修改某一个指定的像素)。
- 独立于硬件的系统,能够在不同系统之上提供一致的显示。
代码示例:QImage
绘图时对像素的修改
头文件中声明事件:
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();// 声明绘画事件void paintEvent(QPaintEvent *event);
};#endif // WIDGET_H
源文件中实现事件,使用 QImage
对图片的像素进行修改:
#include "widget.h"
#include <QPainter>
#include <QImage>Widget::Widget(QWidget *parent): QWidget(parent)
{
}Widget::~Widget()
{
}void Widget::paintEvent(QPaintEvent *event)
{QPainter painter(this); // 实例化画家对象QImage img(":/picture/3.jpg"); // 加载图片// 修改像素点for(int i = 100; i < 200; i++){for(int j = 100; j < 200; j++){img.setPixelColor(i, j, Qt::blue);}}painter.drawImage(0, 0, img);
}
效果如下:
- 没有修改像素之前:显示原始图片。
- 修改像素之后:图片对应区域像素变为蓝色。
QPicture
QPicture
特性:
- 使用
QPainter
直接在上面进行绘制图形。 - 能通过文件路径加载并显示图片。
- 独立于硬件的系统,能够在不同系统之上提供一致的显示。
QPicture
加载的必须是自身的存文件,而不能是任意的jpg
、png
等图片文件。QPicture
类似于很多游戏的Replay
功能。
示例:
#include "widget.h"
#include <QPainter>
#include <QPicture>Widget::Widget(QWidget *parent): QWidget(parent)
{
}void Widget::paintEvent(QPaintEvent *event)
{QPicture pic;QPainter painter;pic.open("C:\\Users\\lenovo\\Desktop\\Test\\pic.pic"); // 设置要保存的路径painter.begin(&pic); // 把图画到 pic 上painter.setPen(Qt::red); // 设置画笔颜色painter.drawEllipse(QPoint(100, 100), 50, 50); // 画圆painter.end();QPainter painter2(this); // 实例化画家对象painter2.drawPicture(0, 0, pic); // 绘制图片
}
Qt 对于界面的美化,还涉及到很多其他的话题,大家未来在工作中如果涉及到了,再针对性学习即可。
- Qt 动画
- Qt 3D
- Qt Quick 三控件
- Qt 串口
- Qt 数据库……