QML筆記整理——在Qt/C++應用中使用QML

一、介紹
爲了在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"
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章