news 2026/6/25 15:52:36

手写一个基于Qt的轻量级示波器界面,附源码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手写一个基于Qt的轻量级示波器界面,附源码

大家好,今天分享一个轻量级类示波器UI界面,基于Qt+C/C++实现,可轻松处理数十万甚至百万数据点绘制而不卡顿。

因一个项目需要显示高速AD采集的仿真波形,采用普通QChart和QCustomPlot实现时,若前端采集速率拉满,UI界面瞬间卡成PPT。

还要支持缩放、游标测量等功能,用现成控件改起来比较麻烦。

所以我自己基于QPainter + RingBuffer封装了一个轻量级waveWidget。

在Demo中并不是将数据简单地画出来,而是实现了一个接近示波器交互体验的波形显示控件:

  • 支持实时刷新、滚动缓存

  • 普通滚轮:Y轴缩放

  • Ctrl + 滚轮:X轴缩放

  • X轴索引、Y轴数值实时显示

  • Marker测量 ΔX / ΔY

  • 双击窗口放大、缩小功能

  • 支持右键菜单操作,可添加自定义功能

话不多说,先上图看效果:

主界面实时显示(可同时支持多通道)

普通滚轮:Y轴缩放

Ctrl + 滚轮:X轴缩放

双击放大缩小选中窗口

标记测量 ΔX / ΔY

支持右键菜单操作,可添加自定义功能

下面简单讲述下部分实现代码,文章底部提供完整实现源码,可直接拷贝到项目工程使用。

waveWidget继承自QWidget,整个控件可以分成三层:数据层(Buffer)、渲染层(Painter)、交互层(Mouse Event),这三个层次分离得比较清楚,后续扩展会很方便。

在数据处理时抛弃传统的 vector.push_back(),采用环形缓冲区设计,当缓冲区未满时正常写入,缓冲区满则自动覆盖最旧数据。

最大占用内存固定化,写入复杂度 O(1),避免大数据量下性能下降,非常适合高频采样场景。

数据写入显示逻辑:

void WaveWidget::appendData(float value) { m_buffer[m_writePos] = value; m_writePos = (m_writePos + 1) % m_capacity; if (m_count < m_capacity) m_count++; update(); }

主体绘制分为四层:背景、网格、波形、交互元素,比全部塞进 paintEvent 可维护高很多。

drawGrid(p); drawWave(p); drawMarkers(p); drawCursor(p);

对于底层的FPGA研发或信号处理工程师来说,光能看到波形是远远不够的,必须能精确地测量出时序。为此,还重写了鼠标与滚轮事件。

动态缩放:在 wheelEvent 中,监听修饰键,按下Ctrl + 滚轮,修改 m_viewSizeX 进行 X 轴时间基准缩放。纯滚轮则修改 m_yScale 控制 Y 轴幅度比例。所有的缩放都是通过纯数学映射完成,不涉及任何底层数据的重新拷贝。

void WaveWidget::wheelEvent(QWheelEvent *e) { if (e->modifiers() & Qt::ControlModifier) { setXView(m_viewSizeX * ((e->angleDelta().y() > 0) ? 0.8 : 1.25)); } else { setYScale(m_yScale * ((e->angleDelta().y() > 0) ? 1.1 : 0.9)); } }

右键工程菜单与卡尺测量:通过重写 contextMenuEvent ,组件支持呼出原生右键菜单,可以随时在波形上打下两根 X 轴(黄色)或 Y 轴(青色)标记线。

void WaveWidget::contextMenuEvent(QContextMenuEvent *e) { if (!m_contextMenu) { m_contextMenu = new QMenu(this); m_contextMenu->addAction("添加 X 标记", [this]() { if(m_markX.size()>=2) m_markX.clear(); m_markX.append(m_selectedIndex); update(); }); m_contextMenu->addAction("添加 Y 标记", [this]() { if(m_markY.size()>=2) m_markY.clear(); m_markY.append((height()/2.0 - m_mousePos.y())/m_yScale); update(); }); m_contextMenu->addAction("清除标记", [this](){ m_markX.clear(); m_markY.clear(); update(); }); } m_contextMenu->exec(e->globalPos()); }

实时差值计算:在drawMarkers函数中,代码会自动执行 abs( m_markX[1] - m_markX[0]) 计算出X轴的采样点差值,以及利用 fabs(m_markY[1] - m_markY[0])计算出Y轴的幅度差值,并高亮显示在左上角。

void WaveWidget::drawMarkers(QPainter &p) { double xStep = (double)width() / (m_viewSizeX - 1); p.setPen(QPen(Qt::yellow, 1)); for (int lx : m_markX) { double px = width() - (m_count - 1 - lx) * xStep; p.drawLine(px, 0, px, height()); } p.setPen(QPen(Qt::cyan, 1)); for (double vy : m_markY) { double py = height() / 2.0 - vy * m_yScale; p.drawLine(0, py, width(), py); } // 绘制结果文字 p.setPen(Qt::white); if (m_markX.size() == 2) p.drawText(10, 20, QString("ΔX: %1").arg(abs(m_markX[1] - m_markX[0]))); if (m_markY.size() == 2) p.drawText(10, 40, QString("ΔY: %1").arg(fabs(m_markY[1] - m_markY[0]), 0, 'f', 3)); }

双击处理:重载mouseDouble ClickEvent(),当双击时发送信号,把业务逻辑交给外部处理。

void WaveWidget::mouseDoubleClickEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { emit doubleClicked(); // 发出双击信号 } QWidget::mouseDoubleClickEvent(e); }

很多时候,真正好用的工具,不一定复杂,但一定贴近实际需求。在工业级的测控系统与高速数据采集中,性能的瓶颈往往就隐藏在一次不经意的深拷贝,或者一次冗余的屏幕擦除中。掌握了底层 UI 渲染的机制,即使面对 GB/s 的吞吐数据,也能稳如泰山。

嵌入式软硬件系统

专注于嵌入式软硬件相关经验分享、工程实践与技术探索,涵盖单片机、FPGA、PCIe、驱动开发、上位机软件以及测控系统设计等多方面内容。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/25 15:40:42

零基础考公务员,先学行测还是先学申论?

零基础考公务员最容易纠结的一个问题是&#xff1a;到底先学行测&#xff0c;还是先学申论&#xff1f; 如果只看表面&#xff0c;行测是选择题&#xff0c;好像更容易开始&#xff1b;申论要读材料、写答案、写文章&#xff0c;看起来更难&#xff0c;所以很多新手会把大部分时…

作者头像 李华
网站建设 2026/6/25 15:40:19

高效智能一键解放双手:原神自动化脚本全方位使用指南

高效智能一键解放双手&#xff1a;原神自动化脚本全方位使用指南 【免费下载链接】genshin-impact-script 原神脚本&#xff0c;包含自动钓鱼、自动拾取、自动跳过对话等多项实用功能。A Genshin Impact script includes many useful features such as automatic fishing, auto…

作者头像 李华
网站建设 2026/6/25 15:39:07

JDK 9 的 PlatformClassLoader 只是简单改个名吗?

都知道JDK9之后将扩展类加载器&#xff08;ExtensionClassLoader&#xff09;重命名为平台类加载器&#xff08;PlatformClassLoader&#xff09;&#xff0c;难道只是简单的重命名吗&#xff1f;都有什么变化&#xff1f;带来的变化1.双亲委派模型的改变JDK8&#xff1a;双亲委…

作者头像 李华
网站建设 2026/6/25 15:38:27

【AUV 三维规划】基于地形崎岖膨化 + 精英遗传算法的崎岖海底全覆盖路径规划(完整 Matlab 源码 + 多水下工程案例)

目录 摘要 一、研究前言与水下三维规划核心痛点 1.1 AUV深海作业应用场景与硬件约束 1.2 现有崎岖海底规划六大技术短板 1.3 全文独立核心创新点(与USV水面算法零关联) 二、崎岖海底三维环境全域建模理论 2.1 深海三维栅格高程建模原理 2.2 海底地形崎岖度量化分级计…

作者头像 李华
网站建设 2026/6/25 15:33:21

Coder:自托管云开发环境,让AI代理在你的服务器上写代码

文章目录Coder&#xff1a;自托管云开发环境&#xff0c;让AI代理在你的服务器上写代码核心卖点&#xff1a;Terraform 定义开发环境成本控制&#xff1a;自动关机AI 代理&#xff1a;重头戏能跑在哪里&#xff1f;和 GitHub Codespaces 比怎么样&#xff1f;实际体验不足之处我…

作者头像 李华
网站建设 2026/6/25 15:27:58

AI 能合法“二创“周星驰经典了?聊聊 Seedance 2.5 背后的版权新玩法

昨天&#xff08;6 月 23 日&#xff09;刷到一条挺炸的消息&#xff1a;以后你能用 AI 合法地二创周星驰的经典片段了。字节在火山引擎 FORCE 原动力大会上&#xff0c;把这事落了地。 先说清楚这是个什么事 大会上字节发了新一代视频模型 Seedance 2.5&#xff08;单段 30 秒…

作者头像 李华