QT quick基础:自定义控件(C++)

本文章记录在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     &lt)
    {
        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     &lt);

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

相关推荐
可编程芯片开发12 小时前
基于分段变步长搜索RMDCFT算法的天基雷达空间机动目标检测方法MATLAB仿真,对比RMDCFT算法
算法
2501_9467361613 小时前
在线考试平台如何选型?从功能、优势与应用场景角度详解
大数据·人工智能·算法
天辛大师13 小时前
天心大师:不确定中锚定自我,AI生活的哲学命题
人工智能·算法·决策树·机器学习·生活·启发式算法
皓月斯语13 小时前
B2132 素数对
c++·算法·题解
星轨初途14 小时前
LeetCode 热题 100——day2 字母异位词分组
c++·算法·leetcode
happyprince14 小时前
篇1:整体观 · bitsandbytes项目全貌解读
人工智能·算法
青梅橘子皮15 小时前
STL---map/set... “家族“详解(从使用到底层)(1)
数据结构·算法
让学习成为一种生活方式15 小时前
DNA 甲基化综述(模式、调控与功能)--Current Opinion in Plant Biology
算法
笨鸟先飞的橘猫15 小时前
c++面向对象编程
开发语言·c++
Adios79415 小时前
搜索二维矩阵 II
java·数据结构·算法