閱讀732 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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

  上一篇:go Android 應用程序獲得版本號
  下一篇:go SSH整合學習筆記之struts2+spring+hibernate+c3p0