QML與指針
在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(); }
點擊運行,並點擊對話框,調試信息輸出:Hi, I am Harlen
運行成功了。後來發現,由於Target是繼承自QObject,所以隻需要將createObject返回類型改為QObject *,也可以運行成功。
最後更新:2017-04-02 06:51:50