本文章记录在C++自定义控件,在qml文件中使用该控件流程。注意使用QT5.12版本
一、自定义控件
cpp
/*/src/model/DataGroups.h*/
#ifndef DATAGROUPS_H
#define DATAGROUPS_H
#include <QObject>
// ═══════════════════════════════════════════════════════════════
// 宏:批量 setter --- 仅在值变化时赋值 + emit 信号
// 用于减少数百个 setter 的样板代码
// ═══════════════════════════════════════════════════════════════
#define BATCH_SETTER(Klass, Type, setterName, member, signal) \
void Klass::setterName(const Type &v) { \
if (member != v) { member = v; emit signal(); } \
}
// ═══════════════════════════════════════════════════════════════
// PowertrainGroup --- 动力总成数据(发动机、变速箱等)
// 典型 CAN 帧: EEC1, EEC2, ETC1, ...
// ═══════════════════════════════════════════════════════════════
class PowertrainGroup : public QObject {
Q_OBJECT
Q_PROPERTY(int engine READ engine NOTIFY engineChanged)
Q_PROPERTY(int speed READ speed NOTIFY speedChanged)
Q_PROPERTY(int currentGear READ currentGear NOTIFY currentGearChanged)
Q_PROPERTY(int waterTemperature READ waterTemperature NOTIFY waterTemperatureChanged)
Q_PROPERTY(int engineOilPressure READ engineOilPressure NOTIFY engineOilPressureChanged)
Q_PROPERTY(int acceleratorPedal READ acceleratorPedal NOTIFY acceleratorPedalChanged)
Q_PROPERTY(int retarderValue READ retarderValue NOTIFY retarderValueChanged)
Q_PROPERTY(int voltage READ voltage NOTIFY voltageChanged)
public:
explicit PowertrainGroup(QObject *parent = nullptr) : QObject(parent) {}
// ── 批量更新(一次调用,内部仅对变化值 emit 信号)─────────
struct Batch {
int engine = -1; // -1 表示不更新此字段
int speed = -1;
int currentGear = -1;
int waterTemperature = -1;
int engineOilPressure = -1;
int acceleratorPedal = -1;
int retarderValue = -1;
int voltage = -1;
};
Q_INVOKABLE void applyBatch(const Batch &b) {
if (b.engine >= 0) setEngine(b.engine);
if (b.speed >= 0) setSpeed(b.speed);
if (b.currentGear >= 0) setCurrentGear(b.currentGear);
if (b.waterTemperature >= 0) setWaterTemperature(b.waterTemperature);
if (b.engineOilPressure >= 0) setEngineOilPressure(b.engineOilPressure);
if (b.acceleratorPedal >= 0) setAcceleratorPedal(b.acceleratorPedal);
if (b.retarderValue >= 0) setRetarderValue(b.retarderValue);
if (b.voltage >= 0) setVoltage(b.voltage);
}
// ── getters ─────────────────────────────────────────
int engine() const { return m_engine; }
int speed() const { return m_speed; }
int currentGear() const { return m_currentGear; }
int waterTemperature() const { return m_waterTemperature; }
int engineOilPressure() const { return m_engineOilPressure; }
int acceleratorPedal() const { return m_acceleratorPedal; }
int retarderValue() const { return m_retarderValue; }
int voltage() const { return m_voltage; }
signals:
void engineChanged();
void speedChanged();
void currentGearChanged();
void waterTemperatureChanged();
void engineOilPressureChanged();
void acceleratorPedalChanged();
void retarderValueChanged();
void voltageChanged();
private:
// ── setters (仅被 applyBatch 调用) ─────────────────
void setEngine(int v) { if (m_engine != v) { m_engine = v; emit engineChanged(); } }
void setSpeed(int v) { if (m_speed != v) { m_speed = v; emit speedChanged(); } }
void setCurrentGear(int v) { if (m_currentGear != v) { m_currentGear = v; emit currentGearChanged(); } }
void setWaterTemperature(int v) { if (m_waterTemperature != v) { m_waterTemperature = v; emit waterTemperatureChanged(); } }
void setEngineOilPressure(int v) { if (m_engineOilPressure != v) { m_engineOilPressure = v; emit engineOilPressureChanged(); } }
void setAcceleratorPedal(int v) { if (m_acceleratorPedal != v) { m_acceleratorPedal = v; emit acceleratorPedalChanged(); } }
void setRetarderValue(int v) { if (m_retarderValue != v) { m_retarderValue = v; emit retarderValueChanged(); } }
void setVoltage(int v) { if (m_voltage != v) { m_voltage = v; emit voltageChanged(); } }
int m_engine = 0;
int m_speed = 0;
int m_currentGear = 0;
int m_waterTemperature = 0;
int m_engineOilPressure = 0;
int m_acceleratorPedal = 0;
int m_retarderValue = 0;
int m_voltage = 0;
};
// ═══════════════════════════════════════════════════════════════
// LightsGroup --- 灯光信号数据
// 典型 CAN 帧: LAMP1, ...
// ═══════════════════════════════════════════════════════════════
class LightsGroup : public QObject {
Q_OBJECT
Q_PROPERTY(int turnLeft READ turnLeft NOTIFY turnLeftChanged)
Q_PROPERTY(int turnRight READ turnRight NOTIFY turnRightChanged)
Q_PROPERTY(int highBeam READ highBeam NOTIFY highBeamChanged)
Q_PROPERTY(int markLight READ markLight NOTIFY markLightChanged)
Q_PROPERTY(int frontFog READ frontFog NOTIFY frontFogChanged)
Q_PROPERTY(int rearFog READ rearFog NOTIFY rearFogChanged)
Q_PROPERTY(int parkGreen READ parkGreen NOTIFY parkGreenChanged)
Q_PROPERTY(int parkRed READ parkRed NOTIFY parkRedChanged)
Q_PROPERTY(int trailerLeft READ trailerLeft NOTIFY trailerLeftChanged)
Q_PROPERTY(int trailerRight READ trailerRight NOTIFY trailerRightChanged)
public:
explicit LightsGroup(QObject *parent = nullptr) : QObject(parent) {}
struct Batch {
int turnLeft = -1;
int turnRight = -1;
int highBeam = -1;
int markLight = -1;
int frontFog = -1;
int rearFog = -1;
int parkGreen = -1;
int parkRed = -1;
int trailerLeft = -1;
int trailerRight = -1;
};
Q_INVOKABLE void applyBatch(const Batch &b) {
if (b.turnLeft >= 0) setTurnLeft(b.turnLeft);
if (b.turnRight >= 0) setTurnRight(b.turnRight);
if (b.highBeam >= 0) setHighBeam(b.highBeam);
if (b.markLight >= 0) setMarkLight(b.markLight);
if (b.frontFog >= 0) setFrontFog(b.frontFog);
if (b.rearFog >= 0) setRearFog(b.rearFog);
if (b.parkGreen >= 0) setParkGreen(b.parkGreen);
if (b.parkRed >= 0) setParkRed(b.parkRed);
if (b.trailerLeft >= 0) setTrailerLeft(b.trailerLeft);
if (b.trailerRight >= 0) setTrailerRight(b.trailerRight);
}
int turnLeft() const { return m_turnLeft; }
int turnRight() const { return m_turnRight; }
int highBeam() const { return m_highBeam; }
int markLight() const { return m_markLight; }
int frontFog() const { return m_frontFog; }
int rearFog() const { return m_rearFog; }
int parkGreen() const { return m_parkGreen; }
int parkRed() const { return m_parkRed; }
int trailerLeft() const { return m_trailerLeft; }
int trailerRight() const { return m_trailerRight; }
signals:
void turnLeftChanged();
void turnRightChanged();
void highBeamChanged();
void markLightChanged();
void frontFogChanged();
void rearFogChanged();
void parkGreenChanged();
void parkRedChanged();
void trailerLeftChanged();
void trailerRightChanged();
private:
void setTurnLeft(int v) { if (m_turnLeft != v) { m_turnLeft = v; emit turnLeftChanged(); } }
void setTurnRight(int v) { if (m_turnRight != v) { m_turnRight = v; emit turnRightChanged(); } }
void setHighBeam(int v) { if (m_highBeam != v) { m_highBeam = v; emit highBeamChanged(); } }
void setMarkLight(int v) { if (m_markLight != v) { m_markLight = v; emit markLightChanged(); } }
void setFrontFog(int v) { if (m_frontFog != v) { m_frontFog = v; emit frontFogChanged(); } }
void setRearFog(int v) { if (m_rearFog != v) { m_rearFog = v; emit rearFogChanged(); } }
void setParkGreen(int v) { if (m_parkGreen != v) { m_parkGreen = v; emit parkGreenChanged(); } }
void setParkRed(int v) { if (m_parkRed != v) { m_parkRed = v; emit parkRedChanged(); } }
void setTrailerLeft(int v) { if (m_trailerLeft != v) { m_trailerLeft = v; emit trailerLeftChanged(); } }
void setTrailerRight(int v) { if (m_trailerRight != v) { m_trailerRight = v; emit trailerRightChanged(); } }
int m_turnLeft = 0;
int m_turnRight = 0;
int m_highBeam = 0;
int m_markLight = 0;
int m_frontFog = 0;
int m_rearFog = 0;
int m_parkGreen = 0;
int m_parkRed = 0;
int m_trailerLeft = 0;
int m_trailerRight = 0;
};
// ═══════════════════════════════════════════════════════════════
// VehicleDataStore --- 数据总入口
// 包含所有数据组,负责跨线程接收 FrameBatch 并分发给各组
// ═══════════════════════════════════════════════════════════════
class VehicleDataStore : public QObject {
Q_OBJECT
Q_PROPERTY(PowertrainGroup* powertrain READ powertrain CONSTANT)
Q_PROPERTY(LightsGroup* lights READ lights CONSTANT)
public:
explicit VehicleDataStore(QObject *parent = nullptr)
: QObject(parent)
{
m_powertrain = new PowertrainGroup(this);
m_lights = new LightsGroup(this);
}
PowertrainGroup* powertrain() const { return m_powertrain; }
LightsGroup* lights() const { return m_lights; }
/// 批量入口:工作线程一次调用,主线程内分发给各组
Q_INVOKABLE void applyFrame(const PowertrainGroup::Batch &pt,
const LightsGroup::Batch <)
{
m_powertrain->applyBatch(pt);
m_lights->applyBatch(lt);
}
private:
PowertrainGroup *m_powertrain;
LightsGroup *m_lights;
};
// 注册 struct 到 Qt 元类型系统 --- 支持跨线程 QueuedConnection
Q_DECLARE_METATYPE(PowertrainGroup::Batch)
Q_DECLARE_METATYPE(LightsGroup::Batch)
#endif // DATAGROUPS_H
1、Q_DECLARE_METATYPE(PowertrainGroup::Batch)


cpp
/*/src/render/GaugeItem.h*/
#ifndef GAUGEITEM_H
#define GAUGEITEM_H
#include <QQuickPaintedItem>
/// 高性能模拟仪表控件 --- 用 C++ QPainter 直接绘制,绕过 QML 绑定系统
///
/// 为什么用 QQuickPaintedItem(Qt5) 而非 QML Rectangle/Canvas:
/// 1. 绘制在 C++ 侧完成,无 JS/QML 绑定开销
/// 2. scene graph 自动合并 update() 请求,避免重复绘制
/// 3. 适合 30~60fps 高频更新的仪表盘(转速表、车速表等)
///
/// 使用方式(QML):
/// GaugeItem {
/// value: powertrain.engine // 绑定到数据源
/// maxValue: 3000
/// minValue: 0
/// unit: "rpm"
/// }
///
/// 对于要求极致性能的场景(60fps+),可改为 QQuickItem + updatePaintNode()(Qt6)
/// 直接操作 QSGNode,完全绕过 QPainter → QSG 转换开销。
class GaugeItem : public QQuickPaintedItem {
Q_OBJECT
Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged)
Q_PROPERTY(qreal minValue READ minValue WRITE setMinValue NOTIFY minValueChanged)
Q_PROPERTY(qreal maxValue READ maxValue WRITE setMaxValue NOTIFY maxValueChanged)
Q_PROPERTY(QString unit READ unit WRITE setUnit NOTIFY unitChanged)
Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
public:
explicit GaugeItem(QQuickItem *parent = nullptr);
qreal value() const { return m_value; }
qreal minValue() const { return m_minValue; }
qreal maxValue() const { return m_maxValue; }
QString unit() const { return m_unit; }
QString label() const { return m_label; }
void setValue(qreal v);
void setMinValue(qreal v);
void setMaxValue(qreal v);
void setUnit(const QString &s);
void setLabel(const QString &s);
void paint(QPainter *painter) override;
signals:
void valueChanged();
void minValueChanged();
void maxValueChanged();
void unitChanged();
void labelChanged();
private:
void drawGauge(QPainter *p, const QRectF &r);
qreal m_value = 0;
qreal m_minValue = 0;
qreal m_maxValue = 3000;
QString m_unit = "rpm";
QString m_label = "Engine";
};
#endif // GAUGEITEM_H
cpp
/*/src/render/GaugeItem.c++*/
#include "GaugeItem.h"
#include <QPainter>
#include <QtMath>
#include <QFont>
GaugeItem::GaugeItem(QQuickItem *parent)
: QQuickPaintedItem(parent)
{
// 高性能渲染设置
setRenderTarget(QQuickPaintedItem::FramebufferObject); // FBO 模式,GPU 合成
setAntialiasing(true);
setPerformanceHint(QQuickPaintedItem::FastFBOResizing);
}
void GaugeItem::setValue(qreal v)
{
if (!qFuzzyCompare(m_value, v)) {
m_value = v;
emit valueChanged();
update(); // 触发重绘(scene graph 会自动合并多次 update)
}
}
void GaugeItem::setMinValue(qreal v) { if (!qFuzzyCompare(m_minValue, v)) { m_minValue = v; emit minValueChanged(); update(); } }
void GaugeItem::setMaxValue(qreal v) { if (!qFuzzyCompare(m_maxValue, v)) { m_maxValue = v; emit maxValueChanged(); update(); } }
void GaugeItem::setUnit(const QString &s) { if (m_unit != s) { m_unit = s; emit unitChanged(); update(); } }
void GaugeItem::setLabel(const QString &s) { if (m_label != s) { m_label = s; emit labelChanged(); update(); } }
void GaugeItem::paint(QPainter *painter)
{
drawGauge(painter, boundingRect());
}
void GaugeItem::drawGauge(QPainter *p, const QRectF &r)
{
p->setRenderHint(QPainter::Antialiasing, true);
const qreal w = r.width();
const qreal h = r.height();
const qreal cx = w / 2.0;
const qreal cy = h * 0.65;
const qreal radius = qMin(w, h) * 0.42;
// ── 背景弧 ────────────────────────────────────
QPen pen(QColor("#333333"), radius * 0.06);
p->setPen(pen);
p->setBrush(Qt::NoBrush);
p->drawArc(QRectF(cx - radius, cy - radius, radius * 2, radius * 2),
135 * 16, 270 * 16); // 从 135° 到 405°(270° 跨度)
// ── 刻度线 ────────────────────────────────────
const int totalTicks = 30;
const qreal range = m_maxValue - m_minValue;
p->setPen(QPen(Qt::white, 1));
QFont font("Source Han Sans SC", radius * 0.07);
p->setFont(font);
for (int i = 0; i <= totalTicks; ++i) {
qreal angle = 135.0 + (270.0 * i / totalTicks);
qreal rad = qDegreesToRadians(angle);
bool major = (i % 5 == 0);
qreal r1 = radius * (major ? 0.75 : 0.85);
qreal r2 = radius * 0.95;
QPointF p1(cx + r1 * qCos(rad), cy - r1 * qSin(rad));
QPointF p2(cx + r2 * qCos(rad), cy - r2 * qSin(rad));
p->drawLine(p1, p2);
if (major) {
qreal tr = radius * 0.60;
QPointF tp(cx + tr * qCos(rad), cy - tr * qSin(rad));
int val = m_minValue + range * i / totalTicks;
QRectF textRect(tp.x() - 20, tp.y() - 8, 40, 16);
p->setPen(Qt::white);
p->drawText(textRect, Qt::AlignCenter, QString::number(val / 100));
}
}
// ── 指针 ──────────────────────────────────────
qreal ratio = (m_value - m_minValue) / (range > 0 ? range : 1);
ratio = qBound(0.0, ratio, 1.0);
qreal needleAngle = 135.0 + 270.0 * ratio;
qreal needleRad = qDegreesToRadians(needleAngle);
QPen needlePen(QColor("#FF3333"), radius * 0.04);
p->setPen(needlePen);
QPointF needleTip(cx + radius * 0.90 * qCos(needleRad),
cy - radius * 0.90 * qSin(needleRad));
p->drawLine(QPointF(cx, cy), needleTip);
// ── 中心圆 ────────────────────────────────────
p->setBrush(QColor("#FF3333"));
p->setPen(Qt::NoPen);
p->drawEllipse(QPointF(cx, cy), radius * 0.06, radius * 0.06);
// ── 数值文字 ────────────────────────────────────
p->setPen(Qt::white);
QFont valFont("Source Han Sans SC", radius * 0.18);
valFont.setBold(true);
p->setFont(valFont);
QRectF valRect(cx - radius * 0.5, cy + radius * 0.15, radius, radius * 0.25);
p->drawText(valRect, Qt::AlignCenter, QString::number(static_cast<int>(m_value)));
// ── 单位 ──────────────────────────────────────
QFont unitFont("Source Han Sans SC", radius * 0.10);
p->setFont(unitFont);
QRectF unitRect(cx - radius * 0.5, cy + radius * 0.40, radius, radius * 0.15);
p->drawText(unitRect, Qt::AlignCenter, m_unit);
// ── 标签 ──────────────────────────────────────
p->setPen(QColor("#AAAAAA"));
QFont labelFont("Source Han Sans SC", radius * 0.09);
p->setFont(labelFont);
QRectF labelRect(cx - radius * 0.5, cy - radius * 0.15, radius, radius * 0.15);
p->drawText(labelRect, Qt::AlignCenter, m_label);
}
代码解析:
1、setRenderTarget(QQuickPaintedItem::FramebufferObject); // FBO 模式,GPU 合成
作用: 使用 FBO(Frame Buffer Object) 模式进行渲染,将绘制结果渲染到 GPU 的帧缓冲对象中,而不是 CPU 内存。
渲染模式:

优点:
1) 利用 GPU 硬件加速,性能更好;
2) 支持透明通道和混合效果;
3) 与 QML 场景图(Scene Graph)融合更好;
缺点:
1)占用 GPU 内存;
2)某些老设备可能不支持。
2、setAntialiasing(true)
作用: 开启抗锯齿,让图形边缘更平滑,消除锯齿(阶梯状)效果。
对比效果:

**适用场景:**1)圆形、弧形、斜线等需要平滑边缘的图形;2)仪表盘、图表、动画控件。
3、setPerformanceHint(QQuickPaintedItem::FastFBOResizing)
作用: 告诉 Qt 在调整控件大小时,优先考虑速度而不是质量。
性能提示选项:

2、在main.cpp中注册C++自定义控件到qml
```cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "src/render/GaugeItem.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
// ── 注册 C++ 自定义控件到 QML ─────────────────────
// 参数说明:
// "Dashboard" - QML 中使用的模块名
// 1, 0 - 模块版本号 (主版本.次版本)
// "GaugeItem" - QML 中使用的类型名
qmlRegisterType<GaugeItem>("Dashboard", 1, 0, "GaugeItem");
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
3、在qml文件中使用控件
cpp
/*main.qml文件*/
import QtQuick 2.12
import QtQuick.Window 2.12
import Dashboard 1.0 // C++ GaugeItem 控件
Window {
visible: true
width: 1280
height: 800
title: qsTr("Hello World")
// ── 转速表(GaugeItem - 高频 C++ 渲染)──────────
GaugeItem {
id: tachometer
x: 40; y: 20
width: 380; height: 380
value: store.powertrain.engine // 绑定到 C++ 数据
minValue: 0
maxValue: 3000
unit: "r/min"
label: "Engine Speed"
}
// ── 车速表(GaugeItem - 高频 C++ 渲染)──────────
GaugeItem {
id: speedometer
x: 440; y: 20
width: 380; height: 380
value: store.powertrain.speed
minValue: 0
maxValue: 1400
unit: "km/h"
label: "Vehicle Speed"
}
}
二、控件中使用数据仓库
1、在main.cpp中添加数据仓库
cpp
// main.cpp
#include "model/DataGroups.h"
// ── 数据仓库(主线程) ────────────────────────────
VehicleDataStore store;
2、连接 C++ 和 QML
cpp
// main.cpp
#include <QQmlContext>
// ── QML 引擎 ────────────────────────────────────
// 创建 QML 应用引擎,负责加载和运行 QML 文件
QQmlApplicationEngine engine;
// 将 C++ 对象 store 注入到 QML 上下文中,使其在 QML 中可以通过 store 这个名字全局访问。
engine.rootContext()->setContextProperty("store", &store);
// 添加 QML 模块的导入路径,让引擎可以从 Qt 资源系统(qrc)中加载 QML 文件。
engine.addImportPath("qrc:/");
3、使用数据仓库
cpp
// main.qml
value: store.powertrain.engine // 绑定到 C++ 数据
value: store.powertrain.speed
三、线程中生成can报文数据(模拟数据)
cpp
/*src/simulator/CanSimulator.h*/
#ifndef CANSIMULATOR_H
#define CANSIMULATOR_H
#include <QObject>
#include <QTimer>
#include "../model/DataGroups.h"
/// CAN 总线模拟器 --- 周期性生成全部信号数据,批量发射
///
/// 关键设计:无论有多少个数据点(几百个),每 tick 只 emit 一次信号。
/// 通过 FrameBatch 结构体一次性携带所有数据跨线程传输,
/// 避免每个属性单独一次信号槽调用。
///
/// 工作线程 → moveToThread → start() → 每 100ms 发一帧
class CanSimulator : public QObject {
Q_OBJECT
public:
explicit CanSimulator(QObject *parent = nullptr);
bool isRunning() const { return m_running; }
public slots:
void start();
void stop();
signals:
/// 批量帧信号:一次携带 powertrain + lights 的全部数据
/// 工作线程 emit → 主线程 QueuedConnection 接收(一次跨线程调用搞定几百个值)
void frameBatch(const PowertrainGroup::Batch &pt,
const LightsGroup::Batch <);
private slots:
void onTick();
private:
QTimer *m_timer = nullptr;
bool m_running = false;
int m_tick = 0; // 用于产生变化的测试数据
};
#endif // CANSIMULATOR_H
cpp
/*src/simulator/CanSimulator.cpp*/
#include "CanSimulator.h"
#include <QDebug>
#include <QMetaType>
#include <cmath>
// 注册 struct,使其支持跨线程 QueuedConnection
//Q_DECLARE_METATYPE(PowertrainGroup::Batch)
//Q_DECLARE_METATYPE(LightsGroup::Batch)
CanSimulator::CanSimulator(QObject *parent)
: QObject(parent)
, m_timer(new QTimer(this))
{
qRegisterMetaType<PowertrainGroup::Batch>();
qRegisterMetaType<LightsGroup::Batch>();
connect(m_timer, &QTimer::timeout, this, &CanSimulator::onTick);
}
void CanSimulator::start()
{
if (m_running) return;
m_running = true;
m_timer->start(100);
qDebug() << "CanSimulator started (batch mode)";
}
void CanSimulator::stop()
{
if (!m_running) return;
m_timer->stop();
m_running = false;
qDebug() << "CanSimulator stopped";
}
void CanSimulator::onTick()
{
m_tick++;
// ── 模拟动力总成数据 ──────────────────────
PowertrainGroup::Batch pt;
// 发动机转速:正弦波 0~3000
pt.engine = static_cast<int>(1500 + 1500 * std::sin(m_tick * 0.2));
// 车速:递增 0~1400 循环
pt.speed = (m_tick * 10) % 1400;
// 水温:缓慢上升
pt.waterTemperature = 60 + (m_tick % 40);
// 机油压力
pt.engineOilPressure = 200 + (m_tick % 3) * 50;
// 油门开度:正弦 0~100
pt.acceleratorPedal = static_cast<int>(50 + 50 * std::sin(m_tick * 0.3));
// 电压
pt.voltage = 24 + (m_tick % 4);
// 档位
pt.currentGear = (m_tick / 5) % 8;
// 缓速器
pt.retarderValue = (m_tick % 6);
// ── 模拟灯光数据 ──────────────────────────
LightsGroup::Batch lt;
lt.turnLeft = (m_tick / 10) % 2; // 每 1s 闪烁
lt.turnRight = 0;
lt.highBeam = (m_tick / 30) % 2;
lt.markLight = 1;
lt.frontFog = 0;
lt.rearFog = (m_tick / 20) % 2;
lt.parkGreen = (m_tick / 15) % 2;
lt.parkRed = 0;
lt.trailerLeft = (m_tick / 10) % 2;
lt.trailerRight = 0;
// 【关键】一次 emit 传输所有数据,不是逐个 emit
emit frameBatch(pt, lt);
}
1、qRegisterMetaTypePowertrainGroup::Batch()
作用 :在运行时向 Qt 的元类型系统注册自定义类型,使其可以在信号槽的队列连接(QueuedConnection) 和 QVariant 中使用。
与 Q_DECLARE_METATYPE 的区别:

为什么需要 qRegisterMetaType:
即使使用了 Q_DECLARE_METATYPE,某些功能仍然需要运行时注册才能工作:
cpp
// 1. 编译时声明
Q_DECLARE_METATYPE(PowertrainGroup::Batch)
// 2. 运行时注册(必须调用一次)
qRegisterMetaType<PowertrainGroup::Batch>();
不调用 qRegisterMetaType 会怎样?
cpp
// ❌ 跨线程信号槽会报错
connect(sender, &Sender::batchReady,
receiver, &Receiver::processBatch,
Qt::QueuedConnection);
// 错误: QObject::connect: Cannot queue arguments of type 'Batch'
// (Make sure 'Batch' is registered using qRegisterMetaType().)
cpp
// main.cpp
#include <QThread>
// ── CAN 模拟器(工作线程) ────────────────────────
QThread simThread; // 定义一个线程
CanSimulator sim; // 类对象
sim.moveToThread(&simThread); // sim在线程中运行
// 【关键】一次 QueuedConnection 传输全部信号值(几百个也只要一次)
QObject::connect(&sim, &CanSimulator::frameBatch,
&store, &VehicleDataStore::applyFrame);
QObject::connect(&simThread, &QThread::finished, &sim, &CanSimulator::stop);
simThread.start(); // 启动线程
QMetaObject::invokeMethod(&sim, "start", Qt::QueuedConnection);
1、QMetaObject::invokeMethod(&sim, "start", Qt::QueuedConnection)
这行代码是 Qt 中跨线程调用方法的经典写法,它的作用是在子线程中异步启动 sim 对象的 start() 方法。也就是 "请你在 sim 对象所在的线程里,异步地调用它的 start() 方法。"


QMetaObject::invokeMethod 的几种调用方式:
1)异步调用(QueuedConnection)- 最常用
cpp
// 在目标线程的事件循环中执行
QMetaObject::invokeMethod(&sim, "start", Qt::QueuedConnection);
2) 同步调用(BlockingQueuedConnection)- 等待执行完成
cpp
// 阻塞调用者,直到目标线程执行完成
QMetaObject::invokeMethod(&sim, "start", Qt::BlockingQueuedConnection);
// ⚠️ 注意:只能在非 GUI 线程使用,否则会死锁
3)带参数的调用
cpp
// 调用 sim.startWithDelay(1000)
QMetaObject::invokeMethod(&sim, "startWithDelay",
Qt::QueuedConnection,
Q_ARG(int, 1000));
4) 带返回值的调用
cpp
int result;
QMetaObject::invokeMethod(&sim, "getStatus",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(int, result));
四、加载模块
看链接:https://blog.csdn.net/zhuowalun8427/article/details/163159463?spm=1001.2014.3001.5502