目录
参考链接:
QT | QPainter,PaintEvent,setRenderHint(防止图形走样)_parker_1的博客-CSDN博客
Qt 之图形(QPainterPath)_青春不老,奋斗不止!-CSDN博客_qpainterpath
Qt开发技术:Qt绘图系统(二)QPainter详解_长沙红胖子-CSDN博客_qpainter
自定义控件学习笔记(二)drawPath()_yy471101598的博客-CSDN博客_drawpath
Qpainter画时钟_qqwangfan的专栏-CSDN博客
这里是一个职场小白的学习历程,所有参考过的网址链接都会贴在上方文章开头处。
--为了方便查看,部分参考链接都挪到了我疑惑时查找的地方
如有侵权请及时告知。
绘画仪表盘需要分别绘制
外圆环、内圆环、扇形、文本和刻度、指针五个东西
这几个元素合并之后就形成了
接下来让我们根据上述拆分的五小部分,分别阐述如何实现
一、准备工作
要想让控件显示在界面上,单纯的在mainwindow.cpp的构造函数中编辑是没有用的。
我们需要重写paintEvent函数
从qt帮助文档可知virtual void paintEvent(QPaintEvent *) override
所有在头文件中需要填写
(我这里先不给完整代码,在最后会放完整的程序代码。也可直接观看参考链接的原码)
#include <QPainter>
----略-----
protected:
void paintEvent(QPaintEvent *event) override;
----略-----
然后重写这个函数
下面是初始化画笔和画图并显示的操作
添加初始化代码
QPainter paint(this);
paint.setBrush(QBrush(Qt::red, Qt::SolidPattern));
其中反走样功能可以不要,因为我们的控件没复杂到需要这个功能。
void MainWindow::paintEvent(QPaintEvent *event)
{
QPainter paint(this);
//反走样功能,也就是防止锯齿现象出现
paint.setRenderHint(QPainter::Antialiasing, true);
//设置了填充的颜色和图案
paint.setBrush(QBrush(Qt::red, Qt::SolidPattern));
QPainterPath path;
//void QPainterPath::addEllipse(qreal x, qreal y, qreal width, qreal height)
path.addEllipse(100, 100, 100, 100);
//通过路径绘制图形
paint.drawPath(path);
}
然后就可以得到一个红彤彤的圆
接下来就开始将上述的五个图形都画出来
二、画局部控件
1、外环
设计思想:
先设置一个大圆A然后设置一个小圆B,大圆面积减去小圆所占面积,就可以得到一个圈。
void MainWindow::draw_Ellipse(QVector<double> &num1,
QVector<double> &num2, QPainter &paint)
{
//画笔
//void QPainterPath::addEllipse(qreal x, qreal y, qreal width, qreal height)
QPainterPath big;
big.addEllipse(num1.at(0), num1.at(1), num1.at(2), num1.at(3));
QPainterPath small;
big.addEllipse(num2.at(0), num2.at(1), num2.at(2), num2.at(3));
QPainterPath path;
//大圆减去小圆得到的一个环
//就像是删除大圆与小圆的交集
path = big-small;
//显示QPainterPath
paint.drawPath(path);
}
2、内环
设计思想:
与外环的设计思想基本一样,且调用了外环的函数,唯一不同的是多了删除边线。
删除前
删除后就不必说了吧
具体代码
void MainWindow::draw_Ellipse2(QVector<double> &num1, QVector<double> &num2, QPainter &paint)
{
//设置画笔颜色和填充形状
//QBrush-用于填充几何图形的调色板,由颜色和填充风格组成
paint.setBrush(QBrush(Qt::gray, Qt::SolidPattern));
//设置画笔删除边线
paint.setPen(Qt::NoPen);
draw_Ellipse1(num1, num2, paint);
}
原文作者在这里还使用了一个类型QRadialGradient
并且还有一段看起来毫无无意义的代码
void MainWindow::draw_Ellipse(QVector<double> &num, QVector<double> &num1, QPainter &paint, QRadialGradient &radgrient)
{
radgrient.setColorAt(0.0,Qt::darkGray);
radgrient.setColorAt(0.2,Qt::white);
radgrient.setColorAt(0.5,Qt::darkGray);
//忽略下方代码
//paint.setBrush(QBrush(Qt::gray,Qt::SolidPattern));
//paint.setPen(Qt::NoPen);
//draw_Ellipse(num,num1,paint);
}
经过百度发现这是一段以圆心为中心渐变填充的函数类型QRadialGradient,但是这里原作者并没有使用到它。
若是要调用的话还应该使用
paint.setBrush(radrient);
当然这些在本程序中并不影响,我怀疑原作者这样写只是为了强行重载。
3、扇形
原作者称这个为扇形,但是我看着不止扇形。
应当还可以拆出三个部分:绿环、红环、扇形
其中的绿环和红环的做法应当是与外环一样,调用了最外圈的蓝环函数。
然后画了一个扇形图,覆盖了绿环。
然而实际研究中我发现原作者并不是这么想的
它将这一块分层了四个部分
即
头两个图形就像一个龙虾钳子,姑且称它为钳子吧
绿钳子和灰钳子是要合并到一起变成一个钳子的
结果为:
要想得到这样一个结果首先得设置角度,因为这个不再仅仅是靠x,y轴定点填充了。
这里不仅要确定xy轴和宽高的值,还要确定角度。
而角度的知识可以参考以下链接:
DrawPie函数的使用说明_千本樱景严的博客-CSDN博客
链接中有个图很好,我截出来放一放
提取以下就是整圆的大小在360*16,正值顺时针旋转,负值逆时针旋转,而0在三点钟方向
即上方整圆面积为360*12------spanAngle
旋转角度为顺时针-360*2-------startAngle
所以上方是旋转之后的结果
旋转之前的结果为
一个360面积占这么多
所以做一个扇形图
首先我们要确定两个角度,即圆占面积角度和圆旋转角度------两个int型
也要确定xy和width、height-----------QRectF型
然后使用QPainter的drawPie函数进行绘制
然后还有一小部分,也是所占角度和旋转角度加上轴和大小。
根据计算获得
QRectF rect_top2(260, 260, 180.0, 180.0);//x,y,width,height
paint.drawPie(rect_top2, -360*6-60, 360*5-160);//startAngle, spanAngle
红圈圈和最开始的蓝圈圈的做法是一样的
所以最终代码为:
void MainWindow::draw_pie(QPainter &paint)
{
//正值逆时针,负值顺时针,0度位于三点钟的位置
//外圆起始角度
int startAngle = -360*2;
//整圆360*16
//外圆覆盖范围
int spanAngle = 360*12;
QRectF rect_top(260, 260, 180.0, 180.0); //绿环
QRectF rect_top1(280, 280, 140.0, 140.0);//灰环
QRectF rect_top2(260, 260, 180.0, 180.0);//灰扇形
paint.setBrush(QBrush(Qt::darkGreen, Qt::SolidPattern));
paint.drawPie(rect_top, startAngle, spanAngle);//绿环
paint.setBrush(QBrush(Qt::gray, Qt::SolidPattern));
paint.drawPie(rect_top1, startAngle, spanAngle);//灰环
paint.drawPie(rect_top2, -360*6-60, 360*5-160);//灰扇形
//红环
paint.setBrush(QBrush(Qt::darkRed, Qt::SolidPattern));
QVector<double> num5 = {320, 320, 60, 60};
QVector<double> num6 = {327.5, 327.5, 45, 45};
draw_Ellipse1(num5, num6, paint);
}
4、刻度和文字
设计思想:
利用QPainer的translate函数确定刻度和文字的位置
然后用QPainer的setPen的画笔颜色
最后用for和if、switch语句逻辑判断,逻辑很容易看懂,多看一下代码就好了。
这里原作者将刻度分为了两个部分:
50-100一部分、0-40一部分
代码如下:
void MainWindow::draw_text_line(QPainter &paint)
{
//设置刻度环的位置
paint.translate(350, 350);
//设置刻度环的颜色
paint.setPen(Qt::black);
for (int i=0; i<=25 ;i++)
{
if(i%5 == 0)
{
//x1,y1,x2,y2
//从(x1, y1)到(x2, y2)画一条线。
paint.drawLine(0, -90, 0, -100);
switch (i/5)
{
case 0: paint.drawText(-5, -103, tr("%1").arg(50));break;
case 1: paint.drawText(-5, -103, tr("%1").arg(60));break;
case 2: paint.drawText(-5, -103, tr("%1").arg(70));break;
case 3: paint.drawText(-5, -103, tr("%1").arg(80));break;
case 4: paint.drawText(-5, -103, tr("%1").arg(90));break;
case 5: paint.drawText(-5, -103, tr("%1").arg(100));break;
}
}
else
{
paint.drawLine(0, -95, 0, -100);
}
//使用旋转分散项目
paint.rotate(5.2);
}
paint.rotate(95);
for(int i=0; i<=24; i++)
{
if(i%5==0)
{
paint.drawLine(0, -90, 0, -100);
switch (i/5)
{
case 0: paint.drawText(-5, -103, tr("%1").arg(0));break;
case 1: paint.drawText(-5, -103, tr("%1").arg(10));break;
case 2: paint.drawText(-5, -103, tr("%1").arg(20));break;
case 3: paint.drawText(-5, -103, tr("%1").arg(30));break;
case 4: paint.drawText(-5, -103, tr("%1").arg(40));break;
}
}
else
{
paint.drawLine(0, -95, 0, -100);
}
paint.rotate(5.2);
}
}
然后我将这两个for循环揉成了一个
即
void MainWindow::draw_text_line(QPainter &paint)
{
//设置刻度环的位置
paint.translate(350, 350);
//设置刻度环的颜色
paint.setPen(Qt::black);
for (int i=0;i<=50;i++)
{
if(i%5==0)
{
paint.drawLine(0, -90, 0, -100);
switch (i/5)
{
//drawText(int,int,QString)
//使用绘制器当前定义的文本方向,在位置(x, y)绘制给定文本。
case 0: paint.drawText(-5, -103, tr("%1").arg(0));break;
case 1: paint.drawText(-5, -103, tr("%1").arg(10));break;
case 2: paint.drawText(-5, -103, tr("%1").arg(20));break;
case 3: paint.drawText(-5, -103, tr("%1").arg(30));break;
case 4: paint.drawText(-5, -103, tr("%1").arg(40));break;
case 5: paint.drawText(-5, -103, tr("%1").arg(50));break;
case 6: paint.drawText(-5, -103, tr("%1").arg(60));break;
case 7: paint.drawText(-5, -103, tr("%1").arg(70));break;
case 8: paint.drawText(-5, -103, tr("%1").arg(80));break;
case 9: paint.drawText(-5, -103, tr("%1").arg(90));break;
case 10: paint.drawText(-5, -103, tr("%1").arg(100));break;
}
}
else
{
paint.drawLine(0, -95, 0, -100);
}
paint.rotate(5);
}
}
发现了问题
我的代码的运行结果是这样子的
然后我发现无论我怎么设置起始位置,都是从圆心上方的正中心开始画圈。
所以这里我明白了作者为什么分成了两份,作者将50-100放在了开头,然后让0-40强行旋转。
让40与50连接从而形成了最终结果。
作者0-40旋转前的结果
旋转后就变成了最终结果
然而我现在的情况是这样的
我思考了一下,既然半个环能旋转,那我让整个刻度环旋转呢
于是我试验了一下
我想让0逆时针旋转120度的样子大概就是
然后我paint.rotate(120);,发现转过了。。。。
开始我以为是这个函数不是以正常角度旋转的,后来我发现,这不就是顺时针的120吗
我突然想起顺时针是正值,逆时针是负值
当我把paint.rotate(-120);这样改成负数后
就与我期望效果差不多了,角度有些不合适但是问题不大,思路是对的。
于是刻度与文本的最终代码为:
void MainWindow::draw_text_line(QPainter &paint)
{
//设置刻度环的位置
paint.translate(350, 350);
//设置刻度环的颜色
paint.setPen(Qt::black);
//旋转整个刻度环
paint.rotate(-125);
for (int i=0;i<=50;i++)
{
if(i%5==0)
{
//x1,y1,x2,y2
//从(x1, y1)到(x2, y2)画一条线。
paint.drawLine(0, -90, 0, -100);
switch (i/5)
{
//drawText(int,int,QString)
//使用绘制器当前定义的文本方向,在位置(x, y)绘制给定文本。
case 0: paint.drawText(-5, -103, tr("%1").arg(0));break;
case 1: paint.drawText(-5, -103, tr("%1").arg(10));break;
case 2: paint.drawText(-5, -103, tr("%1").arg(20));break;
case 3: paint.drawText(-5, -103, tr("%1").arg(30));break;
case 4: paint.drawText(-5, -103, tr("%1").arg(40));break;
case 5: paint.drawText(-5, -103, tr("%1").arg(50));break;
case 6: paint.drawText(-5, -103, tr("%1").arg(60));break;
case 7: paint.drawText(-5, -103, tr("%1").arg(70));break;
case 8: paint.drawText(-5, -103, tr("%1").arg(80));break;
case 9: paint.drawText(-5, -103, tr("%1").arg(90));break;
case 10: paint.drawText(-5, -103, tr("%1").arg(100));break;
}
}
else
{
paint.drawLine(0, -95, 0, -100);
}
//利用旋转分散项目
paint.rotate(5);
}
}
5、指针
最后一个部件指针分为两个小部分
指针和指针指向的数字即文本
所以就是指针和文本两部分
指针这边设计思路如下:
指针可以理解为一个等腰三角形,首先要确定三角形的三个点
然后使用setBrush进行颜色填充。
用drawConvexPolygon绘制多边形形状,确定好点和点的数量。
最后将指针旋转角度到自己满意的位置即可。
文字就不必说了,跟上面刻度的文字赋值是一样的。
上代码:
void MainWindow::draw_point(QPainter &paint)
{
paint.save();//保存当前小部件的形状和状态
//1、指针
//指针形状
//指针可理解为等腰三角形
QPoint hand[3]={
QPoint(0, -90),//顶点
QPoint(4, -25),//右底点
QPoint(-4, -25),//左底点
};
//指针颜色
QColor handcolor(200, 10, 10, 200);
paint.setBrush(handcolor);//填充指针颜色
paint.rotate(105);//旋转指针角度
paint.rotate(value*2.5);//使指针运动了起来
//绘制指针
//---官方文档解释(绘制由第一个点定义的凸多边形使用当前笔计数数组中的点。)
//drawConvexPolygon(图形点的位置, 点的个数)
paint.drawConvexPolygon(hand, 3);
paint.restore();//恢复小部件的形状和状态
//2、文字
paint.rotate(-130);//旋转文字角度
paint.drawText(-6, 3, tr("%1").arg(value));//圆心中间数字的位置和内容
}
其中的save函数和restore函数不是很理解,为什么非要有它们。因为删除这两个函数结果也没有什么影响。查看百度和帮助文档只知道是保存和恢复的作用。但是不知道为什么要这样做。希望有大佬看到了可以指点一下我的迷茫,感谢!
然后这五个小部件就此结束了。
现在我们只需要把它合并关联起来,粘贴到画布上就可以了。
直接在paintEvent重写该函数然后调用。
三、完整程序代码
所以最终完整程序的代码为:
目录结构
MainWindows.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QPainter>
#include <QVector>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
//声明为explicit的构造函数不能在隐式转换中使用
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
int value;
private:
//绘画初始化
void painter_init(QPainter &paint);
//绘制圆环1
void draw_Ellipse1(QVector<double> &num1, QVector<double> &num2, QPainter &paint);
//重载draw_Ellipse函数,绘制圆环2
void draw_Ellipse2(QVector<double> &num1, QVector<double> &num2, QPainter &paint);
//绘画扇形
void draw_pie(QPainter &paint);
//绘制文本和刻度
void draw_text_line(QPainter &paint);
//绘制指针
void draw_point(QPainter &paint);
protected:
void paintEvent(QPaintEvent *event) override;
protected slots:
void move(int num);//拖动水平条的槽函数
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
value = 0;
ui->horizontalSlider->setRange(0, 100);
connect(ui->horizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(move(int)));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::move(int num)
{
value = num;
qDebug() << value;
update();
}
//外环------理解
void MainWindow::draw_Ellipse1(QVector<double> &num1, QVector<double> &num2, QPainter &paint)
{
//画笔
//void QPainterPath::addEllipse(qreal x, qreal y, qreal width, qreal height)
QPainterPath big;
big.addEllipse(num1.at(0), num1.at(1), num1.at(2), num1.at(3));
QPainterPath small;
big.addEllipse(num2.at(0), num2.at(1), num2.at(2), num2.at(3));
QPainterPath path;
//大圆减去小圆得到的一个环
//就像是删除大圆与小圆的交集
path = big-small;
//显示QPainterPath
paint.drawPath(path);
}
//内环-----理解
void MainWindow::draw_Ellipse2(QVector<double> &num1, QVector<double> &num2, QPainter &paint)
{
//设置画笔颜色和填充形状
//QBrush-用于填充几何图形的调色板,由颜色和填充风格组成
paint.setBrush(QBrush(Qt::gray, Qt::SolidPattern));
//设置画笔删除边线
paint.setPen(Qt::NoPen);
draw_Ellipse1(num1, num2, paint);
}
//扇形-----理解
void MainWindow::draw_pie(QPainter &paint)
{
//正值逆时针,负值顺时针,0度位于三点钟的位置
//外圆起始角度
int startAngle = -360*2;
//整圆360*16
//外圆覆盖范围
int spanAngle = 360*12;
QRectF rect_top(260, 260, 180.0, 180.0); //绿环
QRectF rect_top1(280, 280, 140.0, 140.0);//灰环
QRectF rect_top2(260, 260, 180.0, 180.0);//灰扇形
paint.setBrush(QBrush(Qt::darkGreen, Qt::SolidPattern));
paint.drawPie(rect_top, startAngle, spanAngle);//绿环
paint.setBrush(QBrush(Qt::gray, Qt::SolidPattern));
paint.drawPie(rect_top1, startAngle, spanAngle);//灰环
paint.drawPie(rect_top2, -360*6-60, 360*4+120);//灰扇形
//红环
paint.setBrush(QBrush(Qt::darkRed, Qt::SolidPattern));
QVector<double> num5 = {320, 320, 60, 60};
QVector<double> num6 = {327.5, 327.5, 45, 45};
draw_Ellipse1(num5, num6, paint);
}
//文字和刻度----理解
void MainWindow::draw_text_line(QPainter &paint)
{
//设置刻度环的位置
paint.translate(350, 350);
//设置刻度环的颜色
paint.setPen(Qt::black);
//旋转整个刻度环
paint.rotate(-125);
for (int i=0;i<=50;i++)
{
if(i%5==0)
{
//x1,y1,x2,y2
//从(x1, y1)到(x2, y2)画一条线。
paint.drawLine(0, -90, 0, -100);
switch (i/5)
{
//drawText(int,int,QString)
//使用绘制器当前定义的文本方向,在位置(x, y)绘制给定文本。
case 0: paint.drawText(-5, -103, tr("%1").arg(0));break;
case 1: paint.drawText(-5, -103, tr("%1").arg(10));break;
case 2: paint.drawText(-5, -103, tr("%1").arg(20));break;
case 3: paint.drawText(-5, -103, tr("%1").arg(30));break;
case 4: paint.drawText(-5, -103, tr("%1").arg(40));break;
case 5: paint.drawText(-5, -103, tr("%1").arg(50));break;
case 6: paint.drawText(-5, -103, tr("%1").arg(60));break;
case 7: paint.drawText(-5, -103, tr("%1").arg(70));break;
case 8: paint.drawText(-5, -103, tr("%1").arg(80));break;
case 9: paint.drawText(-5, -103, tr("%1").arg(90));break;
case 10: paint.drawText(-5, -103, tr("%1").arg(100));break;
}
}
else
{
paint.drawLine(0, -95, 0, -100);
}
//利用旋转分散项目
paint.rotate(5);
}
}
//指针-----理解
void MainWindow::draw_point(QPainter &paint)
{
paint.save();//保存当前小部件的形状和状态
//1、指针
//指针形状
//指针可理解为等腰三角形
QPoint hand[3]={
QPoint(0, -90),//顶点
QPoint(4, -25),//右底点
QPoint(-4, -25),//左底点
};
//指针颜色
QColor handcolor(200, 10, 10, 200);
paint.setBrush(handcolor);//填充指针颜色
paint.rotate(105);//旋转指针角度
paint.rotate(value*2.5);//使指针运动了起来
//绘制指针
//---官方文档解释(绘制由第一个点定义的凸多边形使用当前笔计数数组中的点。)
//drawConvexPolygon(图形点的位置, 点的个数)
paint.drawConvexPolygon(hand, 3);
paint.restore();//恢复小部件的形状和状态
//2、文字
paint.rotate(-130);//旋转文字角度
paint.drawText(-6, 3, tr("%1").arg(value));//圆心中间数字的位置和内容
}
//初始化
void MainWindow::painter_init(QPainter &paint)
{
paint.setRenderHint(QPainter::Antialiasing, true);
paint.setBrush(QBrush(Qt::darkBlue, Qt::SolidPattern));
}
//合并五个部件(圆环、指针、文本刻度、扇形)
void MainWindow::paintEvent(QPaintEvent *event)
{
QPainter paint(this);
painter_init(paint);//初始化绘画
//获取外圆环
//参数属性: X轴,Y轴,width,height
QVector<double> num1 = {200, 200, 300, 300};//设置圆环位置和大小
QVector<double> num2 = {220, 220, 260, 260};//设置圆环位置和大小
draw_Ellipse1(num1,num2,paint);//绘制外圆
// 获取内圆环
QVector<double> num3 = {220, 220, 260, 260};
QVector<double> num4 = {260, 260, 180, 180};
draw_Ellipse2(num3, num4, paint);
// 获取扇形并重叠
draw_pie(paint);
// 显示刻度和文本
draw_text_line(paint);
// 显示指针
draw_point(paint);
}
完美结束!