C++ による QT / QML シグナルとスロット

QT には、「シグナルとスロット」と呼ばれるオブザーバー メカニズムが組み込まれています。これにより、オブジェクトは内部の知識がなくても相互に通信できます。 QObject から継承 いくつかの Q_PROPERTY を定義します マクロの、QT メタ オブジェクト コンパイラ (moc ) はすべての面倒な作業を行います.C++ クラス内では、これはすべて便利でうまく機能し、従うのもかなり簡単ですが、QML を使用する場合は、もう少し作業が必要です。この小さな例は、QT 5.12 でシグナルとスロットを使用して QML と C++ を結合する方法を示しています。

アプリケーションのスクリーンショットの下。ボタンで増加するか、テキスト入力フィールドを介して設定される単純なカウンターにすぎませんが、始めるには十分です。

このシグナル/スロットの構築はほとんどが文字列ベースであるため、IDE が提供するリファクタリング ツールを使用することはできません。メソッドが value に基づいている場合 value を変更したい たとえば、something まで 、 Q_PROPERTY を変更する必要があります 、QML の使用法とバインディング、およびすべての通常の C++ コード。 QT シグナルとスロット、および QML の仕組みに慣れていない場合は、それほど明白ではありません。

楽しみのために、このサンプル アプリケーションも Webassembly にコンパイルしました。ここで実行するか、このページの下部に iframe として埋め込まれています。 .

まとめ

これは小さなスニペットであるため、私の記事から得られる説明や深みが欠けています。いくつかのコード コメントが提供されていますが、この場合は QT ドキュメントを読むことをお勧めします:

  • シグナルとスロット
  • QML および C++ との相互作用

非常に広範囲に説明されています。それが、私がこの要約を書いた理由でもあります.すべての包括的なドキュメントのために、小さなものから始めるのは難しい.

サンプル コードには、Counter という名前の C++ クラスがあります。 、1 つのプライベート long long を含む m_Value という名前 .QML ファイルでは、このクラスとそのメソッド (QT シグナル/スロットを含む) を使用したいと考えています。

クラスは QObject から継承する必要があります Q_OBJECT を配置する必要があります ヘッダーのマクロ:

class Counter : public QObject
    {
        Q_OBJECT
        [...]

値を設定および取得する方法は、ご想像のとおりです:

long long value() const { return m_Value; };
[...]
void Counter::setValue(long long value) {
        if (value == m_Value)
            return;
        m_Value = value;
        emit valueChanged(value);
    }

上記の方法では、 emit が表示されます キーワード。明確にするために、これは空白の定義です。関数 valueChanged() と呼ばれます。これは私たちの signal です 、ヘッダー ファイルのように:

signals:
    void valueChanged(long long newValue);

setValue() method は slot です :

public slots:
        void setValue(long long value);

これらは、この Q_PROPERTY によって QML にアクセスできます 行:

Q_PROPERTY(long long value READ value WRITE setValue NOTIFY valueChanged)

これらを QObject::connect() 経由で接続することもできます しかし、それはこのスニペットの範囲外です。これは、C++ 内でシグナリングを使用する場合のためのものです。

main.cpp のこれらの行 も必要です。つまり、クラスを QML に追加します。

    QQmlApplicationEngine engine;
    Counter myCounter;

    QQmlContext *context = engine.rootContext();
    context->setContextProperty("MyCounter", &myCounter);

この後、 MyCounter にアクセスできます 通常の C++ クラスであるかのように QML の内部。たとえば、Counter::value() を呼び出すには メソッド:

    Text {
        text: "Counter: " + MyCounter.value + "."
    }    

または Counter::setValue() メソッド:

        Button {
            text: qsTr("Set counter to 10")
            // C++ method Counter::setValue(long long), bound via Q_PROPERTY
            onClicked: MyCounter.setValue(10)
        }

mocの魔法で Q_PROPERTY を介して生成される追加のコード 、以下の例のようにインクリメントすると、インクリメントする値を認識し、正しい演算子のオーバーロードを生成します:

   Button {
        text: qsTr("Increase Counter")
        onClicked: ++MyCounter.value
    }

QT で C++ シグナルを受信することもできます。 valueChanged を定義しました シグナルとして Connection 経由で onValueChanged で (ここでは大文字が重要です。メソッドの前に on を付けます メソッド名の最初の文字を大文字に変更します) QML で行うことができます。以下のように、シグナルが受信されるたびにインクリメントされるローカル変数があります:

Text {
    property int changeCount: 0
    id: labelChanged
    text: "Count has changed " + changeCount + " times."
    // Receive the valueChanged NOTIFY
    Connections {
        target: MyCounter
        onValueChanged: {
            ++labelChanged.changeCount
        }
    }
}

双方向バインディングの例については、最後の TextInput を見てください QMLで。 C++ クラスの現在の値が表示され、値が更新されると更新され、数値を入力すると C++ クラスが更新されます。

コード例

プロジェクト フォルダを作成し、指定されたファイル名の下にすべてのファイルを配置します。

このプロジェクトは、こちらの github でも利用できます。

qmlcppsignalexample.pro

QT += quick

CONFIG += c++11

SOURCES += \
        counter.cpp \
        main.cpp

RESOURCES += qml.qrc

# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =

# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

HEADERS += \
    counter.h

qml.qrc

<RCC>
    <qresource prefix="/">
        <file>main.qml</file>
    </qresource>
</RCC>

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QObject>
#include <QQmlContext>
#include "counter.h"

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

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    Counter myCounter;

    QQmlContext *context = engine.rootContext();
    /* Below line makes myCounter object and methods available in QML as "MyCounter" */
    context->setContextProperty("MyCounter", &myCounter);

    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);
    return app.exec();

}

counter.h

#ifndef COUNTER_H
#define COUNTER_H

#include <QObject>

class Counter : public QObject
{
    Q_OBJECT
    Q_PROPERTY(long long value READ value WRITE setValue NOTIFY valueChanged)
public:
    explicit Counter(QObject *parent = nullptr);
    long long value() const { return m_Value; };

public slots:
    void setValue(long long value);

signals:
    void valueChanged(long long newValue);

private:
    long long m_Value {0} ;
};

#endif // COUNTER_H

counter.cpp

#include "counter.h"

Counter::Counter(QObject* parent) : QObject(parent)
{
}

void Counter::setValue(long long value) {
    if (value == m_Value)
        return;
    m_Value = value;
    emit valueChanged(value);
}

main.qml

import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 2.11

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("QML Signals and slots example - Raymii.org")

    MenuBar {
        width: parent.width
        Menu {
            title: qsTr("File")
            MenuItem {
                text: qsTr("Exit")
                onTriggered: Qt.quit();
            }
        }
    }

    Column {
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.verticalCenter: parent.verticalCenter
        spacing: 20

        Text {
            id: info
            width: parent.width * 0.9
            wrapMode: Text.WordWrap
            text: "QML / C++ binding via signals and slots example program, by Raymii.org. License: GNU GPLv3"
        }


        Text {
            id: labelCount
            // C++ method Counter::value(). Bound via Q_PROPERTY, updates automatically on change
            text: "Counter: " + MyCounter.value + "."
        }

        Text {
            property int changeCount: 0
            id: labelChanged
            text: "Count has changed " + changeCount + " times."
            // Receive the valueChanged NOTIFY
            Connections {
                target: MyCounter
                onValueChanged: {
                    ++labelChanged.changeCount
                }
            }
        }

        Row {
            spacing: 20
            Button {
                text: qsTr("Increase Counter")
                onClicked: ++MyCounter.value
            }

            Button {
                text: qsTr("Set counter to 10")
                // C++ method Counter::setValue(long long), bound via Q_PROPERTY
                onClicked: MyCounter.setValue(10)
            }

            Button {
                text: qsTr("Reset")
                onClicked: {
                    // C++ method Counter::setValue(long long), bound via Q_PROPERTY
                    MyCounter.setValue(0)
                }
            }
        }

        Row {
            spacing: 20

            Text {
                id: setText
                text: qsTr("Enter counter value: ")
            }
            Rectangle {
                width: setText.width
                height: setText.height
                border.width: 1
                border.color: "black"

                TextInput {
                    id: counterInput
                    focus: true
                    text: MyCounter.value
                }
            }
            // Bi-directional binding, entering a number in the textarea updates the
            // C++ class, if the C++ class is updated, the textarea is updated as well.
            Binding {
                target: MyCounter
                property: "value"
                value: counterInput.text
            }
        }
    }
}

ビルド / メイク

上記のコードを作成するには、まずプロジェクトの外にビルド フォルダーを作成します。

cd /tmp
mkdir build-qmlexample
cd build-qmlexample

qmake を実行 、パスを置き換えます (/home/remy/tmp/qt/qml_cpp_signal_example/ ) をプロジェクト パスに追加:

qmake /home/remy/tmp/qt/qml_cpp_signal_example/qmlcppsignalexample.pro -spec linux-g++ CONFIG+=release && make qmake_all

この例では qmake を使用しています 、しかし cmake を使用しても問題はないはずです .ここでは特別なものは使用しません。

qmakeの場合 make を実行できます プロジェクトをビルドするには:

make -j4

しばらくすると、バイナリが利用可能になります:

$ file qml_cpp_signal_example 
qml_cpp_signal_example: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f884f57b90ebf05b51551d42cef5ca3ee52037b4, for GNU/Linux 3.2.0, with debug_info, not stripped

コマンドラインから実行:

./qml_cpp_signal_example

QT ウェブアセンブリのデモ

楽しみのために、サンプル アプリケーションを webassembly にコンパイルしました。ここで実行するか、読み込まれる場合は iframe 以下: