QML与C++混合编程
之前写的文章都是一段一段的,现在整合起来,预估又是一段长臭文...
零、前言
1、先看一下相关类的继承关系:
①、视图关系
②、QML加载相关
上方图参考:https://blog.csdn.net/qq_34139994/article/details/105195447
2、关于Qt与QML的个人理解
一切的一切,底层都是C++实现的,我们通过Qt封装好的C++类,如视图、引擎等,去加载QML文件,底层C++读取这些QML文本后,动态实例化相关对象,最终呈现在界面上。
一、在Qt中加载QML的方式
1、QQmlApplicationEngined搭配 Window
2、QQuickView 搭配 Item
3、QQuickWidget 加载 QML【Item】
见之前文章:https://www.cnblogs.com/judes/p/15606042.html
注意本文以Quick工程为例,非Widget工程,故选择的是前两种加载方式
二、传递C++类型/对象给QML使用
1、注册
有非常多的注册模板函数可供使用,相关信息在手册里直接搜索qmlRegist..
int qmlRegisterAnonymousType int qmlRegisterExtendedType int qmlRegisterExtendedUncreatableType int qmlRegisterInterface //注册一个接口,不可被实例化,可以使用类型名引用【多态】 void qmlRegisterModule int qmlRegisterRevision int qmlRegisterSingletonInstance int qmlRegisterSingletonType //注册单例类型,可安全代替setContextProperty int qmlRegisterType //注册一个可实例化对象,最常用 int qmlRegisterTypeNotAvailable int qmlRegisterUncreatableMetaObject int qmlRegisterUncreatableType //注册不可被实例化的对象,可以使用其中的枚举
举例:创建一个C++的Udp类,并注册给QML用
//cpp qmlRegisterType<Myudp>("Myudp.module",1,0,"Myudp"); //qml
import Myudp.module 1.0
Myudp{
id:udp
}
需要添加头文件:
#include <QtQml/qqmlengine.h>
2、设置上下文
三种加载方式使用的工具是:
QQmlApplicationEngined
QQuickView
QQuickWidget
它们都具有一个函数rootContext,返回QQmlContext;
QQmlContext提供函数:setContextProperty【注册单个上下文属性】、setContextProperties【注册多个上下文属性】。
这个上下文属性其实就是在C++里实例化了的对象,效果与上面的注册单例qmlRegisterSingletonType一致。
举例:
//C++ Myudp udp; QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("udp",&udp);//注册对象 //QML udp.initUdpSlot()
注意:需要先设置上下文,再连接qml:
三、封装C++对象
上面只是QML里能够用简单访问到C++对象,是不足以支持实际项目开发的,故需要对C++对象进行封装。
见之前的文章:https://www.cnblogs.com/judes/p/11242922.html
Qt提供了这些宏用于封装:
1、信号与槽 C++类中的信号与槽都可以在QML中访问 2、C++类的成员函数,Q_INVOKABLE Q_INVOKABLE void function(); 3、C++类的枚举,Q_ENUMS Q_ENUMS (enumName) 4、C++的属性,Q_PROPERTY Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
1、例子:封装一个颜色生成器给QML使用,C++暴露属性、信号、槽、函数、枚举,QML使用【信号的使用有多种方式】
①、MyColor.h
#ifndef MYCOLOR_H #define MYCOLOR_H #include <QObject> #include <QTimer> #include <QtQml/qqmlengine.h>//注册函数需要 #include <QDebug> #include <QColor> class MyColor : public QObject { Q_OBJECT //枚举暴露 Q_ENUMS(RECT_COLOR) //属性暴露,QML可直接使用属性[动态绑定、获取、设置,前提是匹配了对应函数] Q_PROPERTY(QString color READ getColor NOTIFY colorChanged) Q_PROPERTY(QString info READ getInfo WRITE setInfo) //属性相关 public: explicit MyColor(QObject *parent = nullptr); inline QString getColor(){return m_cur_color;} inline QString getInfo(){return m_info;} inline void setInfo(QString info){m_info = info;qDebug()<<"info changed:"<<m_info;} inline void resetColor(){m_cur_index = 0;} inline static void RegisterType(){qmlRegisterType<MyColor>("MyColor.module",1,0,"MyColor");} //信号相关,QML可直接onSignal访问 signals: void colorChanged(); //公共函数相关 public: Q_INVOKABLE inline void reSetColor(){m_cur_index = 0;} //槽函数相关,QML可直接访问,但是需要是public的,private访问会出错 public slots: inline void addColor(){ QColor clr(rand() % 256, rand() % 256, rand() % 256); m_colors.append(clr.name()); } //枚举 public: enum RECT_COLOR { RECT_COLOR_YELLOW, RECT_COLOR_RED, RECT_COLOR_BLUE, RECT_COLOR_ALL }; //内部属性相关 private: QString m_cur_color; //当前的颜色 int m_cur_index = 0; //颜色序号 QList<QString> m_colors; //存储所有颜色列表 QTimer m_timer; //定时器修改当前颜色 QString m_info; //模拟一个变量,QML可修改 }; #endif // MYCOLOR_H
②、MyColor.cpp
#include "mycolor.h" MyColor::MyColor(QObject *parent) : QObject{parent} { m_colors.append("red"); m_colors.append("black"); m_colors.append("green"); m_colors.append("gray"); m_colors.append("blue"); QObject::connect(&m_timer, &QTimer::timeout, [this]{ if(m_cur_index < m_colors.size()) { m_cur_color = m_colors[m_cur_index++]; emit colorChanged(); qDebug()<<"C++ cur color:"<<m_cur_color; } else { m_cur_index = 0; m_cur_color = m_colors[m_cur_index++]; emit colorChanged(); qDebug()<<"C++ cur color:"<<m_cur_color; } }); m_timer.start(1000); }
③、main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "mycolor.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); MyColor::RegisterType(); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/use_mycolor.qml"))); if (engine.rootObjects().isEmpty()) return -1; return app.exec(); }
④、qml
import QtQuick 2.9 import QtQuick.Window 2.2 import QtQuick.Controls 2.3 import MyColor.module 1.0 Window { id: root visible: true width: 770 height: 200 title: qsTr("Hello World") MyColor { id: c1 onColorChanged: { r2.color = c1.color //①.直接在信号对象里获取信号参数 } } Connections { target: c1 function onColorChanged() { r3.color = c1.color //②.通过信号槽连接器捕捉信号 } } Row { anchors.fill: parent spacing: 10 Rectangle { id: r1 width: 100 height: 100 color: c1.color //③.直接使用属性,进行属性绑定 } Rectangle { id: r2 width: 100 height: 100 color: "pink" } Rectangle { id: r3 width: 100 height: 100 color: "pink" } Button { text: "add color" onClicked: { c1.addColor()//④.直接调用C++对象的槽函数 } } Button { text: "reset color" onClicked: { c1.reSetColor()//⑤.调用函数[C++里需要标记上Q_INVOKABLE] } } Button { text: "set time to c++" onClicked: { c1.info = Qt.formatDateTime(new Date(), "hh-mm-ss")//⑥.直接使用属性,进行属性设置 } } Button { text: "get enum" onClicked: { console.log(MyColor.RECT_COLOR_YELLOW) //⑦.直接使用枚举 } } } }
第一个rect通过属性绑定来设置颜色;
第二个rect通过在对象里捕捉信号,并设置颜色;
第三个rect通过Connections连接器来设置颜色;【注意后两个rect的属性未绑定,相当于我们收到信号再主动去设置颜色】
add color按钮直接调用C++的槽函数;
reset color按钮调用C++的普通函数,此函数需要被Q_INVOKABLE标记;
set time to c++按钮用于设置C++对象属性的值,这里设置的是当前时间,C++被设置后打印出来;
get enum按钮获取C++类里定义的枚举值,这里是直接打印,这里的使用方式是:类名.枚举值【如果注册的是对象该怎么用呢?】。
至此,这个例子是很全面的,描述了如何封装C++类,然后注册,然后QML如何访问。
完整下载例子:https://download.csdn.net/download/m0_53292003/63241808
2、例子2:封装UDP给QML使用
见之前文章:https://www.cnblogs.com/judes/p/11241576.html
四、QML里响应C++的信号
//C++ Class A{ signals: void rcvData(QString str); }; //QML onRcvData:{ //执行x如console.log(str); }
见之前文章:https://www.cnblogs.com/judes/p/11243242.html
五、C++访问QML对象的函数
这种访问常用于第三种加载方式,通过获取QML根对象,再通过元对象的invokeMethod函数访问对象的函数。
通过QMetaObject::invokeMethod,见之间文章:
访问函数:https://www.cnblogs.com/judes/p/12939000.html
传递List:https://www.cnblogs.com/judes/p/13029400.html
六、C++捕捉QML的信号
这种访问常用于第三种加载方式,通过获取QML根对象,再通过QObject::connect连接根对象的信号和C++的槽函数。
#include <QQuickItem> //指定对象QObject* quitButton = root->findChild<QObject*>("quitButton"); if (quitButton){ QObject::connect(quitButton, SIGNAL(clicked()), &app, SLOT(quit())); }
见之前文章:https://www.cnblogs.com/judes/p/11243242.html
七、进阶之MVC编程
MVC编程也是QML与C++的混合编程,通常C++提供Model也就是数据,QML提供View也就是视图。见之前文章:
ListView:https://www.cnblogs.com/judes/p/13450897.html
MVC:https://www.cnblogs.com/judes/p/15622972.html
PS:
1、QML使用C++对象时,尽量注册C++类,而不要设置C++对象为上下文
https://raymii.org/s/articles/Qt_QML_Integrate_Cpp_with_QML_and_why_ContextProperties_are_bad.html
长风破浪会有时,直挂云帆济沧海!
可通过下方链接找到博主
https://www.cnblogs.com/judes/p/10875138.html