在QML刚刚出来不久,参加nokia的一个Qt Quick培训的时候,QML就给我的印象是:解释性脚本语言,没有内存操作的说法,更不用说指针了。当时也就是想想,也没有具体去实践探讨。由于现在在用Qt做产品,UI方面不得不跟QML打交道。QML做UI可以说是又好又快,大大节省了开发周期,但是由于QML处理逻辑的能力较差,所以,对于大量的逻辑处理还是需要Qt C++支持。这就涉及到C++与QML解释性语言之间数据交换。
QML在和C++相互嵌入运行的时候,就需要QML的engine: QDeclarativeEngine。通常我们不会直接与这个engine打交道。我们通常的做法是这样的:
#include <QtGui/QApplication> #include "qmlapplicationviewer.h" #include <qdeclarative.h> #include <QDeclarativeView> #include <QDeclarativeContext> int main(int argc, char *argv[]) { QApplication app(argc, argv); MyClass myObj; QDeclarativeView view; QDeclarativeContext *ctxt = view.rootContext(); ctxt->setContextProperty("obj", &myObj); view.setSource(QUrl("main.qml")); return app.exec(); }View已经实现了对engine的封装。如果我们需要在QML中调用C++对象的话,只需要使用setContextProperty().
View和Context提供了QML运行环境。setContextProperty相当于把C++对象在这个Context中注册,QML中只需要使用obj这个别名就可以访问到注册的对象myObj,这种访问包括调用成员函数,当然,成员函数要使用Q_INVOKABLE修饰之后就可以调用了。看到这里,似乎还没看到QML与指针的任何关系。通常情况下,遇到QML需要和C++对象相互嵌入调用的时候,使用setContextProperty,将事先构造好的对象在QML运行环境中进行注册,这样QML就可以调用C++方法了。
并不是所有的对象都可以事先构造好,例如:QML UI上有一个按钮,每点击这个按钮一次就需要构造一个C++对象进行相应的操作,如果点击100下,那岂不是事先要构造好100个对象?在这种情况下,动态创建C++对象无疑是最好的方法。如果对于纯C++,当然简单,new一个对象,并把对象的指针返回就可以对其进行操作了。当时这里是QML,就没那么简单了。i
对于这种需要在QML中动态创建C++对象,并需要对该对象进行操作,我们没有办法每次都调用setContextProperty("obj", &myObj)。因为setContextProperty的第一个参数实际上是在QML中的变量名,QML中就是通过第一个参数找到该对象的。所以该字符创必须唯一。第一次跟我同事讨论方案的时候,我们想,不就是字符串唯一吗,我直接new一个对象,然后将对象的地址转换成字符串,然后调用setContextProperty,并将字符串返回到QML中。这样,QML岂不就是可以自由的操作该对象了。这个方法呢,能满足要求,但是感觉很不专业。要是能操作指针就好了。 最后的方案是通过已经注册的对象,调用 obj.createObject(),返回对象的指针到QML中去。QML通过获取的指针做自己操作。由于QML中无法直接定义指针,所以使用variant这个万能的类型定义变量。
先看代码:
//由于只有一个inline函数,所以只贴出来了头文件,cpp文件实际上什么也没做
//operation.h #ifndef OPERATION_H #define OPERATION_H #include <QObject> #include "target.h" class Operation : public QObject { Q_OBJECT public: explicit Operation(QObject *parent = 0); Q_INVOKABLE inline Target* createObject() { return new Target; }//这里我们就是为了返回指针给QML signals: public slots: }; #endif // OPERATION_H
//target.h #ifndef TARGET_H #define TARGET_H #include <QObject> #include <QDebug> class Target : public QObject { Q_OBJECT public: explicit Target(QObject *parent = 0); Q_INVOKABLE inline void getAction() { qDebug() << "Hi, I am Harlen"; } signals: public slots: }; #endif // TARGET_H
//main.cpp #include <QtGui/QApplication> #include "qmlapplicationviewer.h" #include <qdeclarative.h> #include <QDeclarativeView> #include <QDeclarativeContext> #include "operation.h" #include "target.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); Operation myObj; QDeclarativeView view; QDeclarativeContext *ctxt = view.rootContext(); ctxt->setContextProperty("obj", &myObj); view.setSource(QUrl("qml/QmlPointer/main.qml")); view.show(); return app.exec(); }
import QtQuick 1.0 Rectangle { width: 360 height: 360 property variant pointer color: "skyblue" Text { text: "Harlen Tan" font.bold: true font.pointSize: 20 anchors.centerIn: parent } MouseArea { anchors.fill: parent onClicked: { pointer = obj.createObject() pointer.getAction() } } }注意上面的注释出createObject的签名:
Target* Operation::createObject();,由于obj已经向QML注册过了,所以在QML中直接使用obj.createObject()是绝对可行的。
那么,只要createObject()一调用,QML拿到的绝对就是指针了,然后QML中使用指针操作,就游刃有余了。
调试运行,点击对话框,发现有提示消息: main.qml:19:TypeError: Result of expression 'pointer' [undefined] is not an object.
查看main.qml 的19行:pointer.getAction(),意思是说pointer不是个对象。但是QML中也没有pointer->或者 *pointer这个语法。
怎么办?计量都用完了。只有回去去翻看SDK document. 没有绝望,因为在我心里总有那么一点念头这问题可以解决,只是还没理解透彻。于是看到了帮助文档这么一段话,让我豁然开朗:
Any C++ data that is used from QML - whether as custom properties, or parameters for signals or functions - must be of a type that is recognizable by QML.
By default, QML recognizes the following data types:
bool
unsigned int, int
float, double, qreal
QString
QUrl
QColor
QDate, QTime, QDateTime
QPoint, QPointF
QSize, QSizeF
QRect, QRectF
QVariant
QVariantList, QVariantMap
QObject*
Enumerations declared with Q_ENUMS()
To allow a custom C++ type to be created or used in QML, the C++ class must be registered as a QML type using qmlRegisterType(), as shown in the Defining new QML elements section above.
我们并没有向QML注册我们的指针类型,上面红色标出来的地方时:QObject*是可以识别的,那是不是意味着我们注册下 Target就可以了呢?
于是修改main.cpp
#include <QtGui/QApplication> #include "qmlapplicationviewer.h" #include <qdeclarative.h> #include <QDeclarativeView> #include <QDeclarativeContext> #include "operation.h" #include "target.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); Operation myObj; QDeclarativeView view; QDeclarativeContext *ctxt = view.rootContext(); qmlRegisterType<Target>();//只是添加了这么一句话,向QML环境注册自定义类型 ctxt->setContextProperty("obj", &myObj); view.setSource(QUrl("qml/QmlPointer/main.qml")); view.show(); return app.exec(); }
运行成功了。后来发现,由于Target是继承自QObject,所以只需要将createObject返回类型改为QObject *,也可以运行成功。