我们把绘制图形部分封装为一个组件,给父组件调用
将 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_OBJECT
public:
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
// 配置ECGWidget
m_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); // 发送信号
}
布局还要调整一下,后面有时间再倒腾吧~~~~~
运行效果?将就一下: