• 【QT开发笔记-基础篇】| 第四章 事件QEvent | 4.8 绘图事件


    本节对应的视频讲解:B_站_链_接

    QT开发笔记-基础篇】 第4章 事件 4.8 绘图事件(1)


    本章要实现的整体效果如下:

    整体效果

    QEvent::Paint

    ​ 当窗口/控件需要重绘时,触发该事件,它对应的子类是 QPaintEvent

    本节通过一个向 QLabel 上绘制高低温曲线的案例,来讲解绘图事件。

    有两种实现方式:

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

    本节采用事件过滤器的方法来实现。


    1. 界面放置高低温标签

    首先,在 paint-widget.h 中声明两个标签:

    #include 
    
    class PaintWidget : public QWidget
    {
    private:
        QLabel* lblHigh;
        QLabel* lblLow;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    然后,在 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    此时,运行效果如下:

    运行效果


    2. 安装事件过滤器

    在当前窗口中拦截两个标签的事件,就需要拦截发给标签的事件

    首先,在 paint_widget.cpp 中为标签安装事件过滤器:

    PaintWidget::PaintWidget(QWidget* parent) : QWidget{parent}
    {
    	// ...
    
        lblHigh->installEventFilter(this);
        lblLow->installEventFilter(this);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    然后,在 paint_widget.h 中声明 eventFilter() 函数:

    class PaintWidget : public QWidget
    {
    protected:
        bool eventFilter(QObject* watched, QEvent* event);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    最后,在 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    具体流程:

    • 窗口刚出现时两个标签需要重绘,框架发送 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};
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    然后,在 paint_widget.cpp 中实现这两个函数

    paintHigh() 实现如下:

    #include 
    
    // 温度曲线相关的宏
    #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]);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    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]);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    此时,由于数组 mHighTempmLowTemp 数组的初始值都是 0 ,因此绘制出来的是 7 条水平直线的连接,如下:

    此时效果


    4. 产生随机温度

    接下来,随机生成高温数组、低温数组

    首先,在 paint_widget.h 中声明一个 updateTemp 的函数

    class PaintWidget : public QWidget
    {
    private:
        // 更新高低温
        void updateTemp();
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后,在 paint_widget.cpp 中实现 updateTemp() 函数:

    #include 
    
    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    最后,在 paint_widget.cpp 的构造中,手动调用 updateTemp() 函数:

    PaintWidget::PaintWidget(QWidget* parent) : QWidget{parent}
    {
        // ...
    
        updateTemp();
    
        lblHigh->installEventFilter(this);
        lblLow->installEventFilter(this);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    此时,第一次运行后,就可以生成高低温曲线了,如下:

    此时效果


    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    最终效果

    此时整体调用流程:

    • 拦截标签的双击事件,调用 updateTemp() 函数
    • lblHigh->update() 表示要重绘标签,框架发送 QEvent::Paint 事件给标签
    • 事件被窗口拦截,进而调用其 eventFilter()
    • eventFilter() 中,调用 paintHigh()paintLow() 来真正在标签上绘制曲线
  • 相关阅读:
    100个Linux Shell脚本经典案例解析
    【附源码】计算机毕业设计SSM视频网站
    SparkCore系列-6、RDD 持久化
    零基础最简单方式学习Linux?
    oracle:记一次磁盘头故障处理
    【仿牛客网笔记】 Redis,一站式高性能存储方案——Redis入门
    Ribbon:负载均衡工具
    抖店token的生成和刷新的实际开发笔记
    ProcessDB实时/时序数据库——C/C++之数据订阅
    cv.dnn.NMSBoxes(bbox, confs, self.confThreshold, self.nmsThreshold)
  • 原文地址:https://blog.csdn.net/bili_mingwang/article/details/133940911