一、介紹
爲了在C++中使用QML,用到了QtDeclarative中有三個主要的類:QDeclarativeEngine、QDeclarativeComponent和QDeclarativeContext。很多QML元素也有對應方法獲取用C++創建好的元素實例,如:Item<->QDeclarativeItem、Scale<->QGraphicsScale、Blur<->QGraphicsBlurEffect。爲了使用QtDeclarative,需要在工程文件中加入QT
+= declarative
二、QDeclarativeView
這是一個簡單易用的顯示類,繼承自QGraphicsView,主要用於快速的建立應用原型
示例如下:
#include <QApplication>
#include <QtDeclarative/QDeclarativeView>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QDeclarativeView canvas;
canvas.setSource(QUrl("main.qml"));
canvas.show();
return app.exec();
}
三、QDeclarativeEngine
若想要在Qt/C++中訪問QML,必須有一個QDeclarativeEngine實例。其提供了在C++中初始化QML控件的環境,通過它配置全局的QML設置。如果要提供不同的QML設置,需要實例化多個QDeclarativeEngine
四、QDeclarativeComponent
它對應了一個QML文檔的實例,用來加載QML文件。加載的內容可以是路徑,也可以是QML代碼(URL可以是本地的文件或者QNetworkAccessManager支持的協議訪問的網絡文件)。這個類還包含了QML文件的狀態信息,如:Null、Ready、Loading、Error等。
五、實例——初始化組件
// Create the engine(root context create automatically as well
QDeclarativeEngine engine;
// Create a QML component associated with the engine
// (Alternatively you could create an empty component and the set
// its contents with setData().)
QDeclarativeComponent component(&engine, QUrl("main.qml"));
// Instantiate the component (as no context is given to create(),
// the root context is used by default
QDeclarativeItem* item = qobject_cast<QDeclarativeItem*>(component.create());
// Add item to a view, etc...
六、QDeclarativeContext
1、每個QML組件初始化都會對應一個QDeclarativeContext(engine會自動建立root context)。子context可以根據需要而創建,它與父類的集成關係是由QDeclarativeEngine管理維護的。QML組件實例的數據都應該加入到engine的root環境中,同時,QML子組件的數據也應該加入到子環境中(sub-context)
2、使用context可以把C++的數據和對象暴露給QML
示例如下:
QQmlApplicationEngine engine;
// engine.rootContext() returns a QDeclarativeContext*
engine.rootContext()->setContextProperty("myBackgroundColor",
QColor(Qt::lightsteelblue));
QDeclarativeComponent component(&engine, "main.qml");
QObject* window = component.create(); // Create using the root context
3、這種機制可以被用來爲QML中的View提供C++端的Model
4、前面提到過,context是具有繼承關係的,控件初始化的時候,可以使用對應的context裏的數據,也可以訪問到祖先context的數據。對於重複定義的數據,子context中的定義將會覆蓋context中的定義
示例如下:
QDeclarativeEngine engine;
QDeclarativeContext context1(engine.rootContext());
QDeclarativeContext context2(&context1);
QDeclarativeContext context3(&context2);
context1.setContextProperty("a", 12);
context2.setContextProperty("b", 13);
context3.setContextProperty("a", 14);
context3.setContextProperty("c", 14);
// Instantiate QDeclarativeComponents using the sub-context
component1.create(&context1); // a = 12
component2.create(&context2); // a = 12, b = 13
component3.create(&context3); // a = 14, b = 13, c = 14
七、結構化數據
1、如果有很多數據需要暴露給QML,可以使用默認對象(default object)代替。所有默認對象中定義的屬性都可以在QML控件中通過名字訪問到。通過這種方式暴露的數據,可以在QML端被修改。同時,使用默認對象的速度比多次調用setContextProperty()快。
2、多個默認對象可以加到同一個QML組件實例中,先添加的默認對象是不會被後面添加的對象所覆蓋。於此不同的是,使用setContextProperty()設置的屬性,會被新的屬性覆蓋。
3、Model數據通常都是由C++端代碼動態提供的,而不是一個靜態QML的數據Model。在delegate裏面通過model屬性可以訪問到數據模型,這是一個默認的屬性。
4、可以使用的C++端的數據模型有:
QList<QObject*> —— model.modelData.xxx(xxx是屬性)
QAbstractDataModel —— model.display(decoration)
QStringList —— model.modelData
示例如下:
// MyDataSet.h
class MyData : ... {
...
// The NOTIFY signal informs about changes in the property's value
Q_PROPERTY(QAbstractItemModel* myModel READ model NOTIFY modelChanged)
Q_PROPERTY(QString text READ text NOTIFY textChanged)
...
};
// SomeOtherPieceOfCode.cpp exposes the QObject using e.g. a sub-context
QDeclarativeEngine engine;
QDeclarativeContext context(engine.rootContext());
context.addContextObject(new MyDataSet(...));
QDeclarativeComponent component(&engine, "ListView {model = myModel}");
component.create(&context);
八、QML調用C++方法
所有QObject對象的public的槽方法都可以在QML中調用,如果你不想你的方法是槽方法,可以使用Q_INVOKABLE(如:Q_INVOKABLE void myMethod();)
示例一:
// In C++:
class LEDBlinker : public QObject {
Q_OBJECT
// ...
public slots:
bool isRunning();
void start();
void stop();
};
int main(int argc, char *argv[]){
// ...
QDeclarativeContext* context = engine->rootContext();
context->setContextProperty("ledBlinker", new LEDBlinker);
// ...
}
// In QML:
Rectangle {
MouseArea {
anchors.fill: parent
onClicked: {
if (ledBlinker.isRunning())
ledBlinker.stop()
else
ledBlinker.start();
}
}
}
示例二:
需要注意的是,我們完全可以通過聲明一個“running”屬性來達到同樣的效果,這樣的話,代碼會更加優雅,省略掉了isRunning()和setRunning()兩個方法
// In C++:
class LEDBlinker : public QObject {
Q_OBJECT
Q_PROPERTY(bool running READ isRunning WRITE setRunning)
// ...
};
// In QML:
Rectangle {
MouseArea {
anchors.fill: parent
onClicked: ledBlinker.running = !ledBlinker.running
}
}
九、在C++中調用QML方法
很明顯,反過來在C++中調用QML的方法也是可以的。在QML中定義的方法在C++中都是一個槽函數,同時,在QML中定義的信號可以與C++中定義的槽函數連接。
十、網絡組件
前面討論過,QML組件可以通過網絡加載,這種方式可能會花費些時間,因爲網絡總是有一定的延時。所以,在C++中初始化網絡上的QML控件的時候,需要觀察控件的加載狀態,只有當狀態爲Ready後,才能調用created()創建控件。
示例如下:
MyObject::MyObject() {
component = new QDeclarativeComponent(engine,
QUrl("http://www.example.com/mail.qml"));
// Check for status before creating the object - notice that this kind of
// code could (should?) be used regardless of where the component is located!
if(component->isLoading())
connect(component, SIGNAL(statusChanged(QDeclarativeComponent::Status),
this, SLOT(continueLoading())));
else
continueLoading(); // Not a network-based resource, load straight away
}
// A slot that emits the States parameter of the signal and uses the isXxxx()
// functions instead to check the status - both approaches work the same way
void MyObject::continueLoading() {
if(component->isError()){
qWarning() << component->errors();
} else if(component->isReady()) {
QObject* myObject = component->create();
} // The other status checks here ...
}
十一、QML Components in Resource File
在Qt工程中最方便的方法還是把QML組件添加到資源文件中,所有的JavaScript文件也可以被放在資源文件中。這樣更加容易訪問文件,放入資源文件後,我們無需知道文件的路徑,只需要使用一個指向資源文件中的文件的URL就可以了。同時,資源文件可以編譯到二進制程序中,這樣資源文件就可以與二進制文件一起分發了,非常的方便。
示例如下:
// MyApp.qrc
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource> <file>qml/main.qml</file> </qresource>
</RCC>
// MyObject.cpp
MyObject::MyObject() {
component = new QDeclarativeComponent(engine,
QUrl("qrc:/qml/main.qml"));
if(!component->isError()){
QObject* myObject = component->create();
}
}
Image {
source: "images/background.png"
}