QPaint绘制自定义坐标轴组件00

本文详细介绍了如何在Qt中创建一个UI页面,修改背景颜色,并开发一个自定义的MyChart窗口小部件,使用继承自QWidget的类实现数据刷新和实时图表绘制。通过实例代码展示了如何在ChartTest应用中使用MyChart,以及如何在QTimer的帮助下模拟数据并更新图表显示。
摘要由CSDN通过智能技术生成

最终效果

1.创建一个ui页面,修改背景颜色

鼠标右键->改变样式表->添加颜色->background-color->选择合适的颜色->ok->Apply->ok

重新运行就可以看到widget的背景颜色已经改好

2.创建一个自定义的widget窗口小部件类,class MyChart : public QWidget

mychart.h

#ifndef MYCHART_H
#define MYCHART_H

#include <QWidget>
#include <QPainter>
#include <QString>

struct DataNode
{
    int value;
    QString key;
};

// MyChart继承自QWidget类,是一个窗口小部件。
class MyChart : public QWidget
{
    Q_OBJECT
public:
    // `explicit` 是 C++ 中的一个关键字,用于修饰类的构造函数,表示该构造函数只能用于显式地创建对象,不能被隐式地调用。
    // 只能通过MyChart painter = MyChart(parent)的方式显式地创建一个 `MyChart` 对象:
    // `parent` 参数的默认值为 `nullptr`,这表示如果没有提供父部件的指针,那么 `MyChart` 就没有父部件,即它是一个独立的窗口部件。
    explicit MyChart(QWidget *parent = nullptr);
    void updateValue(const DataNode &node);

protected:
    // `paintEvent(QPaintEvent *event)` 是一个事件处理函数,
    // 在 Qt 框架中,当需要重绘窗口部件时就会自动触发 `paintEvent(QPaintEvent *event)` 函数,
    // 以便开发者可以实现窗口部件的绘制逻辑,从而更新窗口的显示内容。
    // 在窗口需要进行重绘时,Qt 框架会自动调用 `MyChart` 对象的 `paintEvent(QPaintEvent *event)` 函数,从而实现图表的绘制更新。
    // 由于 `paintEvent` 函数是在需要重绘窗口部件时自动调用的,因此我们不需要手动调用它。
    // 当然,如果需要手动更新窗口部件的显示内容,
    // 也可以使用 `QWidget` 类中提供的 `update()` 函数或 `repaint()` 函数来触发 `paintEvent` 函数的调用,
    // 从而实现窗口的重绘。但通常情况下,Qt 框架会自动处理窗口部件的刷新和重绘。
    // `paintEvent` 函数是在 `QWidget` 类中定义的虚函数,
    // 它被设计为在窗口部件需要重新绘制时自动调用,以便让程序员有机会对窗口的内容进行绘制修改。
    // 在 `QWidget` 子类中,如果需要修改默认的绘制行为,则可以重写 `paintEvent` 函数来实现。
    void paintEvent(QPaintEvent *event);

private:
    int yMaxValue = 10;
    int maxNodeNum = 110;
    QList<DataNode> listDataNode;
};

#endif // MYCHART_H

 mychart.cpp

#include "mychart.h"

MyChart::MyChart(QWidget *parent) : QWidget(parent)
{

}

// 数据刷新
void MyChart::updateValue(const DataNode &node)
{
    // 如果当前列表中的数据节点数量已经达到了最大值 `maxNodeNum`,
    // 先删除队列头部的元素,即最早加入的元素(使用 `removeFirst()` 函数)。
    if(listDataNode.size() >= maxNodeNum) {
        listDataNode.removeFirst();
    }
    // 然后,将数据节点 `node` 添加到当前列表的末尾,使用 `append()` 函数实现。
    listDataNode.append(node);
    // 最后,将整个图表更新,调用 `update()` 函数。
    // `update()` 函数是用来触发 `paintEvent()` 函数的信号的。
    // 当窗口或控件需要更新或重绘自己时,它们会同时发射一个 `update()` 信号。
    // 这个信号会被 Qt 的事件循环机制捕获,最终调用 `paintEvent()` 函数进行绘图。
    // 因此,如果不调用 `update()` 函数,`paintEvent()` 函数就不会被调用,也就不会更新图表的显示内容。
    update();
}

// 图标绘制
// `paintEvent` 函数中的调用实际上是在继承关系中向上查找到的 `QWidget::paintEvent()` 函数的实现,
// 它在需要绘制更新时被自动触发。
// 在默认情况下,这个函数为空实现,因此需要我们手动重写它并自己实现绘图功能。
void MyChart::paintEvent(QPaintEvent *event)
{
    (void)event;
    // `QPainter` 是 Qt 中的一个绘图工具类,它封装了各种绘制函数和处理设备上下文的能力。
    // 通过使用 `QPainter` 类可以在窗口、部件和其它设备上上进行绘图操作。
    // 通过调用 `painter` 的各种绘制函数可以在空白的窗口部件上一步步画出你需要的复杂图形,包括直线、圆弧、多边形、文本等等。
    // `this` 关键字是指向当前对象的指针,即指向调用成员函数的对象的指针。
    // `this` 关键字可以用来访问对象的成员变量和成员函数,区分局部变量和成员变量。
    // `this` 指的是当前 `MyChart` 类型的对象,也就是指示当前需要绘制图表的部件对象。
    // 在这个函数中,我们通过将对象指针传给 `QPainter` 构造函数,来创建一个绘制器,使用它进行绘图操作。
    // 需要注意的是,`this` 关键字指向的是对象的指针,而不是类本身。
    // 所以说,`this` 不是用来区分类和对象的关键字,而是用来访问对象内部成员的工具。
    QPainter painter(this);
    // 启用抗锯齿功能,即让绘制的线条、边缘等对锯齿进行平滑处理,让图像更加平滑和自然。
    painter.setRenderHint(QPainter::Antialiasing);
    // `QPen` 是 Qt 中的一个画笔类,用于控制绘图时线条的样式、颜色和粗细等参数,通常与 `QPainter` 类一起使用。
    // 在默认情况下,`QPen` 对象的颜色为黑色,线宽为0,样式为实线。
    // 可以通过 `setBrush()`、`setColor()`、`setWidth()`、`setStyle()` 等函数来设置画笔的各个属性。
    QPen pen;
    pen.setWidth(2);
    pen.setColor(QColor(100, 200, 100));
    // setPen(pen)将创建的 `QPen` 画笔对象传入painter,就可以使用该笔刷来绘制线条、形状、文本等各种图形元素了。
    painter.setPen(pen);

    //坐标轴
    // 高度
    int yLength = this->height() * 0.9;
    // 长度
    int xLength = this->width();
    // `QPoint` 类是 Qt 中的一个点类,用于表示二维平面坐标系中的一个点,其具体坐标值由 `x()` 和 `y()` 成员函数获取。
    // `zero` 是一个 `QPoint` 类型的点,由横坐标`this->width() * 0.03`纵坐标`this->height() * 0.95` 两个数值组成,
    // 它代表了坐标系中的原点或者起始点,用来确定坐标轴的位置。
    QPoint zero(this->width() * 0.03, this->height() * 0.95);
    // 以下两行代码通常表示绘制一个基础的坐标系,绘制坐标系通常是绘制图表的第一步,是各种图表展示中的基础步骤之一。
    // 从 `zero` 点开始,向上绘制一条长度为 `yLength` 的水平线段表示y轴,并向右绘制一条长度为 `xLength` 的垂直线段表示x轴。
    // 这里使用了 `QPoint` 类型的构造函数创建起始点和结束点的对象。
    // y轴,原点zero,终点QPoint(zero.x() + xLength, zero.y())
    painter.drawLine(zero, QPoint(zero.x(), zero.y() - yLength));
    // x轴,原点zero,终点QPoint(zero.x() + xLength, zero.y())
    painter.drawLine(zero, QPoint(zero.x() + xLength, zero.y()));
    // 刻度间隔数
    int durationX = 100;
    int durationY = 10;
    // 每个刻度之间间隔的长度
    int xPeriod = xLength / durationX - 1;
    int yPeriod = yLength / durationY - 1;
    // 绘制坐标轴上的刻度和数字,用以标示坐标轴上每个刻度对应的数值
    // 遍历 y 轴的每个刻度位置,从起点 `zero` 开始向上连续绘制 `durationY` 个横向线段用于表示刻度。
    for (int i = 0; i <= durationY; ++i) {
        // 绘制表示y轴刻度的水平线段
        painter.drawLine(QPoint(zero.x() - 1, zero.y() - i * yPeriod), QPoint(zero.x() + 5, zero.y() - i * yPeriod));
        QString value = QString::number(i * 2);
        // 绘制刻度数值
        painter.drawText(QPoint(zero.x() - 25, zero.y() - i * yPeriod + 5), value);
    }
    for (int i = 0; i < durationX; ++i) {
        // 绘制表示x轴刻度的垂直线段
        painter.drawLine(QPoint(zero.x() + i * xPeriod, zero.y() + 3), QPoint(zero.x() + i * xPeriod, zero.y() - 5));
    }
    // 更新数据
    QList<QPoint> pointList;
    for(int i = 0; i < listDataNode.size(); i++) {
        DataNode node = listDataNode.at(i);
        QString key = node.key;
        int value = node.value;
        // 当前数据在x轴位置对应的刻度值
        int xOffset = zero.x() + i * xPeriod;
        // 当前数据在y轴位置对应的刻度值
        int yOffset = value * yLength / yMaxValue;
        // 像数据列表中添加数据转换后对应的坐标点
        pointList << QPoint(xOffset, zero.y() - yOffset);
        // 使用 `QTransform` 类对绘制坐标文本的位置和方向进行变换
        QTransform transform;
        // `translate()` 函数将文本的绘制起点平移 (`xOffset + 5`, `zero.y() - 7`) 的位置,
        // 即向右偏移5个像素,向上偏移7个像素,这是调试后比较合适的显示位置
        transform.translate(xOffset + 5, zero.y() - 7);
        // `rotate(-45)` 函数将文本沿顺时针方向旋转 45 度。
        transform.rotate(-45);
        // `setTransform()` 函数将 transform 对象设置为画笔对象 painter 的当前变换矩阵。
        painter.setTransform(transform);
        // `drawText()` 函数在变换后的位置绘制文本。
        painter.drawText(0, 5, key);
        // `resetTransform()` 函数将画笔对象的变换矩阵重置为原始状态。
        // 这个步骤很重要,如果不重置的话,下次绘制的文本会沿之前的变换矩阵进行绘制。
        painter.resetTransform();
    }
    // 折线线条宽度
    pen.setWidth(3);
    // 折现线条颜色
    pen.setColor(Qt::red);
    painter.setPen(pen);
    // 遍历并连接个数据节点,绘制折线
    for(int i = 0; i < pointList.size(); i++) {
        if((i+1) < pointList.size()) {
            // 连接个数据点,绘制折线
            painter.drawLine(pointList.at(i), pointList.at(i+1));
        }
    }
}

3.添加一个用于绘制自定义控件的控件,一般是Qwidget,修改QWidget的类属性,提升为自定义的类

提升类完成后qt designer显示当前组件已经是MyChart类

 重新编译运行后,原来的QWidget子窗口页面变成了自定义的Mychart页面

编写应用代码,应用自己编写的MyChart类实现数据刷新

charttest.h

#ifndef CHARTTEST_H
#define CHARTTEST_H

#include <QWidget>
#include <QTimer>
#include <QTime>
#include "mychart.h"

QT_BEGIN_NAMESPACE
namespace Ui { class ChartTest; }
QT_END_NAMESPACE

class ChartTest : public QWidget
{
    Q_OBJECT

public:
    ChartTest(QWidget *parent = nullptr);
    ~ChartTest();
    void initState();

private:
    Ui::ChartTest *ui;
    int index = 0;
    QTimer timer;   //定时器
};
#endif // CHARTTEST_H

charttest.cpp 

#include "charttest.h"
#include "ui_charttest.h"

ChartTest::ChartTest(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::ChartTest)
{
    ui->setupUi(this);
    initState();
}

ChartTest::~ChartTest()
{
    timer.stop();
    delete ui;
}

void ChartTest::initState()
{
    this->resize(1000, 400);
    connect(&timer, &QTimer::timeout, [=]()
    {
        // 模拟数据
        static int y = 1;
        if (y++ >= 9) {
            y = 1;
        }
        static int value = 0;
        DataNode node = {y, "ABC" + QString::number(value++)};
        // 刷新数据
        ui->widget->updateValue(node);
    });
    timer.start(50);
}

main.cpp 

#include "charttest.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    ChartTest w;
    w.show();
    return a.exec();
}

ChartTest.pro 

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    charttest.cpp \
    mychart.cpp

HEADERS += \
    charttest.h \
    mychart.h

FORMS += \
    charttest.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

编写晚代码后运行效果

 

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值