【QML与C++的通讯机制】如何在QML中使用C++类,以及如何在C++中获取QML的内容

 


仅作学习,原文:https://developer.aliyun.com/article/1463150

1、C++和QML之间的分布关系

特点/比较维度 QML C++ Qt
语言和语法 基于JavaScript和JSON的声明性语言 基于C++的面向对象编程库
适用领域 丰富的图形用户界面(GUI) 复杂业务逻辑和底层功能
性能 相对较低,因为基于解释型语言 较高,因为基于编译型语言
开发效率 较高,语法简洁且易于学习 较低,适用于复杂功能开发
社区和支持 较新,可能资源和支持相对较少 庞大的开发者社区和丰富的资源
集成 可与C++ Qt共同使用 可与QML共同使用

在同一可执行文件中,C++和QML共享同一个进程,但是它们运行在不同的线程上。
在Qt中,C++和QML都是在同一个进程中运行的,因为QML只是一种声明式的UI语言,它通过Qt Quick框架解析和渲染,最终也是由C++代码实现的。因此,当我们编译并运行一个Qt应用程序时,只会生成一个可执行文件,在该可执行文件运行时,C++和QML都是在同一个进程中运行的。
当一个Qt应用程序启动时,其主线程会负责初始化QML引擎、创建C++对象、加载QML文件等操作。在此过程中,QML引擎会解析QML文件,将其转换为C++对象,并通过C++与QML之间的绑定实现交互。因此,虽然C++和QML的代码是分离的,但它们是在同一个进程中运行的,可以通过信号和槽、属性绑定、函数调用等方式进行通信。
在Qt中,GUI线程是Qt的主线程,负责处理GUI事件和更新UI界面。因此,QML中的所有UI操作都必须在GUI线程中执行,否则会引发线程安全问题。而C++代码可以在任何线程中执行,但是需要注意线程安全问题。
当我们在C++中调用QML中的方法或属性时,Qt会自动将该调用转发到GUI线程中执行。同时,当我们在QML中调用C++中的方法或属性时,Qt也会自动将该调用转发到C++所在的线程中执行。这种线程切换是由Qt自动完成的,我们不需要手动干预。
总之,虽然C++和QML运行在不同的线程中,但是它们之间的交互是由Qt自动完成的,我们只需要遵守Qt的线程安全规则即可。

2、C++和QML之间的通讯方式 

方式
优点
缺点
适用场景
Property绑定
简单易用、代码量少、实时响应自动更新属性值,适合实现简单的交互。
只能在QML中读取和写入C++对象的属性,无法直接调用C++对象的函数。
适用于只需要传递属性值的场景
Signal/Slot
可以传递复杂类型的参数,
可以在QML中直接调用C++对象的函数,同时也可以实现C++对象向QML发送消息。
需要编写一些额外的代码来处理信号和槽,代码量相对较多,不够自动化
数据量小,通讯频繁的场景,
实时交互的场景,如UI元素状态变化的反馈等
Q_INVOKABLE
可以实现双向通信,可以直接调用C++函数
只能读取和写入属性,无法调用C++对象的函数
需要频繁调用C++函数的场景,
快速调用C++函数的场景,如计算等
Q_PROPERTY
支持属性自动同步,线程安全
无法直接在QML中修改复杂数据类型,不支持多线程异步调用
属性数据量较小,需要频繁同步的场景
Context属性
可以实现双向通信,可以像JavaScript对象一样使用C++对象
需要手动编写C++类并注册,不够自动化
适用于需要在QML中使用C++对象的场景,如复杂业务逻辑的处理等
Qt Remote Objects
支持远程通讯,支持多线程异步调用
实现较为复杂,需要对网络编程有一定了解
分布式应用场景

Q_INVOKABLE和Q_PROPERTY的区别

  • Q_INVOKABLE宏用于声明一个成员函数可以从外部调用,即可以通过Qt元对象系统调用,类似于C++中的public成员函数,但是可以通过元对象系统跨线程和跨进程调用。Q_INVOKABLE与QMetaObject::invokeMethod均由元对象系统唤起。
  • Q_INVOKABLE是个空宏,目的在于让moc识别。 使用Q_INVOKABLE来修饰成员函数,目的在于被修饰的成员函数能够被元对象系统所唤起。

   例如,你可以使用Q_INVOKABLE宏声明一个槽函数,然后使用connect函数将该槽函数与信号连接起来。

  • Q_PROPERTY宏用于声明一个类的属性,并提供读写函数和通知函数(可选)。属性是一个类的状态或特性,可以被设置和读取。通过Q_PROPERTY,可以使用元对象系统来访问这些属性,并且可以将它们与Qt框架中的其他类进行交互。例如,你可以使用Q_PROPERTY宏声明一个类的颜色属性,并实现一个读取和设置该属性的函数。
  1.  这两个宏通常用于不同的情况,但是可以同时使用。如果你希望从外部调用一个类的函数并且需要将该类的状态作为属性访问,则可以使用Q_INVOKABLE和Q_PROPERTY宏来实现这两个功能。
  2. 因此,Q_PROPERTYQ_INVOKABLE的作用不同,前者是用于定义C++类的属性,后者是用于将C++类的函数暴露给QML。需要注意的是,Q_PROPERTY也可以使用READ、WRITE、NOTIFY等参数来指定属性的读写方式和通知机制,而Q_INVOKABLE没有这些参数。
  3. 总的来说,Q_PROPERTYQ_INVOKABLE是Qt框架中用于定义C++类成员的宏,它们的用途不同,分别用于定义属性和函数,并且都可以在QML中使用。

属性绑定

  • 可以在 C++ 代码中使用 Q_PROPERTY 宏定义一个属性,并且通过 QObject::setProperty() 方法设置属性的值,而在 QML 中,您可以使用 Binding 来绑定 QML 中的属性和 C++ 中的属性。这样,当 C++ 中的属性发生变化时,QML 中的属性也会相应地发生变化;反过来,当 QML 中的属性发生变化时,C++ 中的属性也会相应地发生变化。 
  •  需要注意的是,实现双向绑定,需要在 C++ 代码中使用 QQmlProperty 实例对象来设置和获取 QML 中的属性,而不是使用 QObject::setProperty() 和 QObject::property() 方法。

因为QObject::setProperty() 和 QObject::property() 方法只能设置和获取 C++ 对象自身的属性,而无法访问到 QML 中的属性。

 信号和槽

 通过定义 C++ 对象的信号和槽,可以在 QML 中监听并处理这些信号。这种方式实现了双向通信,即 C++ 对象可以向 QML 发送信号,而 QML 也可以向 C++ 对象发送信号。但需要注意的是,信号和槽的参数类型必须在 QML 中可识别,否则会报错。

直接调用

  •  在 QML 中通过 C++ 对象的方法名直接调用 C++ 对象的方法是基于_INVOKABLE。在 C++ 中,使用_Q_INVOKABLE宏声明的成员函数可以在 QML 中使用方法名直接调用。
  • 这些成员函数必须是公共的,并且它们必须符合一定的函数签名规则,才能在 QML 中使用。这种方法比较直接,但需要注意的是,只有在 C++ 对象已经被实例化并在 QML 中注册后才能进行调用。

 上下文属性

在QML和C++之间通讯时,利用上下文属性(Context Property)是基于Q_PROPERTY,通过将 C++ 对象注册为 QML 引擎的上下文属性,可以在 QML 中访问该对象的属性和方法。这种方法实现了双向通信,但需要注意的是,上下文属性只能在 QML 引擎的主线程中使用,否则会导致线程错误。

3、代码实例

属性绑定:

使用Qt的QML C++绑定功能来将C++代码与QML界面进行交互
首先,您需要在C++代码中定义一个QObject派生类,然后将其注册到QML引擎中。
接下来可以在QML中使用该类的实例,并调用其公共槽和属性。

1、在C++代码中定义一个QObject派生类,例如:

复制代码
 1 class MyObject : public QObject {
 2     Q_OBJECT public:
 3     Q_INVOKABLE void myMethod(QString param);
 4     Q_PROPERTY(QString myProperty READ myProperty WRITE setMyProperty NOTIFY myPropertyChanged)
 5     QString myProperty() const;
 6     void setMyProperty(const QString& value); signals:
 7     void mySignal(QString message);
 8     void myPropertyChanged(); private:
 9     QString m_myProperty; 
10 };
复制代码

2、在main函数中注册该类到QML引擎中,例如:

1 QQmlApplicationEngine engine;
2 qmlRegisterType<MyObject>("com.mycompany", 1, 0, "MyObject");
3 engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

3、在QML中使用该类的实例,并调用其公共槽和属性,例如:

1 import com.mycompany 1.0 MyObject {
2            id: myObject
3         myProperty: "Hello, World!"
4         onMyPropertyChanged: console.log("myProperty changed to", myProperty)
5         Component.onCompleted: myMethod("Hello from QML!")
6      }

Q_INVOKABLE :

使用Qt的QML中的JavaScript API来调用C++的Qt Quick模块提供的接口

  1. 在C++中定义一个QObject子类,该子类包含一个或多个Q_INVOKABLE方法,这些方法将成为QML中可调用的接口。
  2. 在QML文件中导入C++模块并创建一个C++对象。
  3. 使用JavaScript API中的call()方法来调用C++对象的方法。

C++代码:

复制代码
 1 class MyObject : public QObject
 2 {
 3 Q_OBJECT
 4 public:
 5 Q_INVOKABLE void myMethod(QString arg);
 6 };
 7 void MyObject::myMethod(QString arg)
 8 {
 9 qDebug() << "Called with argument:" << arg;
10 }
复制代码

QML代码:

复制代码
 1 import MyModule 1.0
 2 MyObject {
 3 id: myObject
 4 }
 5 Button {
 6 text: "Call C++ method"
 7 onClicked: {
 8 myObject.myMethod("Hello, world!");
 9 }
10 }
复制代码

在这个示例中,我们定义了一个名为MyObject的C++类,并将其导入到QML中。我们还在QML中创建了一个名为myObject的MyObject对象,并在按钮的点击事件中调用了myMethod()方法。
当按钮被点击时,myMethod()方法将被调用,并将字符串“Hello, world!”作为参数传递给它。该方法将打印出“Called with argument: Hello, world!”的消息。

信号和槽机制

在 QML 中,可以使用 Qt 的信号和槽机制来实现与 C++ 的交互

在 C++ 中定义一个槽函数:

复制代码
1 class MyObject : public QObject 
2  {
3     Q_OBJECT public slots:
4     void mySlot() {
5         qDebug() << "My slot is called";
6     } 
7  };
复制代码

在 QML 中连接信号和槽:

复制代码
 1 import QtQuick 2.0
 2 Rectangle {
 3     signal mySignal()
 4     Connections {
 5         target: myObject // myObject 是在 C++ 中创建的对象
 6         onMySignal: myObject.mySlot()
 7     }
 8     MouseArea {
 9         anchors.fill: parent
10         onClicked: mySignal()
11     }
12 }
复制代码

当点击 MouseArea 时,会触发 mySignal() 信号,然后在 Connections 中将该信号连接到 myObject 对象的 mySlot() 槽函数中。

上下文属性(Context Property)

基于Q_PROPERTY进行上下文属性在QML和C++之间通讯.

我们可以在C++中定义一个QObject子类,并使用Q_PROPERTY宏定义一个属性:

 

复制代码
 1 class MyObject : public QObject
 2 {
 3     Q_OBJECT
 4     Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged) 
 5  public:
 6     MyObject(QObject *parent = nullptr) : QObject(parent), m_value(0) {}
 7     int value() const { return m_value; }
 8     void setValue(int value) { m_value = value; emit valueChanged(); 
 9     } 
10  signals:
11     void valueChanged(); 
12  private:
13     int m_value;
14 };
复制代码

 

然后,我们可以将MyObject类注册为QML类型,并将其实例化并绑定到QML中的一个上下文属性上:

 

1 qmlRegisterType<MyObject>("MyLib", 1, 0, "MyObject");
2 MyObject myObject; QQmlApplicationEngine engine;
3 engine.rootContext()->setContextProperty("myObject", &myObject);
4 engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

 

在QML中,我们就可以访问myObject的属性和方法

复制代码
1 import MyLib 1.0
2 Text {
3     text: "My object value: " + myObject.value
4     MouseArea {
5         anchors.fill: parent
6         onClicked: myObject.setValue(myObject.value + 1)
7     } 
8  }
复制代码

 

 

posted @   taohuaxiaochunfeng  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示

目录导航