本节对应的视频讲解:B_站_链_接
【QT开发笔记-基础篇】 第4章 事件 4.8 绘图事件(1)
本章要实现的整体效果如下:
QEvent::Paint
当窗口/控件需要重绘时,触发该事件,它对应的子类是 QPaintEvent
本节通过一个向 QLabel
上绘制高低温曲线的案例,来讲解绘图事件。
有两种实现方式:
- 自定义标签控件,并重写
paintEvent()
- 事件过滤器,直接绘制到标准
QLabel
本节采用事件过滤器的方法来实现。
1. 界面放置高低温标签
首先,在 paint-widget.h
中声明两个标签:
#include <QLabel>
class PaintWidget : public QWidget
{
private:
QLabel* lblHigh;
QLabel* lblLow;
};
然后,在 paint-widget.cpp
的构造中显示两个标签:
PaintWidget::PaintWidget(QWidget* parent) : QWidget{parent}
{
QVBoxLayout* verticalLayout = new QVBoxLayout(this);
verticalLayout->setSpacing(0);
verticalLayout->setContentsMargins(0, 0, 0, 0);
//添加一个Label,用于绘制高温曲线
lblHigh = new QLabel(this);
lblHigh->setText("");
lblHigh->setFrameShape(QFrame::Box);
verticalLayout->addWidget(lblHigh);
//添加一个Label,用于绘制低温曲线
lblLow = new QLabel(this);
lblLow->setText("");
lblLow->setFrameShape(QFrame::Box);
verticalLayout->addWidget(lblLow);
}
此时,运行效果如下:
2. 安装事件过滤器
在当前窗口中拦截两个标签的事件,就需要拦截发给标签的事件
首先,在 paint_widget.cpp
中为标签安装事件过滤器:
PaintWidget::PaintWidget(QWidget* parent) : QWidget{parent}
{
// ...
lblHigh->installEventFilter(this);
lblLow->installEventFilter(this);
}
然后,在 paint_widget.h
中声明 eventFilter()
函数:
class PaintWidget : public QWidget
{
protected:
bool eventFilter(QObject* watched, QEvent* event);
};
最后,在 paint_widget.cpp
中实现 eventFilter()
函数
bool PaintWidget::eventFilter(QObject* watched, QEvent* event)
{
if ( event->type() == QEvent::Paint ) {
if ( watched == lblHigh ) {
// paintHigh(); // 后边实现
qDebug() << "paint lblHigh";
}
if ( watched == lblLow ) {
// paintLow(); // 后边实现
qDebug() << "paint lblLow";
}
}
return QWidget::eventFilter(watched, event);
}
具体流程:
- 窗口刚出现时两个标签需要重绘,框架发送
QEvent::Paint
事件给标签 - 事件被当前窗口拦截,进而调用其
eventFilter()
方法 - 在
eventFilter()
中,判断事件类型以及目标控件,来调用绘制高低温曲线的函数
3. 实现 paintHigh、paintLow
首先,在 paint_widget.h
中声明这两个函数,并声明两个数组,用来记录高温和低温
class PaintWidget : public QWidget
{
private:
// 绘制高低温曲线
void paintHigh();
void paintLow();
private:
int mHighTemp[7] = {0};
int mLowTemp[7] = {0};
};
然后,在 paint_widget.cpp
中实现这两个函数
paintHigh()
实现如下:
#include <QPainter>
// 温度曲线相关的宏
#define PADDING 50
#define INCREMENT 8 // 温度曲线像素增量
#define POINT_RADIUS 4 // 曲线描点的大小
#define TEXT_OFFSET_X 12 // 温度文本相对于点的偏移
#define TEXT_OFFSET_Y 10 // 温度文本相对于点的偏移
void PaintWidget::paintHigh()
{
QPainter painter(lblHigh);
painter.setRenderHint(QPainter::Antialiasing, true); // 抗锯齿
// 1. 计算 x 轴坐标
int pointX[7] = {0};
for ( int i = 0; i < 7; i++ ) {
pointX[i] = lblHigh->pos().x() + PADDING + (lblHigh->width() - PADDING * 2) / 6 * i;
}
// 2. 计算 y 轴坐标
// 2.1 计算平均值
int tempSum = 0;
int tempAverage = 0;
for ( int i = 0; i < 7; i++ ) {
tempSum += mHighTemp[i];
}
tempAverage = tempSum / 7; // 最高温平均值
// 2.2 计算 y 轴坐标
int pointY[7] = {0};
int yCenter = lblHigh->height() / 2;
int increment = lblHigh->height() / 20;
for ( int i = 0; i < 7; i++ ) {
pointY[i] = yCenter - ((mHighTemp[i] - tempAverage) * increment);
}
// 3. 开始绘制
// 3.1 初始化画笔
QPen pen = painter.pen();
pen.setWidth(2); //设置画笔宽度为1
pen.setColor(QColor(255, 0, 0)); //设置颜色
painter.setPen(pen);
painter.setBrush(QColor(255, 0, 0)); //设置画刷颜色
painter.setFont(QFont("Microsoft YaHei", 14));
// 3.2 画点、写文本
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(mHighTemp[i]) + "°");
}
// 3.3 绘制曲线
for ( int i = 0; i < 6; i++ ) {
if ( i == 0 ) {
pen.setStyle(Qt::DotLine); //虚线
painter.setPen(pen);
} else {
pen.setStyle(Qt::SolidLine); // 实线
painter.setPen(pen);
}
painter.drawLine(pointX[i], pointY[i], pointX[i + 1], pointY[i + 1]);
}
}
paintLow()
实现如下:
void PaintWidget::paintLow()
{
QPainter painter(lblLow);
painter.setRenderHint(QPainter::Antialiasing, true); // 抗锯齿
// 1. 计算 x 轴坐标
int pointX[7] = {0};
for ( int i = 0; i < 7; i++ ) {
pointX[i] = lblLow->pos().x() + PADDING + (lblLow->width() - PADDING * 2) / 6 * i;
}
// 2. 计算 y 轴坐标
// 2.1 计算平均值
int tempSum = 0;
int tempAverage = 0;
for ( int i = 0; i < 7; i++ ) {
tempSum += mLowTemp[i];
}
tempAverage = tempSum / 7; // 最高温平均值
// 2.2 计算 y 轴坐标
int pointY[7] = {0};
int yCenter = lblLow->height() / 2;
int increment = lblLow->height() / 20;
for ( int i = 0; i < 7; i++ ) {
pointY[i] = yCenter - ((mLowTemp[i] - tempAverage) * increment);
}
// 3. 开始绘制
// 3.1 初始化画笔
QPen pen = painter.pen();
pen.setWidth(2); // 设置画笔宽度为1
pen.setColor(QColor(0, 0, 255)); // 设置颜色
painter.setPen(pen);
painter.setBrush(QColor(0, 0, 255)); //设置画刷颜色
painter.setFont(QFont("Microsoft YaHei", 14));
// 3.2 画点、写文本
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(mLowTemp[i]) + "°");
}
// 3.3 绘制曲线
for ( int i = 0; i < 6; i++ ) {
if ( i == 0 ) {
pen.setStyle(Qt::DotLine); //虚线
painter.setPen(pen);
} else {
pen.setStyle(Qt::SolidLine); // 实线
painter.setPen(pen);
}
painter.drawLine(pointX[i], pointY[i], pointX[i + 1], pointY[i + 1]);
}
}
此时,由于数组 mHighTemp
和 mLowTemp
数组的初始值都是 0
,因此绘制出来的是 7
条水平直线的连接,如下:
4. 产生随机温度
接下来,随机生成高温数组、低温数组
首先,在 paint_widget.h
中声明一个 updateTemp 的函数
class PaintWidget : public QWidget
{
private:
// 更新高低温
void updateTemp();
};
然后,在 paint_widget.cpp
中实现 updateTemp()
函数:
#include <QRandomGenerator64>
void PaintWidget::updateTemp()
{
for ( int i = 0; i < 7; i++ ) {
mHighTemp[i] = 20 + QRandomGenerator::global()->generate() % 10;
mLowTemp[i] = -5 + QRandomGenerator::global()->generate() % 10;
}
lblHigh->update();
lblLow->update();
}
最后,在 paint_widget.cpp
的构造中,手动调用 updateTemp()
函数:
PaintWidget::PaintWidget(QWidget* parent) : QWidget{parent}
{
// ...
updateTemp();
lblHigh->installEventFilter(this);
lblLow->installEventFilter(this);
}
此时,第一次运行后,就可以生成高低温曲线了,如下:
5. 双击更新高低温曲线
在 eventFilter()
中,拦截鼠标双击事件,如下:
bool PaintWidget::eventFilter(QObject* watched, QEvent* event)
{
if ( event->type() == QEvent::Paint ) {
if ( watched == lblHigh ) {
paintHigh(); // 后边实现
// qDebug() << "paint lblHigh";
}
if ( watched == lblLow ) {
paintLow(); // 后边实现
// qDebug() << "paint lblLow";
}
} else if ( event->type() == QEvent::MouseButtonDblClick ) {
updateTemp();
}
return QWidget::eventFilter(watched, event);
}
此时,每次双击鼠标,就可以刷新高低温曲线了,如下:
此时整体调用流程:
- 拦截标签的双击事件,调用
updateTemp()
函数 lblHigh->update()
表示要重绘标签,框架发送 QEvent::Paint 事件给标签- 事件被窗口拦截,进而调用其
eventFilter()
- 在
eventFilter()
中,调用paintHigh()
和paintLow()
来真正在标签上绘制曲线