C++与QML混合编程

一、前言

简单来说,混合编程就是通过Qml高效便捷的构建UI界面,而使用C ++来实现业务逻辑和复杂算法。Qt集成了QML引擎Qt元对象系统,使得QML很容易从C ++中得到扩展,在一定的条件下,QML就可以访问QObject派生类的成员,例如信号、槽函数、枚举类型、属性、成员函数等。

C++与QML交互时,一些基本类型C++与QML之间能自动转化,两者之间可以互通。如下:

C++与QML混合编程_第1张图片

除以上的数据类型外,C++传递给QML的值是无法被QML识别的,也就是自定义的数据类型是无法通过编译系统自动转化的,比如 Enum、QList、QMap、struct 结构体等复杂数据。因此只能采用发送信号携带参数或者使用 Q_INVOKABLE 宏在qml中获得返回值,将该自定义数据类型以QObject子类对象的形式传递给QML

二、基础数据类型实现方式

Qt中提供了两种在 QML 环境中使用C ++对象的方式

方式一: 在C ++中实现一个类,注册到Qml环境中,Qml环境中使用该类型创建对象

方式二: 在C ++中构造一个对象,将这个对象设置为Qml的上下文属性,在Qml环境中直接使用该属性

两种方式之间的区别是第一种可以使C ++类在QML中作为一个数据类型,例如函数参数类型或属性类型,也可以使用其枚举类型、单例等,功能更强大。下面主要说明下方式一的实现步骤

第一步: 创建继承自QObject的C++类,对象必须继承自QObject才能在QML被使用和访问

C++与QML混合编程_第2张图片

第二步:在类定义中使用Q_PROPERTY导出成员的READ、WRITE、NOTIFY接口,这样类中的成员变量就可以在QML调用和修改了,同时变量被修改后也会发送信号通知QML端。用 Q_INVOKABLE 修饰成员函数,这样类中的成员函数就可以直接被QML调用。前提是该模块已经被注册过!!!

class MyObject : public QObject
{
    Q_OBJECT //必须有

public:
    explicit MyObject(QObject *parent = nullptr);
    ~MyObject();
    static MyObject * getInstance();

    //读取函数,对应READ
    int getIValue() {return iValue};
    QString getSStr() {return sStr};

    //被 Q_INVOKABLE 修饰C++函数能直接被QML调用
    Q_INVOKABLE void setCapture(bool state);

    //写函数,对应 WRITE,可以没有
    Q_INVOKABLE void setIValue(int value) {iValue = value};
    Q_INVOKABLE void setSStr(const QString &str) {sStr = value};

    //定义公有的槽函数
public slots:
    Q_INVOKABLE void cppSlot(int i, QString s);

private slots:
    void timer_timeout();

signals:
    //修改通知,对应 NOTIFY,可以没有。可以分开写,也可以用同一个信号
    void iValueChanged(int value);
    void sStrChanged(const QString &str);
    void myObjDataChanged();
    void cppSig(QVariant i, QVariant s);

private:
    QTimer *timer;
    int iValue;
    QString sStr;

    // property declarations required for QML
    Q_PROPERTY(int iValue READ getIValue WRITE setIValue NOTIFY myObjDataChanged)
    Q_PROPERTY(QString sStr READ getSStr WRITE setSStr NOTIFY myObjDataChanged)
};

第三步:在main.cpp中注册模块

//使用qmlRegisterType注册模块,在qml中通过 import 模块名称 进行引用
//模块名称、主版本号、次版本号、类名称
qmlRegisterType("MyObj11", 1, 0, "MyObject");

方式二实现方式

QQmlApplicationEngine engine;
//使用setContextProperty设置全局对象/上下文对象。作用域为全局
//常用于一些不变的常量
QQmlContext *context = engine.rootContext();
context->setContextProperty("SCREEN_WIDTH", 800);

第四步:QML端调用

import QtQml 2.12
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Window 2.12
import MyObj11 1.0 //根据模块名称导入模块

//创建MyObject对象
MyObject{
    objectName: "myobj"
    //可直接操作MyObject类中的数据了
    id: myobj
    iValue: 10
    sStr: "www"
    Component.onCompleted:{
        console.log(iValue, sStr)
    }
}
//监控myobj.iValue的改变
onValueChanged: {
    console.log("onValueChanged: ", value)
}

//1、直接访问C++的成员变量和成员函数....................................
Button{
    id: btn1
    objectName: "button1"
    x:20; y:20
    anchors.margins: 10
    text: "访问C++成员和方法"
    onClicked: {
        myobj.iValue += 2 //修改myobj.iValue的值
        onoff = onoff ? 0 : 1
        myobj.setCapture(onoff) //调用C++端函数
    }
}

三、自定义数据类型实现方式

1、在QML中使用C++端的枚举类型

C++的枚举类型如果要在QML中使用,需要在使用 Q_ENUMS 去修饰这个枚举类型

.h头文件

#ifndef TIMINGSAMPPARA_H
#define TIMINGSAMPPARA_H

#include 

class TimingSampPara : public QObject
{
    Q_OBJECT
public:
    explicit TimingSampPara(QObject *parent = nullptr);
    ~TimingSampPara();

    //枚举
    enum E_TIMINGSAMP_PARA
    {
        E_TIMINGSAMP_PARA_NO = 0,
        E_TIMINGSAMP_PARA_ENABLE,
        E_TIMINGSAMP_PARA_TIME,
        E_TIMINGSAMP_PARA_ALL
    };
    Q_ENUMS(E_TIMINGSAMP_PARA)

    int getNo() const;
    int getEnable() const;
    QString getTime() const;

    void setNo(int value);
    void setEnable(int value);
    void setTime(QString value);

    Q_PROPERTY(int no READ getNo WRITE setNo)
    Q_PROPERTY(int enable READ getEnable WRITE setEnable)
    Q_PROPERTY(QString time READ getTime WRITE setTime)

private:
    int no;
    int enable;    //使能
    QString time;  //时间(月-日 时:分)
};

#endif // TIMINGSAMPPARA_H

main.cpp中注册自定义类型

qmlRegisterType("MyTimingSampPara",1,0, "TimingSampPara");

.qml 端使用

import MyTimingSampPara 1.0

arrTimingSampPara[TimingSampPara.E_TIMINGSAMP_PARA_NO] = 6
arrTimingSampPara[TimingSampPara.E_TIMINGSAMP_PARA_ENABLE] = 201
arrTimingSampPara[TimingSampPara.E_TIMINGSAMP_PARA_TIME] =  "00-00 05:02"

2、在QML中读写C++端的自定义结构体数据

结构体在QML中是无法被识别的,必须将结构体类型转换为QVariantMap或QVariant,或者将结构体封装到QObject子类中,在QObject子类中用Q_PROPERTY定义其成员。转成QVariantMap或QVariant的方式 也是需要先定义成QObject子类,而且QML端无法修改,所以最为简单的还是将结构体封装到QObject子类中

比如想要在QML中读写如下的自定义结构体数据

//定时采样
typedef struct
{
    int no;
    int enable;     //使能
    char time[16];  //时间(月-日 时:分)
}TIMING_SAMP_PARA_T;

//采样模式参数配置
typedef struct
{
    E_SAMP_MODE_T sampMode;       //采样模式 0:定时采样  1:时间等比 2:外控采样 3:流量等比 4:流量跟踪 5:液位触发
    int pipeSave;       //采样管存(S)
    int cycleTime;      //采样循环时间(S)

    TIMING_SAMP_PARA_T *timingSamp; //24个定时采样时刻,动态申请空间


    int timingSampCnt;

}SAMP_MODE_CFG_T;

2.1、QML获取C++类的自定义结构体数据

由于在该自定义结构体中还有嵌套的 TIMING_SAMP_PARA_T 结构体数据,且叫二级成员,没有嵌套的叫一级成员;对于一级成员,直接在QObject子类中使用Q_PROPERTY宏定义一个QML能访问的属性,并绑定其READ、WRITE、NOTIFY接口,就可以在QML端进行读取和修改了;对于二级成员,需要使用 Q_INVOKABLE 宏定义一个函数,函数的返回值为二级成员对应的QObject子类

1、定义二级成员---定时采样子类(timingsamppara.h/.cpp)

#ifndef TIMINGSAMPPARA_H
#define TIMINGSAMPPARA_H

#include 

class TimingSampPara : public QObject
{
    Q_OBJECT
public:
    explicit TimingSampPara(QObject *parent = nullptr);
    ~TimingSampPara();

    //枚举
    enum E_TIMINGSAMP_PARA
    {
        E_TIMINGSAMP_PARA_NO = 0,
        E_TIMINGSAMP_PARA_ENABLE,
        E_TIMINGSAMP_PARA_TIME,
        E_TIMINGSAMP_PARA_ALL
    };
    Q_ENUMS(E_TIMINGSAMP_PARA)

    int getNo() const;
    int getEnable() const;
    QString getTime() const;

    void setNo(int value);
    void setEnable(int value);
    void setTime(QString value);

    Q_PROPERTY(int no READ getNo WRITE setNo)
    Q_PROPERTY(int enable READ getEnable WRITE setEnable)
    Q_PROPERTY(QString time READ getTime WRITE setTime)

private:
    int no;
    int enable;    //使能
    QString time;  //时间(月-日 时:分)
};

#endif // TIMINGSAMPPARA_H

timingsamppara.cpp

#include "timingsamppara.h"
#include "global.h"

TimingSampPara::TimingSampPara(QObject *parent) : QObject(parent)
{
    logPrintf(LOG_APP_TRACE, "%s success\n", Q_FUNC_INFO);
}

TimingSampPara::~TimingSampPara()
{
    logPrintf(LOG_APP_TRACE, "%s success\n", Q_FUNC_INFO);
}

int TimingSampPara::getNo() const
{
    return no;
}

int TimingSampPara::getEnable() const
{
    return enable;
}

QString TimingSampPara::getTime() const
{
    return time;
}

void TimingSampPara::setNo(int value)
{
    no = value;
}

void TimingSampPara::setEnable(int value)
{
    enable = value;
}

void TimingSampPara::setTime(QString value)
{
    time = value;
}

2、定义一级成员---采样模式参数子类(modesetpara.h/.cpp)

#ifndef MODESETPARA_H
#define MODESETPARA_H

#include 
#include "timingsamppara.h"
#include "global.h"

class ModeSetPara : public QQuickItem
{
    Q_OBJECT

public:
    explicit ModeSetPara(QQuickItem *parent = 0);
    ~ModeSetPara() override;

public:
    int getSampMode();
    int getPipeSave();
    int getCycleTime();

    void setSampMode(int value);
    void setPipeSave(int value);
    void setCycleTime(int value);

    Q_PROPERTY(int sampMode READ getSampMode WRITE setSampMode)
    Q_PROPERTY(int pipeSave READ getPipeSave WRITE setPipeSave)
    Q_PROPERTY(int cycleTime READ getCycleTime WRITE setCycleTime)

    Q_INVOKABLE void savePara(void);
    Q_INVOKABLE TimingSampPara *getTimingSampPara(int no);
    Q_INVOKABLE bool saveTimingSampPara(int no, const QVariantList &listpara);

private:
    SAMP_MODE_CFG_T sampModeCfg;
};

#endif // MODESETPARA_H

modesetpara.cpp

#include "modesetpara.h"
#include "global.h"
#include "xmlOp.h"

ModeSetPara::ModeSetPara(QQuickItem *parent):
    QQuickItem(parent)
{
    //从xml配置文件解析
    parseSampModeCfg(MODE_CONFIG_PATH, &sampModeCfg);
    logPrintf(LOG_APP_TRACE, "%s success\n", Q_FUNC_INFO);
}

ModeSetPara::~ModeSetPara()
{
    free(sampModeCfg.timingSamp);
    free(sampModeCfg.liquidLvlTrig);
    logPrintf(LOG_APP_TRACE, "%s success\n", Q_FUNC_INFO);
}

 // Write data into XML
void ModeSetPara::savePara(void)
{
    
    modifySampModePara(MODE_CONFIG_PATH, &sampModeCfg);
    logPrintf(LOG_APP_TRACE, "%s success\n", Q_FUNC_INFO);
}

int ModeSetPara::getSampMode()
{
    return sampModeCfg.sampMode;
}

int ModeSetPara::getPipeSave()
{
    return sampModeCfg.pipeSave;
}

int ModeSetPara::getCycleTime()
{
    return sampModeCfg.cycleTime;
}

void ModeSetPara::setSampMode(int value)
{
    sampModeCfg.sampMode = (E_SAMP_MODE_T)value;
}

void ModeSetPara::setPipeSave(int value)
{
    sampModeCfg.pipeSave = value;
}

void ModeSetPara::setCycleTime(int value)
{
    sampModeCfg.cycleTime = value;
}

 //获取二级结构体对应的QObject子类
TimingSampPara *ModeSetPara::getTimingSampPara(int no)
{
    // Assign value from structure to object
    TimingSampPara *pTimingSampPara = new TimingSampPara();
    pTimingSampPara->setNo(sampModeCfg.timingSamp[no].no);
    pTimingSampPara->setEnable(sampModeCfg.timingSamp[no].enable);
    pTimingSampPara->setTime(sampModeCfg.timingSamp[no].time);

    logPrintf(LOG_APP_TRACE, "%s success\n", Q_FUNC_INFO);
    return pTimingSampPara;
}

bool ModeSetPara::saveTimingSampPara(int no, const QVariantList &listpara)
{
    if(TimingSampPara::E_TIMINGSAMP_PARA_ALL != listpara.size())
    {
        logPrintf(LOG_APP_TRACE, "%s failed! listpara.size=%d(less than %d)\n", Q_FUNC_INFO,
                  listpara.size(), TimingSampPara::E_TIMINGSAMP_PARA_ALL);
        return false;
    }

    // Assign value from variant to structure
    sampModeCfg.timingSamp[no].no = listpara[TimingSampPara::E_TIMINGSAMP_PARA_NO].toInt();
    sampModeCfg.timingSamp[no].enable = listpara[TimingSampPara::E_TIMINGSAMP_PARA_ENABLE].toInt();
    strncpy(sampModeCfg.timingSamp[no].time, listpara[TimingSampPara::E_TIMINGSAMP_PARA_TIME].toString().toStdString().c_str(),
            sizeof(sampModeCfg.timingSamp[no].time));

    // Write data into XML
    modifySampModePara(MODE_CONFIG_PATH, &sampModeCfg);

    logPrintf(LOG_APP_TRACE, "%s success\n", Q_FUNC_INFO);

    return true;
}

3、在main.cpp中注册自定义对象

使用 qmlRegisterType,将自定义的QObject派生类注册到QML

qmlRegisterType("MyTimingSampPara",1,0, "TimingSampPara");
qmlRegisterType("MyModeSetPara",1,0, "ModeSetPara");

4、在QML中读取

import MyTimingSampPara 1.0
import MyModeSetPara 1.0

Item {

    width: 800
    height: 480
    visible: true

    ModeSetPara {
        id: modeCfgPara
    }

    Component.onCompleted: {
        //获取一级成员
        console.log("sampMode=" + modeCfgPara.sampMode)
        console.log("pipeSave=" + modeCfgPara.pipeSave)
        console.log("cycleTime=" + modeCfgPara.cycleTime)
        //设置一级成员
        modeCfgPara.sampMode = 0
        modeCfgPara.pipeSave = 21
        modeCfgPara.cycleTime = 31

        //获取二级成员
        var pTimingSampPara = modeCfgPara.getTimingSampPara(5)
        console.log("timingSamp[5]:")
        console.log("no=" + pTimingSampPara.no)
        console.log("enable=" + pTimingSampPara.enable)
        console.log("time=" + pTimingSampPara.time)
        //设置二级成员,收集界面数据后设置
        var arrTimingSampPara = []
        arrTimingSampPara[TimingSampPara.E_TIMINGSAMP_PARA_NO] = 6
        arrTimingSampPara[TimingSampPara.E_TIMINGSAMP_PARA_ENABLE] = 201
        arrTimingSampPara[TimingSampPara.E_TIMINGSAMP_PARA_TIME] =  "00-00 05:02"
        modeCfgPara.saveTimingSampPara(5, arrTimingSampPara)
    }
}

2.2、C++类接收QML传递的自定义数据

1、在QML中修改

对于一级成员直接修改属性值就可以了;对于二级成员,界面需要先收集界面的输入数据,使用 QVariantList 数据的形式暂时存储,再将其传递给C++类。使用QVariantList的原因是QVariantList以及QStringList在qml中是可以被识别的,可直接通过索引,比如 mList[i] 就可以访问,但是map就不能直接访问,需要做一些处理,没测试通!!

使用 QVariantList 设置的一个问题就是如何将索引与结构体变量对应起来,目前的方法是将结构体成员在 list 中的索引定义成枚举

2、C++接收QML传递的QVariantList变量后,将其转换成二级成员结构体

bool ModeSetPara::saveTimingSampPara(int no, const QVariantList &listpara)
{
    if(TimingSampPara::E_TIMINGSAMP_PARA_ALL != listpara.size())
    {
        logPrintf(LOG_APP_TRACE, "%s failed! listpara.size=%d(less than %d)\n", Q_FUNC_INFO,
                  listpara.size(), TimingSampPara::E_TIMINGSAMP_PARA_ALL);
        return false;
    }

    // Assign value from variant to structure
    sampModeCfg.timingSamp[no].no = listpara[TimingSampPara::E_TIMINGSAMP_PARA_NO].toInt();
    sampModeCfg.timingSamp[no].enable = listpara[TimingSampPara::E_TIMINGSAMP_PARA_ENABLE].toInt();
    strncpy(sampModeCfg.timingSamp[no].time, listpara[TimingSampPara::E_TIMINGSAMP_PARA_TIME].toString().toStdString().c_str(),
            sizeof(sampModeCfg.timingSamp[no].time));

    // Write data into XML
    modifySampModePara(MODE_CONFIG_PATH, &sampModeCfg);

    logPrintf(LOG_APP_TRACE, "%s success\n", Q_FUNC_INFO);

    return true;
}

3、在QML中获取C++端的Map类型

要在 C++和 QML 中使用 map 类型,不是直接使用 QMap,而是用 QVariantMap 来替换,QVariantMap 只是 QMap 的一个重定义的别名

typedef QVariantMap
Synonym for QMap.

c++端定义如下:

#include 
#include 
#include 
#include 

class MyDemo : public QObject
{
    Q_OBJECT
    Q_ENUMS(Gender)
public:
    enum Gender
    {
        Boy,
        Girl
    }
    struct PeopleInfo
    {
        int year;      // 年龄
        int course ,   // 年纪
        Gender gender; // 性别
        QString name   // 名字
    };

public:
    MyDemo (QObject *parent = nullptr);
    ~MyDemo ();

    // 通过QVariantMap获取学生信息
    Q_INVOKABLE QVariantMap getCurrentStudentInfo(void)
    {
        QVariantMap map;
        map.clear();
        map.insert("year", m_studentInfo.year);
        map.insert("course ", m_studentInfo.course );
        map.insert("gender", m_studentInfo.gender);
        map.insert("name", m_studentInfo.name);
        return map;
    }
    // 通过QVariantList获取学生名字
     Q_INVOKABLE QVariantList getStudentNameList(void)
     {
            QVariantList varList;
            for (int i=0; i m_studentNameList;
};

qml端获取示例如下:

Item {
    function readValues(anArray, anObject) {
        for (var i=0; i
typedef QVariantList
Synonym for QList.

四、参考链接

C++与QML混合编程技术(传递自定义数据类型)_北星之茫的博客-CSDN博客_c++和qml混合编程

QML_Qml和C++混合编程_HX科技的博客-CSDN博客_qml c++混合编程

你可能感兴趣的:(#,QML,c++,qt,qml)