QML物件與C\++互動 === 原文:[Interacting with QML Objects from C++](http://doc.qt.io/qt-5/qtqml-cppintegration-interactqmlfromcpp.html) ## 將QML物件載入C++中 `QQmlComponent和`和`QQuickView`都可以將QML載入成C++ Object並且透過C++的程式碼去改變它的內容。但是`QQuickView`的不同處在於它是一個**QWindow**的子類別,被載入的QML將會被渲染並且顯示出來,所以`QQuickView`通常被用來包裝可以被顯示的QML,作為使用者界面。 首先,這裡有一個`MyQml.qml`文件 ```qml import QtQuick 2.0 //也可以是較高的版本 Item { width : 200 height : 200 } ``` ### 透過**QQuickView**載入QML ```cpp QQuickView view ; view.setSource(QUrl::fromLocalFile("MyQml.qml")) ; view.show() ; QObject *object = view.rootObject() ; ``` QQuickView會自動建立QML的實例,所以可以直接透過呼叫`QQuickView::rootObject()`這個函式就可以值得到。 ### 透過**QQmlComponent**載入QML ```cpp QQmlEngine engine ; QQmlComponent component ( &engine , QUrl::fromLocalFile("MyQml.qml") ) ; QObject *object = component.create() ; ``` 利用`QQmlComponent`如果想要得到QML的實例,與`QQuickView`不同的是要透過`QQmlComponent::create()`來建立QML的實例。 可以透過這種方法去改變物件的特性 ```cpp object->setProperty( "width" , 500 ) ; ``` ```cpp QQmlProperty(object,"width").write(500) ; ``` 不過其實封裝好的QML真正類別是`QQuickItem`,所以也可以透過這種方法去改變物件的特性,以確保編譯時能正常執行(compile-time safety)。 ```cpp QQuickItem *item = qobject_cast<QQuickItem*>(object) ; item->setWidth(500) ``` ## 利用objectName存取QML物件 ### findChild範例 因為QML的rootObject底下還有很多的QML Object,形成一個樹狀結構,所以常常需要對其下的子物件做一些控制。可以透過`QObject::findChild<class>`來找到指定名稱的物件。 **MyQml.qml:** ```qml import QtQuick 2.0 //或更高版本 Item { width : 100 , height : 100 , Rectangle { anchors.fill = parent objectName = "rect" } } ``` 可以透過下面這段程式找到objectName為rect的物件 ```cpp QObject *rect = object->findChild<QObject*>("rect") ; if ( rect ) { rect -> setProperty( "color" , "red" ) ; } ``` 如果有多個物件的名稱符合指定的名稱,或是在`ListView`物件會為它的每一個`delegate`創件一個實例,也就是說會存在多個相同名稱的實例,這時候,`QObject::findChild<class>()`就會就會利用`QList<class>`將所有的符合的物件回傳回來。 ### findChild使用方式 找出所有parentWidget中objectName為"widgetname"的子組件。 ```cpp QList<QWidget*> list = parentWidget.findChild<QWidget*>("widgetname") ; ``` 找出所有符合型態的組件,只要不要傳任何參數給`QObject::findChild<class>()`這樣就可以取得parentWidget下面所有類別為`QPushButton`的組件。 ```cpp QList<QPushButton*> list = parentWidget.findChild<QPushButton*>() ; ``` 僅找出**直接子組件**,將目標組件名設為`QString`空字串。 ```cpp QList<QPushButton*> list = parentWidget.findChild<QPushButton*>( QString() , Qt::FindDirectChildernOnly ) ; ``` ## 從C++中獲取QML物件的Properties ### Properties宣告 ```qml import QtQuick 2.0 //或更高 Item { property int number = 10 } ``` 我們可以先用上述的兩種方法取得QML的實例: [QQuickView](#透過qquickview載入qml) ```cpp QObject *object = view.rootObject() ; ``` [QQmlComponent](#透過qquickview載入qml) ```cpp QObject *object = component.create() ; ``` 接著就可以透過`QQmlProperty`或是`QObject::setProperty()`和`QObject::property()`來對QML中的properties做處理。 **QQmlProperty** ```cpp QQmlProperty::read(object,"number").toInt() ; // 10 QQmlProperty::write(object,"number",5) ; // 將number設為5 ``` **QObject::setProperty()以及QObject::property()** ```cpp object->property("number").toInt() ; // 10 object->setProperty("number",5) ; // 將number設為5 ``` ## 呼叫QML中的函式 所有QML中的函式都可以透過`QMetaObject::invokeMethod()`來進行呼叫。 ```qml import QtQuick 2.0 Item { function qmlFunction ( msg ) { console.log(msg) ; return "Hello World!" ; } } ``` 首先一樣先透過這兩種方法取得QML的實例: [QQuickView](#透過qquickview載入qml) ```cpp QObject *object = view.rootObject() ; ``` [QQmlComponent](#透過qquickview載入qml) ```cpp QObject *object = component.create() ; ``` 接著就可以透過`QMetaObject::invokeMethod()`對QML物件中函式進行呼叫: ```cpp QMetaObject::invokeMethod(object,"qmlFunction", Q_RETURN_ARG(QVariant, returnValue, // 接收QML函式回傳的值。 Q_ARG(QVariant, msg)) ; // 傳送一個QVariant型態的msg至qmlFunction()中 ``` 在QML與C++的互相呼叫中,`Q_RETURN_ARG`與`Q_ARG`必須皆為`QVariant`型態。 ## 連接QML中的Signal與Slot signal與slot在Qt中是一個重要的功能,因此也可以在QML中使用signal與slot並利用`QObject::connect()`來讓界面與C++程式碼進行事件的觸發。 ### Slot in C++ and Signal in QML 在QML中宣告Signal ```qml import QtQuick 2.0 // 或更高 Item{ width : 300 height : 300 id : item signal qmlSignal( string msg ) MouseArea { anchors.fill : parent onClicked : item.qmlString ("Clicked") } } ``` 然後在C++中建立一個物件,內含Slot ```cpp class SlotExample : public QObject { Q_OBJECT // Q_OBJECT macro public slots : void cppSlot ( QString msg ) ; } ``` ```hpp SlotExamplge::cppSlot(QString msg) { console.log("Signal Recieve : " + msg); } ``` 接下來利用`QObject::connect()`進行連結,但首先我們要先取得QML的實例,透過上面提過得兩種方法: [QQuickView](#透過qquickview載入qml) ```cpp QObject *object = view.rootObject() ; ``` [QQmlComponent](#透過qquickview載入qml) ```cpp QObject *object = component.create() ; ``` 接著進行連結 ``` SlotExample *example = new SlotExample; QObject::connect(object,SIGNAL(qmlSignal(QString)),example,SLOT(cppSlot(QString))) ; ``` 這樣就可以在範例中的`MouseArea`觸發`onClicked`的時候將signal送到`SlotExample`中了 **注意:** 當一個signal被建立之後,QML也會同時建立另一個signalHandler來處理這個signal,因此我們也可以透過`onQmlSignal:`這個欄位來決定當qmlSignal訊號發出時QML中的相應工作。 ```qml import QtQuick 2.0 // 或更高 Item{ width : 300 height : 300 id : item signal qmlSignal( string msg ) onQmlSignal : console.log("QML Signal triggered!") MouseArea { anchors.fill : parent onClicked : item.qmlString ("Clicked") } } ``` ### Slot in QML and Signal in C++ 在QML中建立Slot,不過在QML中並沒有`slot`關鍵字,因此我們使用function來作為slot。 值得注意的是,如果從C++中發出來的訊號為`action`那們最好將QML中的slot命名為onAction會是比較好的開發習觀。 ```qml import QtQuick 2.0 // 或更高 Item{ width : 300 height : 300 id : item function onCppSignal ( string msg ) { console.log("CppSignal Recieve : " + msg ) ; } MouseArea { anchors.fill : parent } } ``` 另外再建立一個物件用來發出訊號: ```cpp class SignalExample : public QObject { Q_OBJECT // Q_OBJECT macro signals : void cppSignal ( QString msg ) ; } ``` 接著將其連接,一樣要先取得QML的實例,在這裡就不再贅述: ``` SignalExample *example = new SignalExample; QObject::connect(example,SIGNAL(cppSignal(QString)),object,SLOT(onCppSignal())) ; ``` 之後只要呼叫`emit cppSignal()`就可以直接將信號傳送到QML的slot中了。