C++加载QML的方式
QQmlApplicationEngine加载
QQmlApplicationEngine
是 Qt Quick 应用程序中用于加载和执行 QML 文件的类。它是 Qt 框架的一部分,专门用于处理 QML 语言编写的用户界面和应用程序逻辑。QtCreater在加载QML文件时默认就是采用的这种方式。
1 2 3 4 5 6 7 8 9 10
| QQmlApplicationEngine engine; const QUrl url(QStringLiteral("qrc:/qmltest/Main.qml"));
QObject::connect( &engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url);
|
- 加载自定义QML窗口,新建一个控件MyWindow,生成MyWindow.qml文件。
1 2 3 4 5 6 7 8 9
| import QtQuick import QtQuick.Controls Window { width: 1000 height: 800 visible: true title: "hello myWindow" }
|
1 2 3 4 5 6 7 8 9 10 11
| #include <QQmlApplicationEngine>
QQmlApplicationEngine engine("qrc:/qmltest/MyWindow.qml");
QQmlApplicationEngine engine; engine.load("qrc:/qmltest/MyWindow.qml");
|
QQuickView加载
QQuickView
是Qt Quick模块中的一个类,它提供了一个窗口,用于显示Qt Quick用户界面。
- 新建一个控件MyText,生成MyText.qml文件。
1 2 3 4 5 6 7 8 9 10
| import QtQuick
Text { width:200 height:100 anchors.centerIn: parent text:"mytest" }
|
- 根对象必须是QQuickItem的子类:
QQuickView
只支持加载从QQuickItem
派生的根对象。
- 避免使用Window或ApplicationWindow作为根对象:当使用
QQuickView
时,应避免在QML文件中使用Window
或ApplicationWindow
作为根对象。因为QQuickView
本身已经是一个窗口,如果在QML文件中再使用Window
或ApplicationWindow
,会导致运行时弹出两个窗口。
1 2 3 4 5 6 7 8 9 10 11 12
| #include <QQuickView>
QQuickView view(QUrl("qrc:/qmltest/MyText.qml")); view.show();
QQuickView view; view.setSource(QUrl("qrc:/qmltest/MyText.qml")); view.show();
|
QQmlComponent加载
QQmlComponent
是 Qt Quick 中用于动态加载 QML 文件的类。它允许你在运行时加载 QML 组件,这在创建插件或需要根据用户选择动态更改界面时非常有用。
1 2 3 4 5 6 7
| #include <QQmlComponent> #include <QQmlEngine>
QQmlEngine eng; QQmlComponent com(&eng); com.loadUrl(QUrl("qrc:/qmltest/MyWindow.qml")); com.create();
|
三种方式加载怎么选择?
- QQmlApplicationEngine:适合加载完整的应用程序,以 Window 或为根对象的 QML 文件。
- QQuickView:适合在 C++ 应用程序中嵌入 QML 界面,Item(及以 Item 为根的组件)作为根对象。
- QQmlComponent: 适合动态加载和创建 QML 组件,允许在运行时加载 QML 组件。
C++获取QML对象并修改
QQmlApplicationEngine获取rootObject
- 新建一个MyWindow.qml文件,我们要对根对象Window的title属性以及它的子对象Rectange的color属性进行获取和修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import QtQuick import QtQuick.Controls Window { width: 300 height: 100 visible: true title: "hello myWindow" Rectangle { objectName: "myRectangle" width: 100 height: 100 color:"red" } }
|
窗口运行图片
- 通过C++代码使Window的title属性修改为hi myWindow,Rectange的color修改为green,并在控制台打印改完后的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| QList<QObject*> rootObjects = engine.rootObjects();
if(rootObjects.isEmpty()) { return -1; }
QObject* rootObject = rootObjects.first();
rootObject->setProperty("title","hi myWindow");
qDebug()<<rootObject->property("title").toString();
QObject* childObject = rootObject->findChild<QObject*>("myRectangle");
childObject->setProperty("color","green"); qDebug()<<childObject->property("color").toString();
|
修改后窗口运行图片
控制台打印修改后的信息(绿色对应的十六进制是:”#008000”)
QQmlComponent获取rootObject
QQmlComponent在运行时动态加载 QML 文件,并获取其根对象rootObject。
1 2 3 4 5 6
| QQmlEngine eng; QQmlComponent com(&eng); com.loadUrl(QUrl("qrc:/qmltest/MyWindow.qml"));
QObject* rootObject = com.create();
|
rootObject对象管理优化
使用std::unique_ptr智能指针,C++11以上
1 2 3 4 5 6 7 8 9
| QQmlEngine eng; QQmlComponent com(&eng); com.loadUrl(QUrl("qrc:/qmltest/MyWindow.qml"));
std::unique_ptr<QObject> ct( static_cast<QObject*>(com.create()) ); ct->setProperty("title","hi myWindow"); ct->findChild<QObject*>("myRectangle")->setProperty("color","green");
|
C++调用QML函数
在调用函数之前必须先获取对应的QML对象,如rootObject。先创建一个MyWindow.qml文件,写入几个函数测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import QtQuick import QtQuick.Controls Window { width: 300 height: 100 visible: true title: "myWindow" Text { id:mytext text: qsTr("这是一段文字") } function changeMyText1() { mytext.text=qsTr("C++调用了无参函数") } function changeMyText2(str:string) { mytext.text=str } function getString():string { return "qml函数返回的字符串" } }
|
注:QML不支持函数重载!!
- 在C++文件调用无参函数changeMyText1()
1
| QMetaObject::invokeMethod(rootObject,"changeMyText1");
|
- 在C++文件调用有参函数changeMyText2(str:string)
1
| QMetaObject::invokeMethod(rootObject,"changeMyText2",QString("C++调用了有参函数"));
|
- C++文件里获取函数的返回值getString()
1 2
| QString retValue; QMetaObject::invokeMethod(rootObject,"getString", qReturnArg(retValue));
|
invokeMethod函数介绍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
[static, since 6.5] template <typename... Args> bool QMetaObject::invokeMethod(QObject *obj, const char *member, Args &&... args)
[static, since 6.5] template <typename ReturnArg, typename... Args> bool QMetaObject::invokeMethod(QObject *obj, const char *member, QTemplatedMetaMethodReturnArgument<ReturnArg> ret, Args &&... args)
[static, since 6.5] template <typename... Args> bool QMetaObject::invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, Args &&... args)
[static, since 6.5] template <typename ReturnArg, typename... Args> bool QMetaObject::invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QTemplatedMetaMethodReturnArgument<ReturnArg> ret, Args &&... args)
QMetaObject::invokeMethod(相应的qml对象,函数名,调用方式(可选),返回值(可选),参数1(可选),参数2(可选),……,参数n(可选))
|
invokeMethod函数传参类型以及返回值类型需要注意
- 在调用有参函数*changeMyText2(str:string)*时,为什么要加QString进行类型转换?
1 2 3 4
| QMetaObject::invokeMethod(rootObject,"changeMyText2",QString("C++调用了有参函数"));
QMetaObject::invokeMethod(rootObject,"changeMyText2","C++调用了有参函数");
|
因为字符数组不是Qt的元对象系统,在编译时不是已知的,所以要使用QString进行转换。
建议使用Q_ARG
宏来传递。
1
| QMetaObject::invokeMethod(rootObject,"changeMyText2",Q_ARG(QString,"C++调用了有参函数");
|
- 如果qml中函数没有指定返回值类型时,默认返回的是Qt的通用类型
QVariant
。
1 2 3 4 5 6 7 8 9 10
| function getString() { return "123" }
QVariant retValue; QMetaObject::invokeMethod(rootObject,"getString", qReturnArg(retValue)); qDebug()<<retValue.toString();
|
C++接收QML信号
在连接信号和槽前必须先获取对应的QML对象,在MyWindow.qml文件里定义一个信号和按钮以及自增变量count++,每点击按钮,都会把qml里定义count的值,通过信号传递给C++,C++获取值并打印。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import QtQuick import QtQuick.Controls Window { width: 300 height: 100 visible: true title: "myWindow" property int count: 0 signal signalForCpp(count:int) Button{ text: qsTr("发送信号") width: 100 height: 50 onClicked: { signalForCpp(count++) } } }
|
创建一个C++类MyClass定义槽函数,用来接受信号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #ifndef MYCLASS_H #define MYCLASS_H
#include <QObject> #include<QDebug> class MyClass : public QObject { Q_OBJECT public: explicit MyClass(QObject *parent = nullptr);
public slots: void CppSlot(int count) { qDebug()<<"count: "<<count; } };
#endif
|
在main.cpp文件里,连接信号和槽
1 2 3 4 5 6 7 8 9 10
| MyClass myclass;
QObject::connect(
rootObject, SIGNAL(signalForCpp(int)),
&myclass, SLOT(CppSlot(int)) );
|
C++类转换成QML类型
把C++的类暴露给qml,可以在qml文件里创建与cpp类一致的类型,并且可以调用类里面的成员函数,访问成员变量,使用信号和槽等。如,在MyWindow.qml文件里可以使用自定义类型:
1 2 3 4 5 6 7 8 9 10 11 12
| Window { width: 300 height: 100 visible: true title: "myWindow" CppType { id:ct } }
|
自定义一个C++类CppType,要在qml文件里创建CppType类型,并且使用自定义的属性,并调用成员函数。
自定义的类要想让qml兼容,必须满足:
- 继承自
QObject
- 添加
Q_OBJECT
,启用Qt元对象
- 添加
QML_ELEMENT
,将类暴露给qml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #ifndef CPPTYPE_H #define CPPTYPE_H
#include <QObject> #include <QQmlEngine>
class CppType : public QObject { Q_OBJECT QML_ELEMENT public: explicit CppType(QObject *parent = nullptr); signals: };
#endif
|
1 2 3 4 5 6
| #include "cpptype.h"
CppType::CppType(QObject *parent) : QObject{parent} {}
|
通过CMake模块化注册(建议使用)
- 使用CMake在构建Qt项目的时候,把cpp源文件编译成一个模块,在某个qml需要创建时,只需要导入该qml模块即可。这样模块化设计,降低了耦合,便于维护和管理。
1 2 3 4 5 6 7 8 9 10
| qt_add_executable(appqmltest main.cpp cpptype.h cpptype.cpp qml.qrc )
qt_add_qml_module(appqmltest URI com.CppType VERSION 1.0 )
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import QtQuick import QtQuick.Controls import com.CppType 1.0
Window { width: 300 height: 100 visible: true title: "myWindow" CppType { } }
|
- QtCreate存在问题,如果CMake执行后,CppType不能识别,请关闭QtCreate,然后重新导入项目即可。
通过qmlRegisterType注册
- 在程序初始化时,使用
qmlRegisterType
函数将C++类注册到QML类型系统中。
1 2 3 4 5 6 7
|
qmlRegisterType<CppType>("com.CppType", 1, 0, "CppType");
QQmlApplicationEngine engine; engine.load("qrc:/MyWindow.qml");
|