一、前言
简单来说,混合编程就是通过Qml高效便捷的构建UI界面,而使用C ++来实现业务逻辑和复杂算法。Qt集成了QML引擎和Qt元对象系统,使得QML很容易从C ++中得到扩展,在一定的条件下,QML就可以访问QObject派生类的成员,例如信号、槽函数、枚举类型、属性、成员函数等。
C++与QML交互时,一些基本类型C++与QML之间能自动转化,两者之间可以互通。如下:
除以上的数据类型外,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被使用和访问
第二步:在类定义中使用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<MyObject>("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 <QObject>
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<TimingSampPara>("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 <QObject>
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 <QQuickItem>
#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<TimingSampPara>("MyTimingSampPara",1,0, "TimingSampPara");
qmlRegisterType<ModeSetPara>("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<QString, QVariant>.
c++端定义如下:
#include <QObject>
#include <QVariant>
#include <QVariantMap>
#include <QVariantList>
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.count(); ++i)
{
varList.push_back(m_studentNameList.at(i));
}
return varList;
}
private:
// 学生信息
PeopleInfo m_studentInfo;
QList<struct PeopleInfo> m_studentNameList;
};
qml端获取示例如下:
Item {
function readValues(anArray, anObject) {
for (var i=0; i<anArray.length; i++)
console.log("Array item:", anArray[i])
for (var prop in anObject) {
console.log("Object item:", prop, "=", anObject[prop])
}
}
}
4、在QML中获取C++端的列表类型
要在 C++和 QML 中使用 list 类型,不是直接使用 QList,而是用 QVariantList 来替换,QVariantList 只是 QList 的一个重定义的别名
typedef QVariantList
Synonym for QList<QVariant>.