QMLとC++のバインディング

[2016/08/17 19:51 - 少し手直し]
Qtをさわり始めてQMLとC++のバインディングで引っかかったのでまとめてみました。間違いやもっと良い方法があればご指摘下さい。

以下では、QtCreatorでQtQuickアプリケーションのプロジェクトを作成した際に自動で生成される下記のソースコードをベースにします。(ここではui.qmlは使用しません)

```c++:main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}
```

```c++:main.qml
import QtQuick 2.5
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    MouseArea {
        anchors.fill: parent
        onClicked: {
            Qt.quit();
        }
    }

    Text {
        text: qsTr("Hello World")
        anchors.centerIn: parent
    }
}
```


#C++からQMLオブジェクトへアクセス
いろいろな方法があるようですが、ここではobjectNameでアクセスする方法を取り上げます。

main.cppではroot要素へのアクセスを経て、子要素であるTextへQML側で定義するobjectNameでアクセスする。

```c++:main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    /* added */
    //(1) Get an access to root object and qml object by objectName "textObject" defined in main.qml
    QObject *rootObject = engine.rootObjects().first();
    QObject *qmlObject = rootObject->findChild<QObject*>("textObject");
    // (2) set an object property
    qmlObject->setProperty("text", "Text from C++");
    //----------
    
    return app.exec();
}
```

QML側ではアクセスしたい子要素にobjectNameを定義する。

```c++:main.qml
import QtQuick 2.5
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    MouseArea {
        anchors.fill: parent
        onClicked: {
            Qt.quit();
        }
    }

    Text {
        /*added*/
        objectName: ”textObject”
        // ----------
        text: qsTr("Hello World")
        anchors.centerIn: parent
    }
}
```
**結果**
![cpp2qml.png](https://qiita-image-store.s3.amazonaws.com/0/120538/6764f854-5754-56d9-9075-78e49e887336.png)
(見にくかったので色をつけてフォントを大きくしています)

なお、オフィシャルではC++からQMLオブジェクトへのアクセスはC++とQMLの独立性の観点から推奨していないようです。
> **Warning**: While it is possible to use C++ to access and manipulate QML objects deep into the object tree, we recommend that you do not take this approach outside of application testing and prototyping. One strength of QML and C++ integration is the ability to implement the QML user interface separately from the C++ logic and dataset backend, and this strategy breaks if the C++ side reaches deep into the QML components to manipulate them directly. This would make it difficult to, for example, swap a QML view component for another view, if the new component was missing a required ``objectName``. It is better for the C++ implementation to know as little as possible about the QML user interface implementation and the composition of the QML object tree.

#QMLからC++のクラスへアクセス
ContextPropertyにQObjectインスタンスを埋め込んで、QMLからメソッドをコールする例を挙げてみます。(オフィシャルドキュメントではQDateTimeを返すようになっていましたがもっとシンプルにstringを返す形にしてみました)

まずヘッダファイルの準備。

```c++:applicationdata.h
#ifndef APPLICATIONDATA_H
#define APPLICATIONDATA_H

#include <QObject>
#include <QString>

class ApplicationData:public QObject
{
    Q_OBJECT
public:
    Q_INVOKABLE QString getTextFromCpp(){
        return QString("This is the text from C++");
    }
};

#endif // APPLICATIONDATA_H
```

続いて、cppファイルとqmlファイルを変更。

```c++:main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>

/*added*/
#include <QQmlContext>
#include "applicationdata.h"
//----------

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    /*added*/
    ApplicationData data;
    engine.rootContext()->setContextProperty("applicationData",&data);
    //----------

    return app.exec();
}

```

```c++:main.qml
import QtQuick 2.5
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    MouseArea {
        anchors.fill: parent
        onClicked: {
            Qt.quit();
        }
    }

    Text {
        /*changed*/
        //text: qsTr("Hello World")
        text: applicationData.getTextFromCpp()
        //----------
        anchors.centerIn: parent
    }


}
```

**結果**
![qml2cpp.png](https://qiita-image-store.s3.amazonaws.com/0/120538/a2d6f133-7708-45fc-de54-759b805d66dc.png)


# Signal & Slot
今回は、QMLのボタンクリックでC++側へシグナルを出し、C++側のスロットで受け取ったら、逆にC++側からQML側へシグナルを送るというのを例にします。

## C++でConnect
まずは、C++側でConnectする例。cppからのシグナルでQML側のSlotが認識されない、No such slotというエラーで泣かされました。引数をQVariantにするとうまくいく。

```c++:main.qml
import QtQuick 2.5
import QtQuick.Window 2.2
/*added*/
import QtQuick.Controls 2.0
//----------

Window {
    /*added*/
    signal qmlSignal(string msg);
    function qmlSlot(text)
    {
        console.log("qmlSlot is called with the text: " + text)
        textField.text = text;
    }
    //----------

    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    /*deleted*/
//    MouseArea {
//        anchors.fill: parent
//        onClicked: {
//            Qt.quit();
//        }
//    }

    Text {
        /*added*/
        id:textField
        //----------
        text: qsTr("Hello World")
        anchors.centerIn: parent
    }

    /*added*/
    Button{
        id:aButton
        text:"Emit Signal!"
        anchors.centerIn: parent
        anchors.verticalCenterOffset: 30
        onClicked: qmlSignal("Hello from QML")
    }
    //----------

}
```

```c++:cppsignalslot.h
#ifndef CPPSIGNALSLOT_H
#define CPPSIGNALSLOT_H

#include <QObject>
#include <QVariant>

class CppSignalSlot : public QObject
{
    Q_OBJECT
public:
    explicit CppSignalSlot(QObject *parent = 0);

signals:
    void cppSignal(QVariant text);

public slots:
    void cppSlot(QString msg);
};

#endif // CPPSIGNALSLOT_H
```

```c++:cppsignalslot.cpp
#include "cppsignalslot.h"
#include <QDebug>

CppSignalSlot::CppSignalSlot(QObject *parent) : QObject(parent)
{

}

void CppSignalSlot::cppSlot(QString msg)
{
    qDebug() << "cppSlot is called with the message: " << msg;
    emit cppSignal("Hello from C++");
}
```
```c++:main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
/*added*/
#include <QQmlContext>
#include "cppsignalslot.h"
//----------

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    CppSignalSlot obj;

    QObject *root = engine.rootObjects().first();

    // Connect QML Signal to C++ Slot
    QObject::connect(root, SIGNAL(qmlSignal(QString)),
                     &obj,   SLOT(cppSlot(QString)));
    // Connect C++ Signal to QML Slot
    QObject::connect(&obj,   SIGNAL(cppSignal(QVariant)),
                     root, SLOT(qmlSlot(QVariant)));

    return app.exec();
}
```

**結果**
クリック前
![signalslot_beforeClicking.png](https://qiita-image-store.s3.amazonaws.com/0/120538/1b25fa21-c813-59f4-f35d-f982f346f6d7.png)

クリック後
![signalslot_afterClicked.png](https://qiita-image-store.s3.amazonaws.com/0/120538/fb09f4f8-654c-c1b5-07e7-55a9dcd97fec.png)
(上のセクションとwindowが違うのは試験環境が違うためです。。。それ以上の理由はありません)

> [コンソール出力]
> cppSlot is called with the message:  "Hello from QML"
> qml: qmlSlot is called with the text: Hello from C++

## QMLでConnect
次は同じ例でQML側でConnectする例です。後述の通り、Cannot assign to non-existent property とReferenceErrorに泣かされました。


```c++:main.qml
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 2.0

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")
    /*deleted*/
//    MouseArea {
//        anchors.fill: parent
//        onClicked: {
//            Qt.quit();
//        }
//    }

    Text {
        id: textField
        text: qsTr("Hello World")
        anchors.centerIn: parent
    }

    Button{
        text:"Emit Signal!"
        anchors.centerIn: parent
        anchors.verticalCenterOffset: 30
        onClicked: cppSignalSlot.cppSlot("Hello from QML")
    }

    Connections
    {
        target:cppSignalSlot
        onCppSignal:{
            console.log("received cppSignal:" + text)
            textField.text = text;
        }
    }
}
```


```c++:cppsignalslot.h
#ifndef CPPSIGNALSLOT_H
#define CPPSIGNALSLOT_H

#include <QObject>

class CppSignalSlot : public QObject
{
    Q_OBJECT
public:
    explicit CppSignalSlot(QObject *parent = 0);

signals:
    void cppSignal(QString text);

public slots:
    void cppSlot(QString text);
};

#endif // SIGNALSLOT_H
```


```c++:cppsignalslot.cpp
#include "cppsignalslot.h"
#include <QDebug>

CppSignalSlot::CppSignalSlot(QObject *parent) : QObject(parent)
{

}

void CppSignalSlot::cppSlot(QString text)
{
    qDebug() << "cppSlot is called with text: " + text;
    emit cppSignal(QString("Hello from C++"));
}
```

```c++:main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "cppsignalslot.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;

    SignalSlot obj;
    // Put this before lading qml file!
    engine.rootContext()->setContextProperty("cppSignalSlot",&obj);

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}
```
ここでミソなのは、C++からcppSignal()でシグナルを出した時の動作をonCppSignalと書くことです。(要するにC++側はLower Camelで書いて、QML側はon+Upper Camelで書きましょうということみたいです)

あと、コメントにも書きましたが、``main.cpp``でRoot ObjectのContextに設定する前に``main.qml``をロードすると、下記のエラーが出ます。(でも、動作はする。。。)
> QML Connections: Cannot assign to non-existent property "OnCppSignal"
> ReferenceError: cppSignalSlot is not defined



#参考
* [Integrating QML and C++](http://doc.qt.io/qt-5/qtqml-cppintegration-topic.html)
* [Qt Quick での C++ × QML バインディングについてまとめてみた](http://tips.hecomi.com/entry/20130503/1367594609)
* [How can I access my Window object properties from C++ while using QQmlApplicationEngine?](http://stackoverflow.com/questions/23177839/how-can-i-access-my-window-object-properties-from-c-while-using-qqmlapplicatio)
* [How to connect a QML gui with a c++ application](http://hanckmann.com/2014/05/10/cpp-qml-interaction.html)
* [QML2 to C++ and back again, with signals and slots](https://andrew-jones.com/blog/qml2-to-c-and-back-again-with-signals-and-slots/)
* [Exchange Data and Objects between C++ and QML and vice versa](https://ruedigergad.com/2011/11/13/exchange-data-and-objects-between-c-and-qml-and-vice-versa/)
* [Not able to connect c++ signal to qml slot using QML connections](https://forum.qt.io/topic/45683/not-able-to-connect-c-signal-to-qml-slot-using-qml-connections)

そして、結局Qtのオフィシャルドキュメントが一番わかりやすいというオチ。。。