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

news2025/3/15 20:47:14

最终效果

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

编写晚代码后运行效果

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1453231.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第五节笔记:LMDeploy 大模型量化部署实践

大模型部署背景 参数用FP16半精度也就是2字节&#xff0c;7B的模型就大约占14G 2.LMDeploy简介 量化降低显存需求量&#xff0c;提高推理速度 大语言模型推理是典型的访问密集型&#xff0c;因为是decoder only的架构&#xff0c;需要token by token的生成&#xff0c;因…

【2024】如何订阅Netflix奈飞?Netflix奈飞购买教程

【2024】如何订阅Netflix奈飞&#xff1f;Netflix奈飞购买教程 Netflix奈飞作为全球领先的在线流媒体平台之一&#xff0c;拥有丰富的影视资源和独家内容&#xff0c;成为了人们追剧的热门选择。本文将为您介绍如何订阅Netflix奈飞&#xff0c;并提供详细的购买教程&#xff0…

String讲解

文章目录 String类的重要性常用的方法常用的构造方法String类的比较字符串的查找转化数字转化为字符串字符串转数字 字符串替换字符串的不可变性 字符串拆分字符串截取字符串修改 StringBuilder和StringBuffer String类的重要性 在c/c的学习中我们接触到了字符串&#xff0c;但…

C++模板详解 —— 函数模板与类模板

C模板详解 泛型编程函数模板函数模板的概念函数模板的原理 函数模板的实例化函数模板的匹配原则类模板类模板的定义格式类模板的实例化 泛型编程 如果让你编写一个函数&#xff0c;用于两个数的交换。在C语言中&#xff0c;我们会用如下方法&#xff1a; void Swapi(int* p1,…

关于DVWA靶场Could not connect to the database service的几种解决办法

总的来说这个问题都是 config 配置文件没有修改正确 一般修改数据库的用户名和密码与 phpstudy 一致并且添加了 key 就能初始化成功的 但是我还遇到过另一种情况&#xff0c;修改了上面的东西依旧无法连接到数据库 Could not connect to the database service. Please check …

大数据01-导论

零、文章目录 大数据01-导论 1、数据与数据分析 **数据&#xff1a;是事实或观察的结果&#xff0c;是对客观事物的逻辑归纳&#xff0c;是用于表示客观事物的未经加工的原始素材。**数据可以是连续的值&#xff0c;比如声音、图像&#xff0c;称为模拟数据&#xff1b;也可…

PWM驱动直流电机

一、知识补充; 低频时有蜂鸣器响声&#xff0c;加大PWM频率&#xff0c;超出人耳范围就可以听不到&#xff0c;20Hz~20kHz 加大频率-->减小预分频器&#xff0c;从720-->36现在频率就是20kHz这样不会影响占空比&#xff1f; 二、接线图 三、代码分析 main,c #include…

正确看待OpenAI大模型Sora

2月16日凌晨&#xff0c;OpenAI发布了文生视频模型Sora。官方是这样描述的&#xff1a;Sora is an AI model that can create realistic and imaginative scenes from text instructions.Sora一个人工智能模型&#xff0c;它可以根据文本指令创建逼真和富有想象力的场景。Sora…

云计算基础 -NUMA

UMA UMA中文翻译叫&#xff1a;一致性内存访问 多个CPU通过同一根前端总线&#xff08;FSB&#xff09;来访问内存&#xff08;所有的内存访问都需要通过北桥芯片来完成&#xff09;&#xff0c;若多个CPU访问内存的不同内存单元还是相同内存单元&#xff0c;同一时刻&#x…

跟着pink老师前端入门教程(JavaScript)-day03

四、数据类型 &#xff08;一&#xff09;数据类型简介 1、为什么需要数据类型 在计算机中&#xff0c;不同的数据所需占用的存储空间是不同的&#xff0c;为了便于把数据分成所需内存大小不同的数据&#xff0c;充分利用存储空间&#xff0c;于是定义了不同的数据类型。 …

SpringBoot+vue2联合打包部署,混合打包部署

SpringBootvue2联合部署&#xff0c;混合部署 前端工程和后端工程目前是都是相对独立性的模式进行开发的。 打包机 只拥有maven&#xff0c;没有nodejs 软件工程场景&#xff1a; 前后端工程在同一个父工程下面&#xff0c;作为一个子工程存在&#xff0c;各自独立开发。前…

Mysql知识点汇总

Mysql知识点汇总 1. Mysql基本场景的简单语句。2. Mysql的增删改查&#xff0c;统计表中的成绩最好的两个同学的名字&#xff0c;年级等。3&#xff1a;请使用多种方法查询每个学生的每门课分数>80的学生姓名4、order by&#xff0c;group by&#xff0c;子查询4.1、having和…

政安晨:【示例演绎】【Python】【Numpy数据处理】快速入门(一)

简介 NumPy是SciPy家族的成员之一。 SciPy家族是一个专门应用于数学、科学和工程领域的开源Python生态圈&#xff0c;或者说是一个由多个Python库组成的集合&#xff0c;用于解决科学计算中的各种问题。这些库构成了一个功能强大的科学计算工具箱&#xff0c;可以进行数值计算…

Paper - CombFold: predicting structures of large protein assemblies 论文简读

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/136143199 CombFold: predicting structures of large protein assemblies using a combinatorial assembly algorithm and AlphaFold2 CombFold…

【论文精读】DALL·E

摘要 本文利用从互联网上收集的2.5亿个图像/文本对数据&#xff0c;训练了一个120亿参数的自回归transformer&#xff0c;进而得到一个可以通过自然语言/图像控制生成的高保真图像生成模型。在大多数数据集上的表现超越以往的方法。 框架 本文的目标为通过训练一个自回归trans…

mysql调优实战

EXPLAIN执行分析 id:值越大越先执行相同时&#xff0c;由上向下执行。 possible_key: 可能走索引的键。 key&#xff1a;真正走索引的键rows:根据表统计信息及索引选用情况&#xff0c;大致估算出找到所需的记录所需要读取的行数&#xff0c;也就是说&#xff0c;用的越少越好 …

004 - Hugo, 分类

004 - Hugo, 分类content文件夹 004 - Hugo, 分类 content文件夹 ├─.obsidian ├─categories │ ├─Python │ └─Test ├─page │ ├─about │ ├─archives │ ├─links │ └─search └─post├─chinese-test├─emoji-support├─Git教程├─Hugo分类├─…

如何在CSS中实现背景图片的渐变?

--引言 在CSS中&#xff0c;实现背景图片的渐变通常需要使用linear-gradient或者radial-gradient函数&#xff0c;这些函数可以与背景图像一起使用来创建渐变效果。然而&#xff0c;CSS的渐变并不直接支持使用图像作为渐变的颜色停止点。但你可以通过一些技巧来实现类似的效果…

2024年【高处安装、维护、拆除】模拟考试题库及高处安装、维护、拆除实操考试视频

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 高处安装、维护、拆除模拟考试题库是安全生产模拟考试一点通生成的&#xff0c;高处安装、维护、拆除证模拟考试题库是根据高处安装、维护、拆除最新版教材汇编出高处安装、维护、拆除仿真模拟考试。2024年【高处安装…

得物面试:Redis用哈希槽,而不是一致性哈希,为什么?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; Redis为何用哈希槽而不用一致性哈希&#xff1f; 最近…