目录
1、绘图核心类
2、QPainter类的使用
2.1 绘制线段
2.2 绘制矩形
2.3 绘制圆形
2.4 绘制文本
3、QPen类的使用
3.1 使用画笔
4、QBrush类的使用
4.1 使用画刷
5、绘制图片
5.1 测试QPixmap
5.1.1 图片移动
5.1.2 图标缩小
5.1.3 旋转图片
5.1.4 将QPixmap作为绘图设备
5.2 测试QImage
5.3 测试QPicture
结语
前言:
Qt提供了DIY度极强的绘图功能,目的就是解决在特殊场景时,使用系统提供的控件无法达到预期的效果。Qt的绘图功能提供了相关的方法,允许开发者在界面中绘制任意的图形形状,以完成更复杂的界面设计。
1、绘图核心类
要想使用绘图功能,则必须用Qt提供的与绘图相关的类来实例化出对象,常用的核心类介绍如下(通过以下类的直译也可以看出其作用):
QPainter(画家)
| 进行绘图操作的最基本的类,该类提供了⼀系列drawXXX方法,允许在界面中绘制各种图形 |
QPen(画笔)
| 描述用QPainter画出来的线的样式 |
QBrush(画刷)
| 用来填充用QPainter画出来区域 |
QPaintDevice(画板)
| 描述用QPainter画出来图形在哪个对象上,比如画在QWidget上,则QWidget就是画板,而且QWidget本身就继承自QPaintDevice,所以他本就可以作为被画对象 |
值得注意的是:绘图操作不能直接在界面的构造函数处执行,比如不能在QWidget构造函数中进行绘图,原因很简单,可以理解为绘图的前提是必须得有画板,而执行到QWidget构造函数时,还没有生成完整的画板,因此哪怕此时绘图了也不能在最终的界面上显示出来,因为“画快了”。
所以把绘图操作放到Qt提供的paintEvent事件中(即重写该事件,实现绘图功能),而paintEvent事件会在以下情况自动触发并执行对应的动作:
1、控件首次创建的时候。
2、控件被遮挡时或者解除遮挡时,即控件失去焦点被遮挡的瞬间以及获取焦点解除遮挡的瞬间。
3、将窗口最小化的时候。(然后恢复窗口,窗口的内容也会恢复至原来样式(注:窗口实际上就是控件))
4、将控件进行放大缩小的时候。
5、在代码中主动调用repaint()或者update()函数。(这两个函数都是QWidget提供的)
因此当paintEvent事件触发时就会重新进行绘图,这也是为什么在计算机屏幕中可以不断的来回切换各种图形界面,给人的感觉是计算机如同一本书一样可以来回翻页,实际上是计算机强大的计算能力在不断的重新绘图。
2、QPainter类的使用
介绍QPainter类中的系列函数drawxxx的使用,即进行一些基本的绘图。
2.1 绘制线段
使用QPainter提供的函数drawLine进行绘制线段,该函数介绍如下:
void drawLine(const QPoint &p1, const QPoint &p2);
//p1:绘制起点坐标
//p2:绘制终点坐标
在widget.h中声明paintEvent事件,代码如下:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void paintEvent(QPaintEvent *event);//重写paintEvent事件
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
在widget.cpp中重写paintEvent事件,代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *event)
{
//this表示在当前窗口中绘画
QPainter painter(this);
//在20,20坐标到200,20坐标画一条线
painter.drawLine(QPoint(20,20),QPoint(200,20));
}
运行结果如下:
2.2 绘制矩形
使用QPainter提供的函数drawRect进行绘制线段,该函数介绍如下:
void QPainter::drawRect(int x, int y, int width, int height);
//x:矩形左上角横坐标;
//y:矩形左上角纵坐标;
//width:矩形的宽度;
//height:矩形的⾼度
widget.h代码依旧是声明paintEvent事件,因此不再展示widget.h代码,直接展示widget.cpp代码:
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *event)
{
//this表示在当前窗口中绘画
QPainter painter(this);
//在坐标(20,20)处绘制一个长200宽200的矩形
painter.drawRect(20,20,200,200);
}
运行结果:
2.3 绘制圆形
使用QPainter提供的函数drawEllipse进行绘制线段,该函数介绍如下:
void QPainter::drawEllipse(const QPoint ¢er, int rx, int ry)
//center:中⼼点坐标
//rx:圆的宽度
//ry:圆的高度
widget.cpp代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *event)
{
//this表示在当前窗口中绘画
QPainter painter(this);
//将绘画颜色改成红色
painter.setPen(Qt::red);
//在坐标(100,100)处换一个宽50高50的红色圆形
painter.drawEllipse(QPoint(100,100),50,50);
}
运行结果:
2.4 绘制文本
QPainter不仅可以绘图,还可以绘制文本,这样在界面上写字时就不再需要QLabel作为文本的载体了。使用drawText()函数来绘制文章,还可以配合setFont()函数来设置字体样式,让字体看起来更加饱满。drawText函数介绍如下:
void drawText(const QPoint &position, const QString &text)
//position表示文本的坐标
//text表示文本内容
值得注意的是,这里文本坐标是指文本最左侧的基线出的位置,如下图:
测试代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *event)
{
//this表示在当前窗口中绘画
QPainter painter(this);
//设置字体样式
painter.setFont(QFont("华文行楷",30));
//设置画笔
painter.setPen(Qt::green);
painter.drawText(QPoint(150,200),"雨打蕉叶又潇潇了几夜");
}
运行结果:
3、QPen类的使用
使用QPainter绘图时,默认有一个画笔(没有画笔则无法绘图,只不过默认画笔就是最普通的文本样式),当然也可以自定义画笔。在Qt中,QPen类表示画笔,该类定义了QPainter绘图的颜色,线条宽度、线条样式。同时通过QPen类提供的函数来设置上述属性,常用函数如下:
QPen(const QColor &color) | 通过QPen构造函数来设置画笔颜色 |
void setWidth(int width) | 设置画笔画出线条的宽度 |
void setStyle(Qt::PenStyle style) | 设置画笔画出线条的样式 |
其中Qt::PenStyle提供了以下的风格:
将上述的枚举常量传给setStyle,则画笔就可以画出不同的样式。
3.1 使用画笔
对上述画矩形的代码进行画笔修饰,代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *event)
{
//this表示在当前窗口中绘画
QPainter painter(this);
//自定义画笔
QPen pen(QColor(0,255,0));
//设置画笔宽度
pen.setWidth(30);
//设置画笔样式
pen.setStyle(Qt::DotLine);
//将画笔设置到画家中
painter.setPen(pen);
//在坐标(20,20)处绘制一个长200宽200的矩形
painter.drawRect(20,20,200,200);
}
运行结果:
4、QBrush类的使用
QBrush类表示画刷,作用是填充图形,可以通过QBrush提供的函数,对填充样式进行更改,比如颜色、风格等。常用函数介绍如下:
QPen(const QColor &color) | 设置画刷的颜色 |
void setStyle(Qt::BrushStyle style) | 设置画刷的风格 |
其中Qt::BrushStyle是一个枚举类型,他提供了多个枚举常量供开发者选择画刷的风格,如下图:
4.1 使用画刷
对上述画笔代码进行添加画刷,代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *event)
{
//this表示在当前窗口中绘画
QPainter painter(this);
//自定义画笔
QPen pen(QColor(0,255,0));
//设置画笔宽度
pen.setWidth(30);
//设置画笔样式
pen.setStyle(Qt::DotLine);
//将画笔设置到画家中
painter.setPen(pen);
//自定义画刷
QBrush brush(Qt::cyan);
//设置画刷的风格
brush.setStyle(Qt::Dense1Pattern);
//将画刷设置到画家中
painter.setBrush(brush);
//在坐标(20,20)处绘制一个长200宽200的矩形
painter.drawRect(20,20,200,200);
}
运行结果:
5、绘制图片
上文的例子说明了如何在界面中手动绘图,然而Qt还提供了对现有图片进行处理的类,分别是QImage、QPixmap、QBitmap和QPicture,这些都是Qt封装好的绘图设备。其中,QImage可以对图片进行像素级别的操作,QPixmap可以将图片显示到屏幕上,QBitmap是QPixmap的子类,只能显示黑白两种颜色,QPicture用来记录并重演QPainter命令。
5.1 测试QPixmap
QPainter提供了drawPixmap函数,该函数可以将QPixmap中的资源显示在界面上,该函数介绍如下:
void drawPixmap(int x, int y, const QPixmap &pm)
//x表示图片左上角的横坐标
//y表示图片左上角的纵坐标
//pm表示图片资源
首先需要创建一个虚拟目录(虚拟目录详细见:Qt_控件的QWidget属性介绍-第五点),如下图:
代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
//创建QPixmap对象
QPixmap map(":/kklt.png");
//将图片设置在坐标20,20处
painter.drawPixmap(20,20,map);
}
运行结果:
5.1.1 图片移动
将上述图片进行移动操作,移动的本质是将QPainter进行移动,而不是直接对QPixmap进行移动,代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
//创建QPixmap对象
QPixmap map(":/kklt.png");
//移动画家
painter.translate(100,100);
//将图片设置在坐标20,20处
painter.drawPixmap(20,20,map);
}
运行结果:
5.1.2 图标缩小
将上述图片进行缩小,函数仍然使用drawPixmap,该函数也拥有缩小的功能,代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
//创建QPixmap对象
QPixmap map(":/kklt.png");
// //移动画家
// painter.translate(100,100);
//将图片设置在坐标20,20处,尺寸不变
painter.drawPixmap(20,20,map);
//将图片设置在坐标500,20处,尺寸为50*50像素
painter.drawPixmap(650,20,50,50,map);
}
运行结果:
5.1.3 旋转图片
旋转图片的逻辑稍微复杂点,使用QPainter类中的rotate()函数进行旋转,并且旋转的不是图片,而是QPainter本身,所以会导致一种情况,即旋转之后的QPainter不在当前窗口中,然后我们在该QPainter上显示的图片也不会在当前窗口中显示,如下图:
从这里也可以得到一个结论:QPainter是一种类似于”附在“窗口界面上的媒介,我们往窗口中写入的资源实际上是写到该QPainter中,然后QPainter再写入至窗口中,即QPainter和窗口界面是相互独立的,且QPainter可以移动,但是当QPainter超出了窗口,则QPainter就不能将超出部分的资源写入窗口中了。因此要将旋转之后的QPainter再进行移动,将其移动回窗口界面中,注意旋转过后的原点位置以及坐标系都变了。
测试代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
//创建QPixmap对象
QPixmap map(":/kklt.png");
//旋转QPainter,顺时针旋转90°
painter.rotate(90);
//将QPainter移回窗口界面中
painter.translate(0,-500);
//将图片设置在坐标20,20处,尺寸不变
painter.drawPixmap(20,20,50,50,map);
}
运行结果:
5.1.4 将QPixmap作为绘图设备
上述中的QPixmap是作为图片载体,然而QPixmap还可以作为绘图的对象,即将QPixmap作为画板。测试代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
#include <QPixmap>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//将QPixmap作为画板,尺寸为500*500px
QPixmap pixmap(300,300);
//将pixmap的背景色调成白色
pixmap.fill(Qt::white);
//让画家在QPixmap上绘画
QPainter painter(&pixmap);
//在QPixmap上画一个圆
painter.drawEllipse(QPoint(100,100),50,50);
//为了观察是否画在QPixmap上,将QPixmap上的内容保存到文件中
pixmap.save("D:\\Qt_code\\QPixmap\\pix.png");
}
Widget::~Widget()
{
delete ui;
}
运行结果:
5.2 测试QImage
QImage也可以像QPixmap一样作为画板,并且也可以保存到磁盘的文件夹中,此功能就不再测试了。主要测试QImage更改像素的功能,首先重写paintEvent事件,通过函数setPixel来设置某个像素的颜色值,使用qRgb表示⼀个具体的颜色值,测试代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QImage image;
image.load(":\kklt.jpg");
//修改像素点
for(int i = 0;i<200;i++)
for(int j = 0;j<200;j++)
{
QRgb rgb = qRgb(0,255,255);
image.setPixel(i,j,rgb);
}
painter.drawImage(20,20,image);
}
运行结果:
5.3 测试QPicture
QPicture最重要的功能是能够记录QPainter的操作步骤,他跟上述几个类不一样,上述的类是作为QPainter的画板,QPainter将最终的成品绘制在面板中呈现给用户看。而QPicture是记录QPainter绘制的操作,然后将该操作进行复盘,再将复盘的结果呈现给用户看,他们本质上大同小异。
首先需要将QPicture实例作为参数传递给QPainter::begin()函数,以便告诉系统开始记录,记录完毕后使用QPainter::end()命令终止。测试代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
#include <QPicture>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPicture picture;
QPainter painter;
painter.begin(&picture);
painter.drawEllipse(20,20,50,50);//画一个圆
painter.end();
//此时picture中存放的就是QPainter的操作步骤,将picture保存到磁盘上
picture.save("D:\\Qt_code\\QPicture\\pic.pic");
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *event)
{
QPainter painter1(this);
//从磁盘上加载到picture1中
QPicture picture1;
picture1.load("D:\\Qt_code\\QPicture\\pic.pic");
//将picture1的内容用painter1画出来,此时picture1里面存放的并不是图形
painter1.drawPicture(30,30,picture1);
}
测试结果:
结语
以上就是关于Qt绘图的讲解,Qt内部提供了许多功能齐全的部分供我们使用,因此在大部分的场景下很少用到绘图功能,但是不排除一些特别场景,并且这些内置控件的底层也是由绘图一步步画出来的,因此了解绘图也可以更好的了解这些控件的原理。其次就是绘图可以让开发者进行界面开发时拥有很高的自由度,设计一些样式时不再依赖控件作为载体。
最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!