QML和C++混合編程--在C++應用程序中使用QML

    QML很容易從C++進行擴展,在Qt Declarative模塊的類允許從C++加載和操縱QML組件,而且通過Qt的元素對象系統,QML和C++對象是可以容易通過Qt信號和槽進行通信的。

    QML和C++混合編程原因:

    a. 使用定義在C++源中的功能(例如,當使用一個C++基於Qt的數據模型,或者調用第三方C++庫中的函數)

    b. 訪問Qt Declarative模塊中的功能(例如,使用QDeclarativeImageProvider來動態創建圖像)

    c. 編寫自己的QML元素

    要使用Qt Declarative模塊,必須先包含和連接合適的模塊。Using QML in C++ Application。

1. 核心模塊類

    Qt Declarative模塊提供了一組C++接口來實現從C++擴展QML應用程序,將QML嵌入到C++應用程序中。Qt Declarative模塊中有幾個核心類提供了完成該任務的基本功能。

    a. QDeclarativeEngine: 一個QML引擎提供了執行QML代碼的環境,每個應用程序至少需要一個引擎實例。

    b. QDeclarativeComponent: 一個組件封裝了一個QML文件

    c. QDeclarativeContext: 上下文運行應用程序將數據暴露給引擎創建的QML組件

    QDeclarativeEngine允許將全局設置應用到它的所有QML組件實例上,例如,QNetworkAccessManager用於網絡通信,一個文件路徑用作永久性存儲等。而QDeclarativeComponent用來加載QML文件,每一個QDeclarativeComponent實例表示一個獨立的文件。一個QDeclarativeComponent可以從一個URL、一個QML文件的路徑或者原始的QML代碼創建,可以通過QDeclarativeComponent::create()函數進行實例化:

QDeclarativeEngine engine;
QDeclarativeComponent component(&engine, QUrl::fromLoacalFile("MyRectangle.qml"));
QObject * ractangleInstance = component.create();
//...
delete rectangleInstance;
   QML 文件也可以使用QDeclarativeView加載,這個類提供了一個方便的基於QWidget的視圖,它將QML組件嵌入到一個基於QGraphicsView的應用程序。

2. 混合使用QML和C++的方法

    這裏提供了幾種方法來通過C++擴展QML應用程序,

    a. 加載一個QML組件,然後通過C++對其進行操作

    b. 直接將一個C++對象及其屬性嵌入到QML組件;

    c. 定義一個新的QML元素(通過基於QObject的C++類)並在QML代碼中創建它們

    這些方法不具有排他性,可以在應用程序中同時使用多種方法。

3. 從C++中加載一個QML組件

    一個QML文件可以使用QDeclarativeComponent或者QDeclarativeView加載。QDeclarativeComponent將一個QML組件加載爲一個C++對象;QDeclarativeView直接將QML組件加載到一個QGraphicsView中。

    如果使用QDeclarativeComponent,需要調用QDeclarativeComponent::create()來創建該組件的一個新的實例:

QDeclarativeEngine engine;
QDeclarativeComponent component(&engine, QUrl::fromLocalFile("MyIem.qml"));
QObject * object = component.create();
 ...
delete object;
    如果使用QDeclarativeView,那麼它會自動創建該組件的實例,後面可以通過函數QDeclarativeView::rootObject()來獲取實例:
QDeclarativeView view;
view.setSource(QUrl::formLoacalFile("MyItem.qml"));
view.show();
QObject * object = view.rootObject
    這裏的object是新創建的MyItem.qml組件的實例,可以使用QObject::setProperty()或者QDeclarativeProperty來修改該項目的屬性:

objec->setProperty("width", 200);
QDeclarativeProperty(object, "width").write(500);
    另外,也可以先將對象轉換爲真實的類型,然後電泳編譯時安全的函數。在這裏MyItem.qml的基對象是一個Item,它由QDeclarativeItem類進行定義

QDeclarativeItem * item = qobject_cast<QDeclarativeItem * >(object);
item->setWidth(500);
    QML組件本質上是一個包含孩子的對象樹,而孩子對象可以擁有兄弟和它自己的孩子。可以使用QObject::objectNam屬性和QObject::findChild()函數來定位QML組件的子對象。MyItem.qml中項目有一個Rectangle子項目:

import QtQuick 1.0

Item {
    width: 100; height: 100
    Rectangle {
        anchors.fill: parent
        objectName: "rect"
    }
}
    定位孩子:

QObject * rect = object->findChild<QObject *>("rect");
if(rect)
    rect->setProperty("color", "red")
    如果objectName用在一個ListView或者Repeater的委託中或者其他一些使用 委託創建多個實例的元素中,那麼會有多個孩子使用個相同的objectName。這種情況下使用QObject::findChildren()來查找所有匹配objectName的孩子。

4. 在QML組件中嵌入C++對象

    在加載一個QML場景到C++應用程序中時,直接嵌入C++數據到QML對象是很有用的。這個可以通過QDeclarativeContext來實現,它可以將數據暴露給QML組件的上下文,允許將數據從C++注入到QML中。例如,這裏有一個QML項目引用了一個currentDateTime值,而該值並沒有在當前的作用域:

//MyItem.qml
import QtQuick 1.0

Text { text: currentDateTime}
    這裏的currentDateTime值可以直接在加載了該QML組件的C++應用程序中設置,這裏需要使用QDeclarativeContext::setContextProperty():

QDeclarativeView view;
view.rootContext()-> setContextProperty("currentDateTime", QDateTime::currentDateTime());
view.setSource(QUrl::fromLoacalFile("MyItem.qml"));
view.show();
    上下文屬性可以使用QVariant或者QObject * 值,這也就意味着自定義的C++對象也可以使用這種方式進行注入,而且這些對象可以直接在QML中進行讀取和修改。

如果在QML中調用一個C++函數,那麼這個函數必須是一個Qt槽,或者該函數使用Q_INVOKEABLE宏進行標記,這裏就是使用了該宏對函數進行了標記。

applicationdata.h文件:

#include <QObject>
#include <QDateTime>

class ApplicationData : public QObject
{
     Q_OBJECT
     public:
         Q_INVOKABLE QDateTime getCurrentData\eTime() const {
              return QDateTime::currentDateTime();
         }
}
qml加載

QDeclarativeView view;
Application data;
view.rootContext()->setContextProperty("applicationData", &data);
view.setSource(QUrl::fromLocalFile("../myDeclarativeContext/MyItem.qml"));
view.show();

      使用了QDeclarativeContext::setContextProperty()設置了applicationData對象,可以在QML文件中直接訪問它。最後向項目添加新的MyItem.qml文件。

import QtQuick 1.0

Text{text:applicationData.getCurrentDateTime()}
    C++代碼中調用QML組件中的函數,使用QMetaObject::invokeMethod()函數來實現。如MyItem.qml中有一個函數:

import QtQuick 1.0

Item {
    function myQmlFunction(msg) {
        console.log("Got message: ", msg);
        return "some return value"
    }
}
    C++應用程序使用該QML函數:

QDeclarativeEngine engine;
QDeclarativeComponent component(&engine, "MyItem.qml");
QObject * object = component.crate();
QVariant msg = "Hello from C++";
QMetaObject::invokeMethod(object, "myQmlFunction", Q_RETURN_ARG(QVarient, returnedVealue), Q_ARG(QVariant, msg));
qDebug<<"QML function returned:" << returnedValue.toString();
delete object;

     在QMetaObject::invokeMethod()函數的Q_RETURN_ARG()和Q_ARG()參數必須指定爲QVariant類型,分別用於接收返回值和傳遞函數參數。

     如果QML需要從上下文屬性中接收一個信號,可以使用Connection元素進行關聯。如果ApplicationData擁有一個叫dataChanged()的信號,那麼可以在Connection對象中使用一個onDataChanged處理器來關聯該信號:

Text {
    text:applicationData.getCurrentDateTime()
    Connections {
         target: applicationData
         onDataChanged: console.log("The application data changed!")
    }
}
    而對於QML中的信號在C++中是自動可用的,可以像關聯普通的Qt C++信號一樣來對其進行關聯。

import QtQuick 1.0

Item{
    id: item
    width:100; heigth:100
    signal qmlSignal(string msg)
    Mouse {
        anchors.fill:parent
        onClicked: item.qmlSingal("Hello from QML")     
   }
}
     假如在C++代碼中有一個MyClass類,它有一個cppSlot()槽,那麼可以這樣進行關聯:    
QDeclarativeView view(QUrl::fromLocalFile("MyItem.qml"));
QObject * item = view.rootObject();

MyClass myClass;
QObject::connect(item, SIGNAL(qmlSignal(QString)),&myClass, SLOT(cppSlot(QString)));

5. 定義一個新的QML元素

    新的QML元素可以在QML元素中進行定義,它們也可以使用C++類來定義。事實上,很多核心QML元素都是通過C++類實現的。當使用這些元素中的一個來創建一個QML對象時,也就是創建了一個基於QObject的C++類的實例並且設置了屬性。例如,下面的ImageViewer類有一個image路徑屬性:

#include <QtCore>
#include <QtDeclarative>

class ImageViewer: public QdeclarativeItem//繼承自QDeclarativeItem
{
    Q_OBJECT
    Q_PROPERTY(QUrl Imge READ Imge WRITE setImge NOTIFY ImgeChanged)

public:
    void setImage(const QUrl &url);
    QUrl image() const;

signals:
    void imageChanged();
}
      使用qmlRegisterType()使用在QML引擎進行註冊:

qmlRegisterType<ImageViewer>("MyLibrary", 1, 0, "ImageViewer");

    這時,任何在C++應用程序或者插件中加載的QML代碼都可以創建一個ImageViewer對象:

    import MyLibrary 1.0

    ImageViewer{image: "simle.png"}

自定義的C++類型不一定非要繼承自QDeclarativeItem,只有在其是一個可顯示的項目時纔是必須的。如果該項目時不可以顯示的,那麼它可以繼承自QObject。

新的QML元素的內容:Qt 幫助--Writing QML extensions with C++關鍵字。



發佈了66 篇原創文章 · 獲贊 16 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章