LoginSignup
36
35

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-08-17

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

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

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();
}
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でアクセスする。

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を定義する。

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
(見にくかったので色をつけてフォントを大きくしています)

なお、オフィシャルでは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を返す形にしてみました)

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

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ファイルを変更。

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();
}

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

Signal & Slot

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

C++でConnect

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

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")
    }
    //----------

}
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
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++");
}
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

クリック後
signalslot_afterClicked.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に泣かされました。

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;
        }
    }
}
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
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++"));
}
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

参考

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

36
35
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
35