大家好今天分享一个轻量级类示波器UI界面基于QtC/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、驱动开发、上位机软件以及测控系统设计等多方面内容。