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
以下: