QML与Qt C++ 交互机制详解

介绍

QML和 C++对象可以通过,signals,slots和 属性修改进行交互。对于一个C++对象,任何数据都可以通过Qt的 Meta-Object System暴露给QML(何总方法,后面介绍),同时,任何的QML对象数据通过Meta-object system在C++端直接访问。
在实际的项目中很多地方会用到QML与Qt C++交互。在这里总结了若干方法供大家参考,欢迎大家指导和拍砖。

在这里不外乎有三种方法:
1. 把Qt C++中的对象或类型暴露给 QML端,供QML端使用。(官方说法是“嵌入”而非“暴露”,比较文明。- -b)
2. QML中的Signal Handler(相当于Qt C++发送信号给QML端,QML端的Signal Handler进行处理)。
3. 在Qt C++端创建QML对象,既然对象都有了。那你想怎么样它就怎么样它呗。(没用过,看起来也不太实用,不过介绍介绍,有用过的同学留言哈)。

好,我们开始吧~

知识准备

别急,让我们先来看看,一些东西,如果您都知道,可以跳过此节。
QML API有三个主要成员——QDeclarativeEngineQDeclarativeComponentQDeclarativeContext

QDeclarativeEngine提供了QML的运行环境。
QDeclarativeComponent封装了QML Documents
QDeclarativeContext允许程序使用QML组件显示数据。

QML包含一个非常好用的API——QDeclarativeView。通过它,应用程序可以很方便的把QML组件嵌入到QGraphicsView中。QDeclarativeView主要用于在应用程序开发过程中进行快速原型开发。

暴露Qt C++的对象或类型给QML

创建需要暴露给QML的数据类型

#ifndef MYCLASS_H
#define MYCLASS_H
#include <QObject>
#include <QString>
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(QString myString READ myString WRITE setmyString NOTIFY myStringChanged)
public:
explicit MyClass(QObject *parent = 0);
Q_INVOKABLE QString getMyString();
signals:
void myStringChanged();
public slots:
void setmyString(QString aString);
QString myString();
private:
QString m_string;
};
#endif // MYCLASS_H

若你想数据元素中的方法可以被QML直接调用有2种方法:
1. 在函数申明前添加 Q_INVOKABLE 宏。
2. 申明成public slots。

QML可以直接访问改数据元素的属性,该属性由QPROPERTY所申明。
具体实现请参考,示例代码。

暴露已存在的Qt C++对象给QML

//main.cpp
MyClass myObj;
QDeclarativeEngine *engine=viewer.engine();
QDeclarativeContext *context=engine->rootContext();
context->setContextProperty("myObjectExposeByCXProperty", &myObj);

qml中可以直接使用myObjectExposeByCxProperty对象。

//mainpage.qml
...
Button{
...
id:btn1
...
text: qsTr("PROPERTY") 
//此处调用myString为MyClass的QPROPERTY的属性不是方法,所以没有括号。
onClicked: label.text=myObjectExposeByCXProperty.myString;
}
...
 
 

注册Qt C++类类型给QML

另外一种方式是注册类型
//main.cpp
qmlRegisterType<MyClass>("RegisterMyType", 1, 0, "MyClassType");
QML中这样使用
//mainpage.qml
...
import RegisterMyType 1.0
Button{
id:btn2
...
text: qsTr("INOVKABLE")
//此处调用的时INVOKABLE的方法,不是属性,所以有括号。
onClicked: label.text=myclassExposeByRegType.getMyString();
}
//创建对象,由于QML是解释执行的,所以放后面也没什么关系。
MyClassType
{
id:myclassExposeByRegType
}
步骤:
1. 导入import。
2. 创建对象。
3. id直接使用。

QML中的Signal Handler

还是使用上面的那例子,在qml中点击按钮控件,改变其中对象的字符串,这时候在Qt C++中发送一个signal信号给qml端,qml端接收到使用signal handler响应,改变label2的值。具体代码如下。
qml中修改string的值。
//mainpage.qml
Button{
id:btn3
text: qsTr("emit stringchanged signal")
onClicked: myObjectExposeByCXProperty.myString="xxxxx"; 
}
Qt C++触发信号
//myclass.cpp
void MyClass::setmyString(QString aString)
{
if(aString==m_string)
{
return;
}
m_string=aString;
emit myStringChanged();
}
连接signal handler响应
//mainpage.qml
Connections
{
target: myObjectExposeByCXProperty
onMyStringChanged:label2.text="Signal handler received" 
}

有参数形式的:

基本思路与具体步骤

基本思路,把你的Qt C++中的对象暴露给QML端,然后利用signals-slots 进行连接,并传递消息。具体步骤如下
1 创建自己的对象,如果你的对象是要显示在QML端,可以继承QDeclarativeItem,如果只是一个控制类,而不需要显示在QML端,只需要继承QObject。这里用到数据绑定请参考Using QML Bindings in C++ Applications

#include<QObject>
class NetConnectController : public QObject
{
Q_OBJECT
Q_PROPERTY(int status READ status WRITE setStatus NOTIFY statusChanged) 
public:
explicit NetConnectController(QObject *parent = 0);
 
void Ready()
{
emit statusChanged( m_status);
}
signals:
void statusChanged(int aStatus);
private:
int status() const;
void setStatus(int aStatus);
private :
//表示网络不同的状态
int m_status;
};


2 暴露你的对象给QML

..... 
NetConnectController netController
QDeclarativeEngine * engine = viewer.engine();
(engine->rootContext())->setContextProperty("NetController",&netController);
.....

3在QML中连接Signal-slot

......
 
Connections
{
target: NetController
onStatusChanged:changeStatus(aStatus)//Call JS Function
}
......

注意:上面的onStatusChanged 命名格式 “on”+"Qt C++中的signal名字"。在QML端可以直接使用Qt C++端的参数。例如上面的"aStatus"。

 

Qt C++中直接调用QML的函数

同样的QML的函数也可以被Qt C++端调用。
所有的QML函数都通过meta-object system暴露Qt C++端,在Qt C++端可以使用QMetaObject::invokeMethod()方法直接调用。下面就是这样的一个例子。
// MyItem.qml
import QtQuick 1.0
Item {
function myQmlFunction(msg) {
console.log("Got message:", msg)
return "some return value"
}
}
// main.cpp
QDeclarativeEngine engine;
QDeclarativeComponent component(&engine, "MyItem.qml");
QObject *object = component.create();
QVariant returnedValue;
QVariant msg = "Hello from C++";
QMetaObject::invokeMethod(object, "myQmlFunction",
Q_RETURN_ARG(QVariant, returnedValue),
Q_ARG(QVariant, msg));
qDebug() << "QML function returned:" << returnedValue.toString();
delete object;
注意:QMetaObject::invokeMethod()方法中的参数Q_RETURN_ARG()和Q_ARG()都被定义为QVariant类型,此类型是QML函数的的参数和返回值的通用数据类型。 
更多例程可以在SDK的安装目录中:\QtSDK\Examples\4.7\declarative\tutorials\extending 看到。

/**********************Q_INVOKABLE与invokeMethod用法*****************/

Qt中经常使用的几个宏: Q_OBJECT, SIGNAL与SLOT, Q_SIGNALS 与 Q_SLOTS, Q_EMIT ,Q_INVOKABLE, Q_PROPERTY。相比其他宏,Q_INVOKABLE 显得更加神秘,但Q_INVOKABLE的理解与使用变得越来越重要。本文将围绕Q_INVOKABLE以及相对应的invokeMethod展开讨论。

Q_INVOKABLE

#define Q_INVOKABLE

重新回顾一下Q_INVOKABLE的定义,它在$QTDIR/src/corelib/kernel/qobjectdefs.h 中,简单被define,目的在于让moc识别。

使用Q_INVOKABLE来修饰成员函数,目的在于被修饰的成员函数能够被元对象系统所唤起。

QMetaObject::invokeMethod

静态方法QMetaObject::invokeMethod() 的定义如下:

  1. view plain

    1. bool QMetaObject::invokeMethod ( QObject * obj, const char * member,Qt::ConnectionType type,  
    2. QGenericReturnArgument ret, QGenericArgument val0 = QGenericArgument( 0 ), …)  

invokeMethod的用法为,尝试调用对象obj的方法member(注意member可以为信号或者是槽),如何member可以被调用,则返回真,否则返回假。QMetaObject::invokeMethod可以是异步调用,也可以是同步调用。这取决与它的连接方式Qt::ConnectionType type。如果type为Qt::DirectConnection,则为同步调用,若为Qt::QueuedConnection,则为异步调用。例如:

  1. view plain

    1. QMetaObject::invokeMethod(object, "methodName",   
    2. Qt::QueuedConnection,   
    3. Q_ARG(type1, arg1),   
    4. Q_ARG(type2, arg2));  

上述调用为异步调用。请注意,因为上面所示的参数需要被在构建事件时进行硬拷贝,参数的自定义型别所对应的类需要提供一个共有的构造函数、析构函数以及拷贝构造函数。而且必须使用注册Qt型别系统所提供的qRegisterMetaType() 方法来注册这一自定义型别。

Q_INVOKABLE与QMetaObject::invokeMethod均由元对象系统唤起。这一机制在Qt C++/QML混合编程跨线程编程Qt Service Framework 以及 Qt/ HTML5混合编程以及里广泛使用。

Qt C++/QML混合编程

QML中调用C++方法借助了Qt元对象系统。考虑在QML中使用Qt C++定义的方法,如下代码所示:

view plain

  1. import Qt 4.7   
  2. import Shapes 5.0   //自定义模块  
  3. Item {   
  4.     width: 300; height: 200  
  5.     Ellipse {   
  6.          x: 50; y: 35; width: 200; height: 100   
  7.         color: "blue"   
  8.          MouseArea {   
  9.             anchors.fill: parent  
  10.             // 调用C++中定义的randomColor方法   
  11.             onClicked: parent.color = parent.randomColor()    
  12.         }   
  13.     }  
  14. }  

为了让上述QML代码成功的调用下面这段代码定义的randomColor()函数,最为关键的一点见randomColor方法用Q_INVOKABLE 修饰。

view plain

  1. #include <QDeclarativeItem >  
  2. class EllipseItem : public QDeclarativeItem   
  3. {   
  4.     Q_OBJECT   
  5. public:  
  6.       Q_INVOKABLE QColor randomColor() const;  
  7.       …  
  8. }  

在跨线程编程中的使用

我们如何调用驻足在其他线程里的QObject方法呢?Qt提供了一种非常友好而且干净的解决方案:向事件队列post一个事件,事件的处理将以调用我们所感兴趣的方法为主(当然这需要线程有一个正在运行的事件循环)。而触发机制的实现是由moc提供的内省方法实现的。因此,只有信号、槽以及被标记成Q_INVOKABLE的方法才能够被其它线程所触发调用。如果你不想通过跨线程的信号、槽这一方法来实现调用驻足在其他线程里的QObject方法。另一选择就是将方法声明为Q_INVOKABLE,并且在另一线程中用invokeMethod唤起。

Qt Service Framework

Qt服务框架是Qt Mobility 1.0.2版本推出的,一个服务(service)是一个独立的组件提供给客户端(client)定义好的操作。客户端可以通过服务的名称,版本号和服务的对象提供的接口来查找服务。 查找到服务后,框架启动服务并返回一个指针。

服务通过插件(plug-ins)来实现。为了避免客户端依赖某个具体的库,服务必须继承自QObject。这样QMetaObject 系统可以用来提供动态发现和唤醒服务的能力。要使QmetaObject机制充分的工作,服务必须满足,其所有的方法都是通过 signal,slot,property 或invokable methodQ_INVOKEBLE来实现

其中,最常见的与servicer交互的方法如下:

view plain

  1. QServiceManager manager;QObject *storage ;  
  2. storage = manager.loadInterface("com.nokia.qt.examples.FileStorage"); if (storage)     QMetaObject::invokeMethod(storage, "deleteFile", Q_ARG(QString, "/tmp/readme.txt"));  
上面的代码通过service的元对象提供的invokeMethod方法,调用文件存储对象的deleteFile() 方法。客户端不需要知道对象的类型,因此也没有链接到具体的service库。  当然在服务端的deleteFile方法,一定要被标记为Q_INVOKEBLE,才能够被元对象系统识别

Qt服务框架的一个亮点是它支持跨进程通信,服务可以接受远程进程。在服务管理器上注册后 进程通过signal,slot,invokable method和property来通信,就像本地对象一样。服务可以设定为在客户端间共享,或针对一个客户端。  请注意,在Qt服务框架推出之前,信号、槽以及invokable method仅支持跨线程。 下图是跨进成的服务/客户段通信示意图(图片来自诺基亚论坛)。这里我们可以清楚的看到,invokable methodQ_INVOKEBLE 是跨进城、跨线程对象之间通信的重要利器。

 

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值