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++關鍵字。