QML与指针

         在QML刚刚出来不久,参加nokia的一个Qt Quick培训的时候,QML就给我的印象是:解释性脚本语言,没有内存操作的说法,更不用说指针了。当时也就是想想,也没有具体去实践探讨。由于现在在用Qt做产品,UI方面不得不跟QML打交道。QML做UI可以说是又好又快,大大节省了开发周期,但是由于QML处理逻辑的能力较差,所以,对于大量的逻辑处理还是需要Qt C++支持。这就涉及到C++与QML解释性语言之间数据交换。

                QML在和C++相互嵌入运行的时候,就需要QML的engine: QDeclarativeEngine。通常我们不会直接与这个engine打交道。我们通常的做法是这样的:

[cpp]  view plain copy print ?
  1. #include <QtGui/QApplication>  
  2. #include "qmlapplicationviewer.h"  
  3. #include <qdeclarative.h>  
  4. #include <QDeclarativeView>  
  5. #include <QDeclarativeContext>  
  6.   
  7. int main(int argc, char *argv[])  
  8. {  
  9.     QApplication app(argc, argv);  
  10.     MyClass myObj;  
  11.     QDeclarativeView view;  
  12.     QDeclarativeContext *ctxt = view.rootContext();  
  13.     ctxt->setContextProperty("obj", &myObj);  
  14.     view.setSource(QUrl("main.qml"));  
  15.   
  16.   
  17.     return app.exec();  
  18. }  
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文件实际上什么也没做

[cpp]  view plain copy print ?
  1. //operation.h  
  2. #ifndef OPERATION_H  
  3. #define OPERATION_H  
  4.   
  5. #include <QObject>  
  6. #include "target.h"  
  7. class Operation : public QObject  
  8. {  
  9.     Q_OBJECT  
  10. public:  
  11.     explicit Operation(QObject *parent = 0);  
  12.     Q_INVOKABLE inline Target* createObject() { return new Target; }//这里我们就是为了返回指针给QML  
  13.   
  14. signals:  
  15.   
  16. public slots:  
  17.   
  18. };  
  19.   
  20. #endif // OPERATION_H  


[cpp]  view plain copy print ?
  1. //target.h  
  2. #ifndef TARGET_H  
  3. #define TARGET_H  
  4.   
  5. #include <QObject>  
  6. #include <QDebug>  
  7.   
  8. class Target : public QObject  
  9. {  
  10.     Q_OBJECT  
  11. public:  
  12.     explicit Target(QObject *parent = 0);  
  13.     Q_INVOKABLE inline void getAction() { qDebug() << "Hi, I am Harlen"; }  
  14.   
  15. signals:  
  16.   
  17. public slots:  
  18.   
  19. };  
  20.   
  21. #endif // TARGET_H  


[cpp]  view plain copy print ?
  1. //main.cpp  
  2. #include <QtGui/QApplication>  
  3. #include "qmlapplicationviewer.h"  
  4. #include <qdeclarative.h>  
  5. #include <QDeclarativeView>  
  6. #include <QDeclarativeContext>  
  7. #include "operation.h"  
  8. #include "target.h"  
  9.   
  10. int main(int argc, char *argv[])  
  11. {  
  12.     QApplication app(argc, argv);  
  13.     Operation myObj;  
  14.     QDeclarativeView view;  
  15.     QDeclarativeContext *ctxt = view.rootContext();  
  16.     ctxt->setContextProperty("obj", &myObj);  
  17.     view.setSource(QUrl("qml/QmlPointer/main.qml"));  
  18.     view.show();  
  19.   
  20.     return app.exec();  
  21. }  


[javascript]  view plain copy print ?
  1. import QtQuick 1.0  
  2.   
  3. Rectangle {  
  4.     width: 360  
  5.     height: 360  
  6.   
  7.     property variant pointer  
  8.     color: "skyblue"  
  9.     Text {  
  10.         text: "Harlen Tan"  
  11.         font.bold: true  
  12.         font.pointSize: 20  
  13.         anchors.centerIn: parent  
  14.     }  
  15.     MouseArea {  
  16.         anchors.fill: parent  
  17.         onClicked: {  
  18.            pointer = obj.createObject()  
  19.             pointer.getAction()  
  20.         }  
  21.     }  
  22. }  
注意上面的注释出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


[cpp]  view plain copy print ?
  1. #include <QtGui/QApplication>  
  2. #include "qmlapplicationviewer.h"  
  3. #include <qdeclarative.h>  
  4. #include <QDeclarativeView>  
  5. #include <QDeclarativeContext>  
  6. #include "operation.h"  
  7. #include "target.h"  
  8.   
  9. int main(int argc, char *argv[])  
  10. {  
  11.     QApplication app(argc, argv);  
  12.     Operation myObj;  
  13.     QDeclarativeView view;  
  14.     QDeclarativeContext *ctxt = view.rootContext();  
  15.     qmlRegisterType<Target>();//只是添加了这么一句话,向QML环境注册自定义类型  
  16.     ctxt->setContextProperty("obj", &myObj);  
  17.     view.setSource(QUrl("qml/QmlPointer/main.qml"));  
  18.     view.show();  
  19.   
  20.     return app.exec();  
  21. }  

点击运行,并点击对话框,调试信息输出: Hi, I am Harlen

运行成功了。后来发现,由于Target是继承自QObject,所以只需要将createObject返回类型改为QObject *,也可以运行成功。


QML中你所不知道的state


       最后一次写QML已经是2010年了,最近由于产品需要,重拾QML。之前nokia给我们培训QML的时候,对于state这个概念理解的不是很透彻。最近在做产品前期的QML热身,发现QML中的state有一种神奇的功能:历史记忆效应

       state核心就是体现了一个状态机的原理,处在某一状态去改变某些属性以达到目的。关于state如何使用的我这里就不说了,看看nokia的QML文档就知道state如何使用。我这里主要讲讲state的历史记忆效应。


从代码开始入手说起:




[javascript]  view plain copy print ?
  1. import QtQuick 1.0  
  2.   
  3. Rectangle {  
  4.     width: 360  
  5.     height: 360  
  6.     color: "#E4F3F9"  
  7.   
  8.   
  9.     Rectangle{  
  10.   
  11.         id: innerRec  
  12.         width: 60  
  13.         height: 30  
  14.         color: "#59A72C"  
  15.         state: "pre"  
  16.   
  17.         anchors.centerIn: parent  
  18.   
  19.         Text {  
  20.             id: txt  
  21.             anchors.centerIn: parent  
  22.             text: "Hello QML"  
  23.         }  
  24.   
  25.   
  26.         MouseArea{  
  27.             anchors.fill: parent  
  28.   
  29.             onClicked: if (innerRec.state == "pre")innerRec.state = "tag";else innerRec.state = "pre"  
  30.   
  31.         }  
  32.   
  33.         states: [  
  34.             State {  
  35.                 name: "tag"  
  36.                 PropertyChanges {  
  37.                     target: innerRec;  
  38.                     color: "#5CB4DA"  
  39.                 }  
  40.                 PropertyChanges {  
  41.                     target: txt;  
  42.                     text: "Hello Qt"  
  43.                 }  
  44.             }  
  45.         ]  
  46.   
  47.     }  
  48. }  

当我们第一次点击巨型区域的时候,该区域会变色,并字体变为“Hello Qt”。按照设计在点击一次恢复到原始状态,也就是“Hello QML”包括颜色。

比较正常的思维就是在点击一次通过priorityChange把颜色以及字体更改回去。

其实,不用。只需要把state属性更改为原始的“pri”就可以。因为每个state,包括原始的state,都会将当前QML对象的属性记录下来,这些属性包括颜色,文字,大小等等。

所以,上面的代码就能实现不断点击不断交替切换的效果。

效果如下:


QML与指针_第1张图片


QML与指针_第2张图片


你可能感兴趣的:(QML与指针)