在 C++ 中, 怎麼和 QML 物件互動 ?
以下內容為本人的學習筆記,如需要轉載,請宣告原文連結[englyf]https://www.jianshu.com/p/66649d0e9bb6
請注意這裡使用的環境是
IDE:Qt5.12
Lang:C++、QML
Compiler:vs2017x64
所有 QML 物件都是 QObject 的派生型別, 無論這個物件是引擎的內部實現或者是由第三方源定義而來。也就是說,QML 引擎可以利用 Qt 的元物件系統(Meta Object System
)去動態例項化任何的 QML 物件型別,以及檢查被建立的物件。
所以說,在 C++ 程式碼中,無論是因為要顯示一個可渲染的 QML 物件,或者需要整合非視覺化的 QML 物件資料,建立 QML 物件都是非常容易實現的。當一個 QML 物件被建立之後,為了讀寫這個物件的屬性,呼叫這個物件的方法和接收這個物件的訊號通知,都能夠在 C++ 中對此進行檢查。
怎麼在 C++ 中載入 QML 物件呢?
我們可以使用 Qt 提供的兩個類來載入 QML 文件,分別是
QQmlComponent 載入一個 QML 文件後,生成一個 C++ 物件,可以在 C++ 程式碼中對這個物件進行修改。
QQuickView 同樣可以做到這些,但QQuickView 是QWindow 的一個派生型別,載入之後的物件也會被渲染出來。QQuickView 通常被用來將視覺化的 QML 物件整合到應用的使用者介面中。
下面看個舉個栗子,
// textItem.qml import QtQuick 2.0 Item { width: 100; height: 100 }
既可以用QQmlComponent 也可以用QQuickView 將上面這個 QML 檔案載入到 C++ 程式碼中。如果使用QQmlComponent 則需要呼叫QQmlComponent::create () 建立元件的例項並返回物件(例項)指標,而使用QQuickView
就會自動建立元件的例項,然後呼叫QQuickView::rootObject ()
獲取例項指標。
// Using QQmlComponent QQmlEngine engine; QQmlComponent component(&engine, QUrl::fromLocalFile("textItem.qml")); QObject *object = component.create(); ... delete object;
// Using QQuickView QQuickView view; view.setSource(QUrl::fromLocalFile("textItem.qml")); view.show(); QObject *object = view.rootObject();
怎麼在 C++ 中設定 QML 物件的屬性呢?
現在可以呼叫上面例項的QObject::setProperty () 或者借用QQmlProperty::write () 來修改 Item 的屬性:
object->setProperty("width", 500); QQmlProperty(object, "width").write(500);
這兩種方式是有區別的,後者 QQmlProperty::write() 除了設定屬性之外,還會移除原來的繫結,所以這裡要特別注意一下。
比如,假設在 QML 檔案中,已將 width 繫結到 height:
width: height
如果設定屬性的方式是呼叫 object->setProperty("width", 500),那麼 width 的值只是臨時被設定為 500,一旦 height 改變了,width也是會跟隨改變的,因為繫結關係沒有被移除。但是,如果設定屬性的方式是呼叫 QQmlProperty(object, "width").write(500) ,那麼width 的值不會再跟隨 height 的改變而改變,因為原來的繫結關係已被移除。
此外呢,設定屬性還有一種方法 就是,先將物件強制轉換為實際型別,然後使用編譯時安全性呼叫方法。在上面的檔案 textItem.qml 中,Item 由類QQuickItem 定義:
QQuickItem *item = qobject_cast<QQuickItem*>(object); item->setWidth(500);
怎麼在 C++ 中按照物件名訪問已載入的 QML 物件呢?
QML 元件實際上是一組具有子節點的物件樹,子節點同樣有兄弟物件和子物件。可以使用QObject::findChild () 並傳入屬性值QObject::objectName (也即是物件名) 來定位到 QML 元件的子物件。下面看看我這的栗子大不大:
// textItem.qml import QtQuick 2.0 Item { width: 100; height: 100 Rectangle { anchors.fill: parent objectName: "rect" } }
可以看到根項是 Item,然後還有個子項Rectangle 。可以通過下面的方式定位子物件:
QObject *rect = object->findChild<QObject*>("rect"); if (rect) rect->setProperty("color", "red");
這裡要注意一下,一個物件可以有多個相同 objectName(屬性)的子物件。比如,ListView 建立其委託的多個例項,如果使用特定的 objectName 宣告其委託,則 ListView 將具有多個相同 objectName 的子節點。這種情況下,可以使用QObject::findChildren () 來查詢符合 objectName 的所有子節點。
特別注意:雖然可以在 C++ 中訪問並且操作 QML 物件,但是除了測試和原型設計之外,這種方法是不推薦的!QML 和 C++ 整合的優勢之一就是實現與 C++ 邏輯和資料集後端分離的 UI 介面,如果在 C++ 中直接操作 QML 將意味著放棄優勢。這種方法也使得在不影響對應 C++ 部分的前提下去改動 QML UI 變得困難。
怎麼在 C++ 中訪問 QML 物件型別的成員呢?
訪問屬性
任何 QML 物件中宣告的屬性都自動可以在 C++ 程式碼中訪問。下面再來個栗子:
// textItem.qml import QtQuick 2.0 Item { property int a: 100 }
在上面 QML 檔案中宣告的屬性 a 的值可以使用QQmlProperty 來讀寫,或者用QObject::setProperty () 來寫屬性值和用QObject::property () 來讀屬性值:
QQmlEngine engine; QQmlComponent component(&engine, "MyItem.qml"); QObject *object = component.create(); qDebug() << "Property value:" << QQmlProperty::read(object, "someNumber").toInt(); QQmlProperty::write(object, "someNumber", 5000); qDebug() << "Property value:" << object->property("someNumber").toInt(); object->setProperty("someNumber", 100);
為了確保 QML 引擎知道屬性的改變,你應該始終採用QObject::setProperty (),QQmlProperty 或者QMetaProperty::write () 來設定 QML 物件的屬性值。比如,假如你有個自定義型別 PushButton, 在內部有個屬性 buttonText 並且和成員變數 m_buttonText 關聯。 像下面這樣子直接修改成員變數 m_buttonText 是不受推薦的:
// un-recommended QQmlComponent component(engine, "textItem.qml"); PushButton *button = qobject_cast<PushButton*>(component.create()); button->m_buttonText = "clicked !";
如果變數 m_buttonText 被直接修改,那麼QML 引擎將不會知道屬性改變了,因為這種操作完美地躲開了 Qt 的meta-object system 。後果就是,繫結的 buttonText 屬性不會被更新,而且屬性變更訊號槽 onButtonTextChanged() 不會被呼叫。
訪問 QML 方法
由於所有的 QML 方法都暴露給了元物件系統 Meta-object system, 所以在 C++ 程式碼中可以通過QMetaObject::invokeMethod () 呼叫對應的 QML 方法,並且輸入的引數和來自 QML 中的返回值在 C++ 中通常被轉換成QVariant 值。下面有個例子:
// textItem.qml import QtQuick 2.0 Item { function qmlFunction(msg) { console.log("Got msg:", msg) return "return value" } }
// main.cpp QQmlEngine engine; QQmlComponent component(&engine, "textItem.qml"); QObject *object = component.create(); QVariant returnedValue; QVariant msg = "hi from C++"; QMetaObject::invokeMethod(object, "qmlFunction", Q_RETURN_ARG(QVariant, returnedValue), Q_ARG(QVariant, msg)); qDebug() << "value returned from QML :" << returnedValue.toString(); delete object;
這裡要注意一下,引數Q_RETURN_ARG () andQ_ARG () 必須指定為QVariant 型別, 是因為QVariant 是用於 QML 方法輸入引數和返回值的通用資料型別。
連線 QML 訊號
// 未完待續 !!!
//
參考英文資料[Qt]https://doc.qt.io/qt-5/qtqml-cppintegration-interactqmlfromcpp.html