通过下面一个例子来讲解绘图事件:
窗口刚打开时上面和下面两个标签会显示高温和低温曲线
然后每次双击时,会随机生成几个高温值和低温值,并且把新生成的温度重新绘制上去,从而产生双击曲线变化的效果
绘图事件
当窗口/控件需要重绘时,就会触发该事件,对应的子类是QPaintEvent
对应的事件类型是QEvent::Paint
需要重写的函数是
void paintEvent(QPaintEvent* ev) override
什么时候需要重绘呢,常见的比如:
1.窗口初始化和显示:比如窗口首次创建,或者窗口被覆盖,最小化后再恢复时,会重绘
2.部件大小或位置变化:比如窗口/控件的大小或位置发生变化时,需要重绘来适应新的大小或位置
3.强制重绘:调用窗口/控件的update()或repaint()函数时,对应调用者会被重绘
等等
举例
通过自定义1个控件
该控件有上下两个标签
上面的标签显示高温曲线(只显示7个温度)
下面的标签显示低温曲线(只显示7个温度)
通过事件过滤器的方式来捕获绘图事件,从而将曲线绘制到上下两个标签中
双击标签时,会随机产生7个高温值和低温值,然后调用2个标签的update()方法强制重绘,触发绘图事件,在绘图事件中将新的温度曲线绘制进去
#ifndef PAINTEVENT_H
#define PAINTEVENT_H
#include <QWidget>
#include<QLabel>
#include<QVBoxLayout>
#include<QEvent>
#include<QPainter>
#include<QRandomGenerator64>
class PaintEvent : public QWidget
{
Q_OBJECT
private:
QLabel* top_label;//在这个标签中绘制高温曲线
QLabel* btm_label;//在这个标签中绘制低温曲线
int high_tempera[7]{0};//存储7个高温的温度值
int low_tempera[7]{0};//存储7个低温的温度值
public:
explicit PaintEvent(QWidget *parent = nullptr):QWidget{parent}
{
//构造函数
QVBoxLayout* layout=new QVBoxLayout(this);
layout->setSpacing(0);
layout->setContentsMargins(0,0,0,0);
top_label=new QLabel(this);
top_label->setFrameShape(QFrame::Box);
layout->addWidget(top_label);
btm_label=new QLabel(this);
btm_label->setFrameShape(QFrame::Box);
layout->addWidget(btm_label);
//使用事件过滤器,来自定义这两个标签的绘图事件
//在重写的eventFilter函数中,对这两个标签的绘图事件进行处理
top_label->installEventFilter(this);
btm_label->installEventFilter(this);
//在这个函数中随机生成7个高温和低温值
//并调用2个标签的update方法,触发绘图事件,将随机生成的温度值绘制上去
updateTempurature();
}
protected:
bool eventFilter(QObject* watched,QEvent* ev) override
{
//捕捉绘图事件
if(ev->type()==QEvent::Paint)
{
if(watched==top_label)
{
//在这个函数中将高温曲线绘制到上面的标签中
paintHigh();
}
if(watched==btm_label)
{
//在这个函数中将低温曲线绘制到下面的标签中
paintLow();
}
}
//捕捉鼠标双击事件
else if(ev->type()==QEvent::MouseButtonDblClick)
{
//鼠标双击时在这个函数中随机生成7个高温和低温值
//并调用2个标签的update方法,触发绘图事件,将随机生成的温度值绘制上去
//实现双击曲线值更新的效果
updateTempurature();
}
return QWidget::eventFilter(watched,ev);
}
#define PADDING 50
#define POINT_RADIUS 4//温度圆点的半径
#define TEXT_OFFSET_X 12//温度文本相对于点在X轴的偏移
#define TEXT_OFFSET_Y 10//温度文本相对于点在Y轴的偏移
void paintHigh()//绘制高温曲线
{
//painter将曲线绘制到top_label标签上面
QPainter painter(top_label);
//1.计算X轴坐标
int pointX[7]{0};//绘制7个点,整个曲线左右两边离标签两边留一定的距离PADDING,比如50px
for(int i=0;i<7;++i)
{
//把整个标签的宽度减去两边的PADDING后平均分成6份,表示x坐标增量
//每一个点的x坐标递增这个增量
pointX[i]=0+PADDING+(top_label->width()-PADDING*2)/6*i;
}
//2.计算y轴坐标
int sum=0;//计算7个温度的温度总和
int average=0;//计算7个温度的平均值
for(int i=0;i<7;++i)
{
sum+=high_tempera[i];
}
average=sum/7;
int pointY[7]{0};
//整个标签的高度/2来表示平均温度所在的y坐标
//然后这些温度的y坐标就在平均温度上下波动
int y_center=top_label->height()/2;
//把整个标签的高度平均分成20份,来表示一个y坐标的变化系数,你也可以不是20份,也可以是30,也可以10
//份数越小,那么这些温度的y坐标波动的幅度就越小,份数越大,那么幅度就越大,最好配合前面的图片理解
int increment=top_label->height()/20;
for(int i=0;i<7;++i)
{
//那么每一个温度的y坐标就是平均温度所在的坐标-(温度和平均温度的差值)*变化系数
//qt坐标系是向右x正,向下y正,因此温度越大,那么y应该往上,y就越小,因此要减去(温度和平均温度的差值)*变化系数
pointY[i]=y_center-(high_tempera[i]-average)*increment;
}
//3.开始绘制
QPen pen=painter.pen();
pen.setWidth(2);
pen.setColor(QColor(255,0,0));
painter.setPen(pen);
painter.setBrush(QColor(255,0,0));
painter.setFont(QFont("Microsoft YaHei",14));
//4.绘制点、文本
for(int i=0;i<7;i++)
{
painter.drawEllipse(
QPoint(pointX[i],pointY[i]),
POINT_RADIUS,POINT_RADIUS);
painter.drawText(
QPoint(pointX[i]-TEXT_OFFSET_X,pointY[i]-TEXT_OFFSET_Y),
QString::number(high_tempera[i])+"°");
}
//5.绘制曲线,把个点连起来
for(int i=0;i<7;++i)
{
if(i==6)
{
continue;
}
if(i==0)
{
//第一个点连接第二个点时画虚线
pen.setStyle(Qt::DotLine);//虚线
}
else
{
pen.setStyle(Qt::SolidLine);
}
painter.setPen(pen);
painter.drawLine(QPoint(pointX[i],pointY[i]),QPoint(pointX[i+1],pointY[i+1]));
}
}
void paintLow()//绘制低温曲线,同理
{
QPainter painter(btm_label);//painter将曲线绘制到btm_label标签上面
//1.计算X轴坐标
int pointX[7]{0};//绘制7个点,整个曲线左右两边离标签两边留一定的距离,比如50px
for(int i=0;i<7;++i)
{
pointX[i]=btm_label->pos().x()+PADDING+(btm_label->width()-PADDING*2)/6*i;
}
//2.计算y轴坐标
int sum=0;//计算7个温度的温度总和
int average=0;//计算7个温度的平均值
for(int i=0;i<7;++i)
{
sum+=low_tempera[i];
}
average=sum/7;
int pointY[7]{0};
int y_center=btm_label->height()/2;
int increment=btm_label->height()/20;
for(int i=0;i<7;++i)
{
pointY[i]=y_center-(low_tempera[i]-average)*increment;
}
//3.开始绘制
QPen pen=painter.pen();
pen.setWidth(2);
pen.setColor(QColor(0,0,255));
painter.setPen(pen);
painter.setBrush(QColor(0,0,255));
painter.setFont(QFont("Microsoft YaHei",14));
//4.绘制点、文本
for(int i=0;i<7;i++)
{
painter.drawEllipse(
QPoint(pointX[i],pointY[i]),
POINT_RADIUS,POINT_RADIUS);
painter.drawText(
QPoint(pointX[i]-TEXT_OFFSET_X,pointY[i]-TEXT_OFFSET_Y),
QString::number(low_tempera[i])+"°");
}
//5.绘制曲线
for(int i=0;i<7;++i)
{
if(i==6)
{
continue;
}
if(i==0)
{
pen.setStyle(Qt::DotLine);//虚线
}
else
{
pen.setStyle(Qt::SolidLine);
}
painter.setPen(pen);
painter.drawLine(QPoint(pointX[i],pointY[i]),QPoint(pointX[i+1],pointY[i+1]));
}
}
//更新温度
void updateTempurature()
{
for(int i=0;i<7;i++)
{
//随机生成7个温度
high_tempera[i]=20+QRandomGenerator::global()->generate()%10;
low_tempera[i]=-5+QRandomGenerator::global()->generate()%10;
}
//然后强制触发上下两个标签的绘图事件
top_label->update();
btm_label->update();
}
};
#endif // PAINTEVENT_H