创建显示心电图的组件
我们把绘制图形部分封装为一个组件,给父组件调用
将 ECGWidget
作为独立组件并不一定需要将其注册为 Qt 的自定义插件或使用 “提升法”。这两种方式通常用于更复杂的场景,而对于大多数内部使用的组件,简单的类继承就足够了。
#ifndef ECGWidget_H
#define ECGWidget_H#include <QWidget>
#include <QJsonArray>
#include "heartdata.h"class ECGWidget : public QWidget
{Q_OBJECT
public:explicit ECGWidget(QWidget *parent = nullptr);// 设置历史数据void setHistoryData(HeartData **channelData);// 更新实时数据void updateRealtimeData(const QList<QByteArray>& newData);// 设置网格密度void setGridDensity(double density) { m_dots = density; }protected:void paintEvent(QPaintEvent *event) override;private:void drawECGGrid(QPainter &painter);void drawECGWave(QPainter &painter, bool isRealtime = false);HeartData *m_channelData[12] = { nullptr }; // 12通道数据QList<QByteArray> m_newData; // 实时数据bool m_hasRealtimeData = false; // 是否有实时数据标志double m_dots = 5.0; // 网格密度(默认5px)
};#endif // ECGWidget_H
#include "ecgwidget.h"
#include <QPainter>
#include <QFont>ECGWidget::ECGWidget(QWidget *parent): QWidget(parent)
{// 初始化背景色setStyleSheet("background-color: white;");
}void ECGWidget::setHistoryData(HeartData **channelData)
{// 复制通道数据for(int i = 0; i < 12; i++) {if(m_channelData[i]) delete m_channelData[i];m_channelData[i] = new HeartData(*channelData[i]);}update();
}void ECGWidget::updateRealtimeData(const QList<QByteArray>& newData)
{m_newData = newData;m_hasRealtimeData = true;// 更新通道数据for(int i = 0; i < qMin(12, m_newData.size()); i++) {if(m_channelData[i]) {QString data = QString::fromLocal8Bit(m_newData[i]);m_channelData[i]->addData(data.toInt());}}update();
}void ECGWidget::paintEvent(QPaintEvent *event)
{Q_UNUSED(event);QPainter painter(this);// 绘制网格drawECGGrid(painter);// 绘制波形drawECGWave(painter, m_hasRealtimeData);// 绘制标题painter.save();QFont f("Microsoft YaHei", 9, QFont::Bold);painter.setFont(f);painter.setPen(QColor(0, 102, 227));painter.drawText(10, 15, "Channel:12 Speed:25mm/s");painter.restore();
}void ECGWidget::drawECGGrid(QPainter &painter)
{int w = width();int h = height();painter.save();painter.setPen(QColor(247, 250, 250));// 绘制小网格for(int x = 0; x <= w; x += m_dots) {painter.drawLine(x, 0, x, h);}for(int y = 0; y <= h; y += m_dots) {painter.drawLine(0, y, w, y);}// 绘制大网格(5x5小格)painter.setPen(QColor(206, 224, 229));for(int x = 0; x <= w; x += m_dots * 5) {painter.drawLine(x, 0, x, h);}for(int y = 0; y <= h; y += m_dots * 5) {painter.drawLine(0, y, w, y);}painter.restore();
}void ECGWidget::drawECGWave(QPainter &painter, bool isRealtime)
{int w = width();int h = height();int cols = 2;int rows = 6;int rectWidth = w / cols;int rowHeight = h / rows;int middleHeight = rowHeight / 2;// 计算缩放比例 (1mV = 10mm = 10 * dots)float scale = 0.001 * (10 * m_dots); // 转换为像素比例painter.save();painter.setPen(QColor(62, 168, 115));for (int col = 0; col < cols; col++) {for (int row = 0; row < rows; row++) {int index = col * rows + row;if(index >= 12 || !m_channelData[index]) continue;// 获取通道数据QString name = m_channelData[index]->getChannelName();QJsonArray data = m_channelData[index]->getDataArr();// 绘制通道名称painter.drawText(col * rectWidth, row * rowHeight + middleHeight + 25, name);// 绘制波形QVector<QPointF> points;for (int i = 0; i < data.size(); i++) {if (i > rectWidth - m_dots * 2) break;float yPos = row * rowHeight + middleHeight - data[i].toInt() * scale + 25;points.append(QPointF(col * rectWidth + i, yPos));}painter.drawPolyline(points.constData(), points.size());}}painter.restore();
}
在 ecg 类中集成 ECGWidget
ecg
类负责数据管理、串口通信等业务逻辑,并将数据传递给 ECGWidget
:
// ecg.h
#ifndef ECG_H
#define ECG_H#include <QWidget>
#include <QJsonArray>
#include "heartdata.h"
#include "serialtool.h"
#include "ecgwidget.h" // 包含ECGWidget头文件namespace Ui {
class ecg;
}class ecg : public QWidget
{Q_OBJECTpublic:explicit ecg(QWidget *parent = nullptr);~ecg();void ReadECGFile(QString fileName);void getHistoryData();private slots:void on_closeButton_clicked();void receiveData(); // 串口数据接收槽函数private:Ui::ecg *ui;ECGWidget *m_ecgWidget; // ECG绘图组件QJsonArray m_dataArrs;HeartData *m_channelData[12];SerialTool *m_serial;QList<QByteArray> m_newdata;bool m_serialflag;
};#endif // ECG_H
3. 在 ecg 类中初始化和使用 ECGWidget
// ecg.cpp
#include "ecg.h"
#include "ui_ecg.h"
#include "userdata.h"ecg::ecg(QWidget *parent) :QWidget(parent),ui(new Ui::ecg)
{ui->setupUi(this);// 创建ECGWidget实例并添加到布局中m_ecgWidget = new ECGWidget(this);ui->verticalLayout->addWidget(m_ecgWidget); // 假设UI中有一个verticalLayout// 配置ECGWidgetm_ecgWidget->setGridDensity(5.0);m_ecgWidget->setWaveColor(QColor(62, 168, 115));m_ecgWidget->setChannelLayout(2, 6); // 2列6行布局// 初始化其他组件...ReadECGFile(":/resource/hisdata.txt");serialPortInit();
}// 从文件读取历史数据并传递给ECGWidget
void ecg::ReadECGFile(QString fileName)
{// ... 原有代码 ...getHistoryData();m_ecgWidget->setHistoryData(m_channelData); // 传递数据到ECGWidget
}// 处理串口接收的数据并更新ECGWidget
void ecg::receiveData()
{QByteArray message = m_serial->m_serialport->readAll();m_newdata = message.split(',');m_serialflag = true;// 更新ECGWidget的实时数据m_ecgWidget->updateRealtimeData(m_newdata);
}
4. 信号槽机制优化(可选但推荐)
为了进一步解耦,可以在 ecg
类中添加信号,让 ECGWidget
通过连接信号来接收数据:
// ecg.h
signals:void newECGDataAvailable(const QList<QByteArray>& data);// ecg.cpp
ecg::ecg(QWidget *parent) : QWidget(parent), ui(new Ui::ecg)
{// ... 其他初始化 ...// 连接信号槽connect(this, &ecg::newECGDataAvailable, m_ecgWidget, &ECGWidget::updateRealtimeData);
}void ecg::receiveData()
{// ... 接收数据 ...emit newECGDataAvailable(m_newdata); // 发送信号
}
布局还要调整一下,后面有时间再倒腾吧~~~~~
运行效果?将就一下: