QMLの基礎 - QMLとC++のバインディング

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

QMLとC++のバインディング手順を記載する。

ここでは、Qt Creatorにおいて、Qt Quickの空のプロジェクトを作成した時に自動生成されるソースコードを元にする。


QMLからC++へアクセスする (推奨)

setContextPropertyメソッドにQObjectのインスタンスを渡して、QMLからC++のクラスメソッドを呼んでいる。

 // main.cppファイル
 
 #include <QGuiApplication>
 #include <QQmlApplicationEngine>
 #include "CMainWindow.h"
 
 int main(int argc, char *argv[])
 {
 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
 #endif
 
    QGuiApplication app(argc, argv);
 
    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app,
                    [url](QObject *obj, const QUrl &objUrl)
                    {
                       if (!obj && url == objUrl)
                          QCoreApplication::exit(-1);
                    }, Qt::QueuedConnection);
    engine.load(url);
 
    // 追加
    CMainWindow MainWindow;
    engine.rootContext()->setContextProperty("MainWindow", &MainWindow);
 
    // 複数のクラスを追加する場合、以下のように記述する
    //CSubWindow SubWindow;
    //engine.rootContext()->setContextProperty("SubWindow", &SubWindow);
 
    return app.exec();
 }


 // CMainWindow.hファイル
 
 #pragma once
 
 #include <QObject>
 #include <QString>
 
 class CMainWindow : public QObject
 {
    Q_OBJECT
 
    public:
       Q_INVOKABLE QString getTextFromCpp()
       {
          return QString("This is the text from C++");
       }
 };


 // main.qmlファイル
 
 import QtQuick 2.5
 import QtQuick.Window 2.2
 
 Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")
 
    // 追加
    Text {
        id: textMainMessage
        text: qsTr("ここにテキストが表示される")
 
        x: (parent.width / 2) - width / 2
        y: (parent.height / 2) - height / 2
        Layout.fillWidth: true
        Layout.fillHeight: true

        font.pixelSize: 15
        color: "#ffffff"
    }
 
    Button {
        id: mainWindowButton
        x: (parent.width / 2) - width / 2
        y: (parent.height / 2) + height / 2
        width: 120
        height: 35
        Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
        text: qsTr("OK")
 
        Connections {
           target: mainWindowButton
           function onClicked() {
              let strMainMessage = MainWindow.getTextFromCpp();  // C++のメソッドを呼ぶ
              textMainMessage.text = strMainMessage;
           }
        }
    }
 }



C++からQMLオブジェクトへアクセスする (非推奨)

C++からQMLオブジェクトへのアクセスは、C++とQMLの独立性の観点から非推奨であることに注意する。

ここでは、objectNameでアクセスする方法を記載する。(他にも様々な手順がある)

まず、root要素へアクセスして、子要素であるTextへQMLで定義するobjectNameでアクセスする。

 // main.cppファイル
 
 #include <QGuiApplication>
 #include <QQmlApplicationEngine>
 #include "CMainWindow.h"
 
 int main(int argc, char *argv[])
 {
 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
 #endif
 
    QGuiApplication app(argc, argv);
 
    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app,
                    [url](QObject *obj, const QUrl &objUrl)
                    {
                       if (!obj && url == objUrl)
                          QCoreApplication::exit(-1);
                    }, Qt::QueuedConnection);
    engine.load(url);
 
    // 追加
    // ルートオブジェクトおよびmain.qmlに定義されているobjectName(textObject)のQMLオブジェクトへのアクセスを取得する
    QObject *rootObject = engine.rootObjects().first();
    QObject *qmlObject = rootObject->findChild<QObject*>("textObject");
 
    // QMLオブジェクトに対して、任意の値を設定する
    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")
 
    // 追加
    Text {
        id: textMainMessage
        objectName: "textObject"
        text: qsTr("ここにテキストが表示される")
 
        x: (parent.width / 2) - width / 2
        y: (parent.height / 2) - height / 2
        Layout.fillWidth: true
        Layout.fillHeight: true

        font.pixelSize: 15
        color: "#ffffff"
    }
 }



シグナルとスロット

以下の例では、QML側のボタン押下時にC++側へシグナルを出力してC++側のスロットで受信した後、C++側からQML側へシグナルを送出している。

C++側でコネクトする

C++側からのシグナルにおいて、引数をQVariantにする必要がある。
これを指定しない場合、QML側のスロットが認識されない。

 // main.qmlファイル
 
 import QtQuick 2.5
 import QtQuick.Window 2.2
 import QtQuick.Controls 2.0
 
 Window {
    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")
 
    Text {
        id:textField
        text: qsTr("Hello World")
        anchors.centerIn: parent
    }
 
    Button {
        id:aButton
        text:"Emit Signal!"
        anchors.centerIn: parent
        anchors.verticalCenterOffset: 30
        onClicked: qmlSignal("Hello from QML")
    }
 }


 // main.cppファイル
 
 #include <QGuiApplication>
 #include <QQmlApplicationEngine>
 #include <QQmlContext>
 #include "CSignalSlot.h"
 
 int main(int argc, char *argv[])
 {
    QGuiApplication app(argc, argv);
 
    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
 
    CSignalSlot SignalSlot;
 
    QObject *root = engine.rootObjects().first();
 
    // Connect QML Signal to C++ Slot
    QObject::connect(root, SIGNAL(qmlSignal(QString)), &SignalSlot, SLOT(cppSlot(QString)));
 
    // Connect C++ Signal to QML Slot
    QObject::connect(&SignalSlot, SIGNAL(cppSignal(QVariant)), root, SLOT(qmlSlot(QVariant)));
 
    return app.exec();
 }


 // CSignalSlot.hファイル
 
 #ifndef CSIGNALSLOT_H
 #define CSIGNALSLOT_H
 
 #include <QObject>
 #include <QVariant>
 #include <QDebug>
 
 class CSignalSlot : public QObject
 {
    Q_OBJECT
 
 public:
    explicit CSignalSlot(QObject *parent = 0) : QObject(parent)
    {
    }
 
 signals:
    void cppSignal(QVariant text);
 
 public slots:
    void cppSlot(QString msg)
    {
       qDebug() << "cppSlot is called with the message: " << msg;
       emit cppSignal("Hello from C++");
    }
 };
 
 #endif // CSIGNALSLOT_H


QML側でコネクトする

C++側からシグナル(cppSignalメソッド)を送出する時の動作をonCppSignalと記述する。
これは、C++側はLower Camelで記述、QML側は"on" + Upper Camelで記述する必要がある。

main.cppにおいて、ルートオブジェクト(rootContextクラス)のコンテキスト(setContextPropertyメソッド)を設定する前にmain.qmlを読み込むと、
以下のエラーが出力されるため注意すること。

QML Connections: Cannot assign to non-existent property "OnCppSignal"
ReferenceError: cppSignalSlot is not defined


 // 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")
 
    Text {
       id: textField
       text: qsTr("Hello World")
       anchors.centerIn: parent
    }
 
    Button {
       text:"Emit Signal!"
       anchors.centerIn: parent
       anchors.verticalCenterOffset: 30
       onClicked: cSignalSlot.cppSlot("Hello from QML")
    }
 
    Connections {
       target:cSignalSlot
       onCppSignal: {
          console.log("received cppSignal:" + text)
          textField.text = text;
       }
    }
 }


 // main.cppファイル
 
 #include <QGuiApplication>
 #include <QQmlApplicationEngine>
 #include <QQmlContext>
 #include "CSignalSlot.h"
 
 int main(int argc, char *argv[])
 {
    QGuiApplication app(argc, argv);
 
    QQmlApplicationEngine engine;
 
    CSignalSlot SignalSlot;
 
    // Put this before lading qml file!
    engine.rootContext()->setContextProperty("CSignalSlot", &SignalSlot);
 
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
 
    return app.exec();
 }


 // CSignalSlot.hファイル
 
 #ifndef CSIGNALSLOT_H
 #define CSIGNALSLOT_H
 
 #include <QObject>
 #include <QDebug>
 
 class CSignalSlot : public QObject
 {
    Q_OBJECT
 
 public:
    explicit CSignalSlot(QObject *parent = 0) : QObject(parent)
    {
    }
 
 signals:
    void cppSignal(QString text);
 
 public slots:
    void cppSlot(QString text)
    {
       qDebug() << "cppSlot is called with text: " + text;
       emit cppSignal(QString("Hello from C++"));
    }
 };
 
 #endif // CSIGNALSLOT_H