绘图事件(QEvent::Paint),当窗口/控件需要重绘时,触发该事件,它对应的子类是QPaintEvent
本节通过一个向QLabel上绘制高低温曲线的案例,来讲解绘图事件,本节效果:
122.gif

有两种实现方式:

  • 自定义标签控件,并重写paintEvent()
  • 事件过滤器,直接绘制到标准 QLabel

本节采用第一种方式,事件过滤器的实现方式会在绘图一章中讲解

1. 自定义控件 LabelTemp

自定义一个标签控件LabelTemp,让它继承自QWidget,然后重写拖放相关的函数。

1.1 添加 LabelTemp 类

首先,在左侧项目文件名上右键,选择 “Add New…”,选择 “C++ Class”,先继承自QWidget,如下:
image-20260820110021486

然后,把父类由QWidget修改为QLabel,如下:

// labeltemp.h
#include <QLabel>

class LabelTemp : public QLabel {
    Q_OBJECT
public:
    explicit LabelTemp(QWidget *parent = nullptr);

signals:
};

// labeltemp.cpp
#include "labeltemp.h"

LabelTemp::LabelTemp(QWidget *parent) : QLabel{parent}
{
}

1.2 将 LabelTemp 显示到界面

来到paint_widget.cpp,在构造函数中添加LabelTemp控件,如下:

#include "labeltemp.h"

PaintWidget::PaintWidget(QWidget* parent) : QWidget{parent}
{
    QVBoxLayout* verticalLayout = new QVBoxLayout(this);
    verticalLayout->setSpacing(0);
    verticalLayout->setContentsMargins(0, 0, 0, 0);

    //添加一个Label,用于绘制高温曲线
    LabelTemp* lblHigh = new LabelTemp();
    lblHigh->setStyleSheet("background-color: rgb(30, 50, 100);");
    lblHigh->setProperty("high", true); // 由于标识这是一个高温标签
    verticalLayout->addWidget(lblHigh);

    //添加一个Label,用于绘制低温曲线
    LabelTemp* lblLow = new LabelTemp();
    lblLow->setStyleSheet("background-color: rgb(30, 50, 100);");
    lblLow->setProperty("high", false); // 由于标识这是一个低温标签
    verticalLayout->addWidget(lblLow);
}

此时,运行效果:
image-20260820110040765

2. 绘制

2.1 开始绘制

首先,来到labeltemp.h,声明成员变量和事件函数,如下:

class LabelTemp : public QLabel {

protected:
    void paintEvent(QPaintEvent *event);
    void mouseDoubleClickEvent(QMouseEvent *event);

private:
    QVector<int> values = {0, 0, 0, 0, 0, 0, 0};
};

然后,来到labeltemp.cpp 实现paintEvent()函数,如下:

#include <QPainter>

// 温度曲线相关的宏
#define PADDING 50
#define INCREMENT 8       // 温度曲线像素增量
#define POINT_RADIUS 3    // 曲线描点的大小
#define TEXT_OFFSET_X 12  // 温度文本相对于点的偏移
#define TEXT_OFFSET_Y 10  // 温度文本相对于点的偏移

void LabelTemp::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);

    // 1. 计算 x 轴坐标
    int pointX[7] = {0};
    for (int i = 0; i < 7; i++) {
        pointX[i] = this->pos().x() + PADDING + (this->width() - PADDING * 2) / 6 * i;
    }

    // 2. 计算 y 轴坐标
    // 2.1 计算平均值
    int tempSum = 0;
    int tempAverage = 0;

    for (int i = 0; i < 7; i++) {
        tempSum += (values[i]);
    }
    tempAverage = tempSum / 7;  // 温度平均值

    // 2.2 计算 y 轴坐标
    int pointY[7] = {0};
    int yCenter = this->height() / 2;
    int increment = this->height() / 20;
    for (int i = 0; i < 7; i++) {
        pointY[i] = yCenter - ((values[i] - tempAverage) * increment);
    }

    // 3. 开始绘制
    // 3.1 初始化画笔
    QPen pen = painter.pen();
    pen.setWidth(2);  // 画笔宽度
    qDebug() << this->property("high").toBool();
    pen.setColor(property("high").toBool() ? QColor(30, 160, 60) : QColor(255, 170, 0));  // 画笔颜色

    painter.setPen(pen);
    painter.setBrush(property("high").toBool() ? QColor(30, 160, 60) : QColor(255, 170, 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(values[i]) + "°");
    }

    // 3.3 绘制曲线
    for (int i = 0; i < 6; i++) {
        pen.setStyle(i == 0 ? Qt::DotLine : Qt::SolidLine);  // 虚线
        painter.setPen(pen);
        painter.drawLine(pointX[i], pointY[i], pointX[i + 1], pointY[i + 1]);
    }
}

此时,运行结果(默认温度值为0):
image-20260820110108951

2.2 双击更新

双击高温标签或低温标签,更新高低温的温度值,并重绘曲线
只需实现mouseDoubleClickEvent()函数,如下:

#include <QRandomGenerator>

void LabelTemp::mouseDoubleClickEvent(QMouseEvent *event)
{
    for (int i = 0; i < 7; i++) {
        bool high = this->property("high").toBool();
        values[i] = (high ? 20 : -5) + QRandomGenerator::global()->generate() % 10;
    }
    this->update();
}

此时,每次双击鼠标,就可以刷新高低温曲线了!

3. 平滑曲线

平滑曲线会在《绘图、自定义控件》一章进行详细讲解。平滑曲线的效果,如下:
122.gif