以下学习可能要花费一个小时左右。如果遇到疑问在评论区留言,我会看到的!加油!
- 首先创建一个不带ui,且类名修改为 MainWidget(自定),基类为QWidget的项目。

- 在
mainwidget.h
中重写QWidget::paintEvent(QPaintEvent *event)
函数
void paintEvent(QPaintEvent *event) override;
- 在maiwidget.cpp中写源码
void MainWidget::paintEvent(QPaintEvent *event)
{
/**
为所欲为绘制
*/
}
看到这里肯定有很多小伙伴在疑虑,
为什么要不带ui?
为什么要用QWidget而不用默认的QMainWindow?
为什么要重写paintEvent,不能在构造函数直接用吗?
1、不带ui是我的习惯,在构造函数里设置一些简单的窗口及自定义控件方便。
2、我们重写的paintEvent函数,是在QWidget中的;而QMainWindow也是继承自QWidget,其本身没有该函数,所以我们不用QMainWindow。
3、这个问题我们需要了解QPainter的机制,请往下看。
绘制系统主要依赖于三个类:QPainter、QPainterEngine、QPainterDevice。QPainter可以理解为画笔,我们主要也是用这个类的函数接口。QPainterDevice就是绘画设备,就是我们所见的屏幕。QPainterEngine则是连接画笔和屏幕的引擎,将QPainter翻译到屏幕上;一般我们用不到这个类,因为我们在使用QPainter时,它会由QPainter自动调用。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
下面开始正式学习!!!
一、前期准备
一些可能用到的函数
setPalette(QPalette(Qt::white)); //设置界面背景颜色,白色,自定
setAutoFillBackground(true); //设置自动填充背景色
二、QPainter的绘制接口
1、绘制点
函数原型:
inline void QPainter::drawPoint(const QPointF &p)
{
drawPoints(&p, 1);
}
inline void QPainter::drawPoint(int x, int y)
{
QPoint p(x, y);
drawPoints(&p, 1);
}
inline void QPainter::drawPoint(const QPoint &p)
{
drawPoints(&p, 1);
}
就是一个点坐标,不做解释
QPainter paint(this);
paint.drawPoint(100,100);
如图,,这个点很小,在运行中默认只有一个像素大小。

2、绘制直线
函数原型:
inline void QPainter::drawLine(const QLineF &l)
{
drawLines(&l, 1);
}
inline void QPainter::drawLine(const QLine &line)
{
drawLines(&line, 1);
}
inline void QPainter::drawLine(int x1, int y1, int x2, int y2)
{
QLine l(x1, y1, x2, y2);
drawLines(&l, 1);
}
inline void QPainter::drawLine(const QPoint &p1, const QPoint &p2)
{
QLine l(p1, p2);
drawLines(&l, 1);
}
inline void QPainter::drawLine(const QPointF &p1, const QPointF &p2)
{
drawLine(QLineF(p1, p2));
}
可见,直线原理就是告诉接口两个点坐标,再将两个点相连。
如图,语句
QPainter paint(this);
paint.drawLine(50,50,250,250);
画出的图

3、绘制矩形
函数原型:
inline void QPainter::drawRect(const QRectF &rect)
{
drawRects(&rect, 1);
}
inline void QPainter::drawRect(int x, int y, int w, int h)
{
QRect r(x, y, w, h);
drawRects(&r, 1);
}
inline void QPainter::drawRect(const QRect &r)
{
drawRects(&r, 1);
}
可见,矩形的画法是线申明一个点坐标作为左上落脚点,再定义其长度和高度
如图,使用函数
QPainter paint(this);
paint.drawRect(50,50,200,200);

4、绘制圆角矩阵
原型:
void drawRoundRect(const QRectF &r, int xround = 25, int yround = 25);
QT_DEPRECATED_X("Use drawRoundedRect(..., Qt::RelativeSize) instead")
void drawRoundRect(int x, int y, int w, int h, int = 25, int = 25);
QT_DEPRECATED_X("Use drawRoundedRect(..., Qt::RelativeSize) instead")
void drawRoundRect(const QRect &r, int xround = 25, int yround = 25);
可见,坐标,长,高和矩形是一样的,只是后面多了两个参数,第一个参数是x轴上圆润长度,第二个参数是y轴上圆润长度。
如图,为了看的清楚,添加一个矩形,
paint.drawRoundRect(50,50,200,150,50,10);
paint.drawRect(50,50,200,150);

5、绘制椭圆
函数原型:
inline void QPainter::drawEllipse(int x, int y, int w, int h)
{
drawEllipse(QRect(x, y, w, h));
}
inline void QPainter::drawEllipse(const QPointF ¢er, qreal rx, qreal ry)
{
drawEllipse(QRectF(center.x() - rx, center.y() - ry, 2 * rx, 2 * ry));
}
inline void QPainter::drawEllipse(const QPoint ¢er, int rx, int ry)
{
drawEllipse(QRect(center.x() - rx, center.y() - ry, 2 * rx, 2 * ry));
}
可见,绘制椭圆也是固定一个点,再申明长和宽
如图,使用函数paint.drawEllipse(100,100,200,100);

如果不理解是如何形成的,那么我们添加一个矩形来看看,矩形的还不了解就返回上一层看看。
paint.drawRect(100,100,200,100);
paint.drawEllipse(100,100,200,100);

6、绘制圆弧
原型:
inline void QPainter::drawArc(const QRect &r, int a, int alen)
{
drawArc(QRectF(r), a, alen);
}
inline void QPainter::drawArc(int x, int y, int w, int h, int a, int alen)
{
drawArc(QRectF(x, y, w, h), a, alen);
}
可见,圆弧是先确定一个矩形,再根据两个角度来确定。这两个角度一个是开始角度,一个是绘制角度。
开始角度是说从一个固定角度开始。
绘制角度是说从固定角度开始,你要绘制多少度的弧。
有两点要说的是,角度是被划分成16部分的,所以输入角度时要✖️16;0角度是沿x轴方向的,度数沿逆时针增长。具体见代码:
paint.drawArc(10,10,200,200,0*16,90*16); //0度开始,弧长90度

paint.drawArc(10,10,200,200,30*16,150*16); //30度开始,弧长150度

7、绘制扇形
原理和画弧一样,不解释
代码:paint.drawPie(10,10,200,200,30*16,150*16);

8、绘制多边形
原型:
void drawPolygon(const QPointF *points, int pointCount, Qt::FillRule fillRule = Qt::OddEvenFill);
inline void drawPolygon(const QPolygonF &polygon, Qt::FillRule fillRule = Qt::OddEvenFill);
void drawPolygon(const QPoint *points, int pointCount, Qt::FillRule fillRule = Qt::OddEvenFill);
inline void drawPolygon(const QPolygon &polygon, Qt::FillRule fillRule = Qt::OddEvenFill);
可见,绘制多边形就是将点坐标作为数组形式,把首地址传给绘制接口,再申明点的个数,最后将两点之间用线段连接。
比如画一个三角形,设定三个点坐标为(100,100)、(100,400)、(500,400),这就是一个直角三角形。再将点和个数传给drawPolygon()
函数接口。
QPoint point[3] = {QPoint(100,100), QPoint(100,400), QPoint(500,400)};
paint.drawPolygon(point,3);
如图:

9、绘制文本
原型:
void drawText(const QPointF &p, const QString &s);
inline void drawText(const QPoint &p, const QString &s);
inline void drawText(int x, int y, const QString &s);
void drawText(const QPointF &p, const QString &str, int tf, int justificationPadding);
void drawText(const QRectF &r, int flags, const QString &text, QRectF *br = nullptr);
void drawText(const QRect &r, int flags, const QString &text, QRect *br = nullptr);
inline void drawText(int x, int y, int w, int h, int flags, const QString &text, QRect *br = nullptr);
void drawText(const QRectF &r, const QString &text, const QTextOption &o = QTextOption());
可见,可以用点、矩形来确定文本位置。有一点要说的是,既然是文本,那么肯定可以使用QFont控制字体的大小、模式等,最后再绘制前paint.setFont(font);
完成。
演示代码如下,为了直观,添加了矩形:
QFont font;
font.setPointSize(50);
font.setFamily("Microsoft YaHei");
font.setItalic(true);
paint.setFont(font);
QRect rect(100,100,200,100);
paint.drawRect(rect);
paint.drawText(rect,"Hello");
如图:

10、绘制图片
原型,有点多,只罗列了常用的几个:
inline void QPainter::drawPixmap(const QPoint &p, const QPixmap &pm)
{
drawPixmap(QPointF(p), pm);
}
inline void QPainter::drawPixmap(const QRect &r, const QPixmap &pm)
{
drawPixmap(QRectF(r), pm, QRectF());
}
inline void QPainter::drawPixmap(int x, int y, const QPixmap &pm)
{
drawPixmap(QPointF(x, y), pm);
}
inline void QPainter::drawPixmap(int x, int y, int w, int h, const QPixmap &pm)
{
drawPixmap(QRectF(x, y, w, h), pm, QRectF());
}
可见,图片绘制也是由点、矩形来确定位置的,最后加上图片的路径即可(用QPixmap)。
代码如下,加入矩形:
paint.drawRect(10,10,380,280);
paint.drawPixmap(10,10,380,280,QPixmap("/Users/yucheng/allpic/back.jpeg"));
如图:

11、二进制绘制图片
用法和QPixmap相同,将类变为QBitmap即可。具体看代码:
QPainter paint(this);
QPixmap pix("/Users/yucheng/allpic/qq.png");
QBitmap bit("/Users/yucheng/allpic/qq.png");
paint.drawPixmap(10,10,pix);
paint.drawPixmap(110,10,bit);

三、画笔与画刷
1、简介
首先要知道,画笔类QPen
常用来画线,画刷类QBrush
常用来填充图形内部。
2、画笔
画笔用来画图形,众所周知,笔有很多种,包括影响线条的粗细、颜色,以及个人影响的线条的款式,如实线、虚线、点线。当然,线条粗了以后,始末端的样式也是不同的,包括平整、圆形。
接下来一个个看!
(1)、线条粗细、颜色
QPainter可以使用QPen来定义笔,QPen中使用.setWidth(int)
来设置笔的粗细,使用.setBush(Qt::black);
来设置笔的颜色。
.setWidth(int)
后的参数是像素大小,最小为1,即使输入0,也是画出一像素大小。
.setBush(Qt::black);
后颜色是Qt自带的颜色输入Qt::
后会显示各类接口,选择想要的颜色即可。
代码,为了颜色及款式对比鲜明,往后我都将背景填充为白色:
QPainter paint(this);
QPen pen;
pen.setWidth(10);
pen.setBrush(Qt::blue);
paint.setPen(pen);
paint.drawLine(50,100,50,200);
paint.drawRect(100,100,200,100);
效果:

(2)、线条款式
线条默认都是直线的,当然也有其他了。如图:

默认情况是直线,使用pen.setStyle(Qt::SolidLine);
切换喜欢的样式。
(3)、线条末端样式和线条连接方式
线条末端一般分为三种情形:

默认是第一种情况,见上图(1),使用pen.setCapStyle(Qt::SquareCap);
切换模式。
线条接连也分为三种方式:

默认是第一种方式,见上图(1),使用pen.setJoinStyle(Qt::BevelJoin);
切换形式。
(4)、前三种结合举例
代码:
void MainWidget::paintEvent(QPaintEvent *event)
{
QPainter paint(this);
QPen pen;
pen.setWidth(10);
pen.setBrush(Qt::blue);
pen.setStyle(Qt::DashDotLine); //设置线条为破折号+远点形式(2)
pen.setCapStyle(Qt::RoundCap); //设置笔帽末端为圆形(1)
pen.setJoinStyle(Qt::RoundJoin); //设置线条连接方式为圆形(3)
paint.setPen(pen);
paint.drawLine(50,100,50,200);
paint.drawRect(100,100,200,100);
}
内部也可以写成
QPainter paint(this);
QPen pen(Qt::blue,10,Qt::DashDotLine,Qt::RoundCap,Qt::RoundJoin);
paint.setPen(pen);
paint.drawLine(50,100,50,200);
paint.drawRect(100,100,200,100);
图形如下:

与(1)中图形对比

3、画刷
画刷用来给图形内部填色,包括样式、颜色、渐变、纹理。
(1)、画刷颜色
QPainter使用QBrush来定义画刷,使用.setBrush(QBrush)
设置。
代码:
QPainter paint(this);
QPen pen;
pen.setWidth(3);
pen.setBrush(Qt::blue);
paint.setPen(pen); //设置画笔
paint.setBrush(Qt::red); //设置画刷为红色
paint.drawRect(100,100,200,100);
显示:

(2)、画刷纹理
画刷纹理主要有:图片来源 只展示部分,剩余的在下面介绍。

在使用时,我们需要定义一个QBrush类成员来存放纹理及其颜色。.setStyle(Qt::NoBrush)
来设置纹理.setColor(Qt::Red)
设置纹理颜色
代码:
QPainter paint(this);
QPen pen;
pen.setWidth(3);
pen.setBrush(Qt::blue);
QBrush brush;
brush.setStyle(Qt::Dense1Pattern);
brush.setColor(Qt::red);
paint.setPen(pen);
paint.setBrush(brush);
paint.drawRect(100,100,200,100);
效果:

(3)、纹理渐变
渐变纹理只有在使用下面三种纹理才能使用:
Qt::LinearGradientPattern
、Qt::RadialGradientPattern
、Qt::ConicalGradientPattern
如图:
并且,每种纹理都要定义初始化纹理类。
纹理类型 | 纹理定义-类 | 定义方法 | 参数解释 |
---|---|---|---|
Qt::LinearGradientPattern | QLinearGradient | QLinearGradient(const QPointF &start, const QPointF &finalStop) | 起始点、终点 |
Qt::RadialGradientPattern | QRadialGradient | QRadialGradient(const QPointF ¢er, qreal radius, const QPointF &focalPoint) | 中心点坐标、半径、焦点坐标 |
Qt::ConicalGradientPattern | QConicalGradient | QConicalGradient(const QPointF ¢er, qreal startAngle) | 中点坐标、渐变角度 |
如代码,使用第二个举例:
QPainter paint(this);
QPen pen;
pen.setWidth(3);
pen.setBrush(Qt::blue);
QRadialGradient gradient(200,150,50); //中点坐标、半径、焦点可不写,自定
gradient.setColorAt(0,Qt::red);
gradient.setColorAt(1,Qt::green);
QBrush brush(gradient);
paint.setPen(pen);
paint.setBrush(brush);
paint.drawRect(100,100,200,100);
效果:

(4)、自定图形渐变

没什么好说的,直接上代码:
QPainter paint(this);
QPen pen;
pen.setWidth(3);
pen.setBrush(Qt::blue);
QBrush brush;
brush.setStyle(Qt::TexturePattern); //当你不写这句话时,没有关系,因为在下一句执行时会自动设置style
brush.setTexture(QPixmap("/Users/yucheng/allpic/qq.png"));
paint.setPen(pen);
paint.setBrush(brush);
paint.drawRect(100,100,200,100);
效果:

四、最后
1、简洁代码
在进行简单操作时,总是定义一个新的成员总是很麻烦,比如说自定义纹理,用了将近10行的代码,那么在熟练之后可以用类的构造函数来简化方法。将自定义纹理代码简化如下:
QPainter paint(this);
paint.setPen(QPen(Qt::blue,3));
paint.setBrush(QBrush(QPixmap("/Users/yucheng/allpic/qq.png")));
paint.drawRect(100,100,200,100);
2、反走样
反走样在图形学中就是锯齿,玩游戏的朋友都知道开了抗锯齿的画面都会舒服很多。对比下面两个图形,尤其是上下部分。

仔细发现,左边的圆部分地方会有明显的锯齿,但是右边的就没有,那是因为我们在绘制右边的图形时,添加了反走样。
代码:
QPainter paint(this);
paint.setPen(QPen(Qt::black,50));
paint.drawEllipse(50,50,500,500);
paint.setRenderHint(QPainter::Antialiasing,true); //反走样
paint.drawEllipse(600,50,500,500);
3、颜色取样方式
不管在设定画笔颜色或者画刷颜色时候,我们都用的是Qt自带的颜色。当然还可以使用QColor类中的颜色。
了解QColor的用法参考博客:QColor用法