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中了。