Qtの基礎 - シグナルとスロット
概要
シグナルおよびスロットの詳細は、以下のWebサイトを参照すること。
https://blog.qt.io/jp/2010/07/20/create-signals-and-slots-2
connect関数
Qt::ConnectionType
connect
関数のQt::ConnectionType
の設定により動作が異なる。
接続タイプ(定数) | 動作 |
---|---|
Qt::AutoConnection | デフォルトの設定である。 SenderとReceiverが同じスレッドに存在する場合は、 Qt::DriectConnection が使用される。それ以外の場合は、 Qt::QueuedConnection が使用される。接続タイプはシグナルを emit する時に決定する。
|
Qt::DriectConnection | シグナルを呼び出したスレッドから対象のスロットを呼び出す。(同期呼び出し) |
Qt::QueuedConnection | Receiverのスレッド上でスロット関数が呼び出される。 シグナルはReceiver側のキューに入れられて、Receiverのイベントルーパーからスロット関数が呼び出される。(非同期呼び出し) |
Qt::BlockingQueuedConnection | 基本的な動作は、Qt::QueuedConnectionと同様であるが、 シグナルの呼び出した側は、受信側のスロットの実行終了を待ち合わせる。 多用するとデッドロックを招きやすくなる。 例えば、Receiver側がSender側と同じスレッドに存在する場合には、必ず、デッドロックすることに注意する。 |
Qt::UniqueConnection | 他の接続と組み合わせてビットORで設定する接続タイプである。 この設定が指定されている場合、オブジェクト間のシグナルとスロット、または、シグナルとシグナルの接続の組み合わせは1度しかできなくなる。 2つ以上同じシグナル・スロットの組み合わせで connect 関数を実行した場合、connect 関数は失敗する。この設定は、Qt 4.6で追加された。 |
connect関数
connect
関数は大きく分けて、2種類、細かく分けて4種類の種類が存在する。
connect関数のシンタックス | スタイルの呼称 |
---|---|
Stringベース | Qt 4スタイル |
Functorベース | Qt 5スタイル |
Lambdaスタイル 1 | |
Lambdaスタイル 2 |
// Qt 4スタイルのconnect関数の形式
QMetaObject::Connection QObject::connect(Senderのポインタ, SIGNAL(シグナルの関数名(引数の型, ...)), Receiverのポインタ, SLOT(スロットの関数名(引数の型, ...)), Qt::ConnectionType type = Qt::AutoConnection)
// 例.
connect(sender, SIGNAL(value3Changed(int)), receiver, SLOT(setValue3(int)));
// Qt 5スタイルのconnect関数の形式
QMetaObject::Connection QObject::connect(Senderのポインタ, &シグナルのクラス名::シグナルの関数名, Receiverのポインタ, &スロットのクラス名::スロットの関数名, Qt::ConnectionType type = Qt::AutoConnection)
// 例.
connect(sender, &Sender::value3Changed, receiver, &Receiver::setValue3);
connect
関数の第2引数と第4引数において、Qt 4スタイルではSIGNAL
、SLOT
というマクロを使用する。
このマクロは、Qt側で文字列(const char *signalのように)に変換される。
Qt 5スタイルと異なる点を、以下に示す。
- マクロの有無
- クラス名を記述するかどうか
- 仮引数を明示的に記述するかどうか
// Lambdaスタイル 1のconnect関数の形式
QMetaObject::Connection connect(Senderのポインタ, &Senderのクラス名::シグナルの関数名, [=]() { 処理1; 処理2; ...; });
// 例.
connect(sender, &Sender::valueChanged, [=]() { qDebug() << "Lambda Style1 signal received."; });
// Lambdaスタイル 2のconnect関数の形式
QMetaObject::Connection connect(Senderのポインタ, &Senderのクラス名::シグナルの関数名, コンテキストオブジェクト, [=]() { 処理1; 処理2; ...; }, Qt::QueuedConnection);
// 例.
connect(sender, &MyObject1::valueChanged, this, [=]() { qDebug() << "Lambda Style2 signal received."; }, Qt::QueuedConnection);
Lambda形式は、Receiverのスロット関数を作成しなくてよいため、シグナルが発生しているかどうかログを出力する時に便利である。
Lambdaスタイル 1は、引数にQt::ConnectionType
が無い。
また、Qt::ConnectionType
がQt::DirectConnection
固定となる。
つまり、SenderとReceiverが同一スレッドで動作することが前提となる。
send
関数により、Senderを取得することもできない。
Lambdaスタイル 2は、Qt 5.2で追加されたシンタックスである。
Lambdaスタイル 1と比較すると、第3引数にコンテキストが追加されて、第5引数にQt::ConnectionType
が追加されている。
Qt::ConnectionType
が指定できるため、Qt::DirectConnection
以外の動作も可能である。
また、QObject::sender
関数も使用できる。
上記のLambdaスタイル 1およびLambdaスタイル 2の例では、ラムダ式にキャプチャ式 [=]
を使用しているが、キャプチャ式 [=]
以外のキャプチャ記法も使用できる。
Stringベース | Functorベース | |
---|---|---|
型チェックのタイミング | 実行時 | コンパイル時 |
暗黙の型変換 | 不可 | 可能 |
シグナルをラムダ式で接続できる | 不可 | 可能 |
シグナルより多くの引数を持つスロットに シグナルを接続できる |
可能 | 不可 |
QMLに接続できる | 可能 | 可能 |
シグナルとスロット
connect関数を使用して、シグナルとスロットの設定を行う。
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
以下に、connenct関数の引数の意味を記載する。
- sender
- 信号が発生するコントロールIDまたはクラスのアドレスを渡す。
- SIGNAL(signal)
- signalに信号とする関数名を渡す。
- 例: プッシュボタンの場合、
SIGNAL(clicked())
と記述する。
- receiver
- 信号を受信するコントロールIDまたはクラスのアドレスを渡す。
- SLOT(slot)
- 信号を受信した時に呼び出す関数名を渡す。
connect関数の使用方法
#include "mainwindow.h"
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPushButton* button = new QPushButton("Quit");
QObject::connect(button, SIGNAL(clicked()), &app, SLOT(quit()));
button->show();
return app.exec();
}
静的メンバ関数のconnect
静的メンバ関数は、インスタンスを生成せずに実行することができる。
通常のメンバ関数の呼び出しと同様、thisからシグナルを発信して、thisでシグナルを受ければよい。
また、送信側と受信側の両方がthisの場合、受信側インスタンスの指定を省略できるconnectのオーバーロード関数も存在する。
以下の例では、コンストラクタでシグナルとスロットを接続して、画面が表示された時、showEventメソッドでシグナルをemitしている。
また、送信側のクラスのインスタンスは生成していない。
// MainWindow.cpp
#include "MainWindow.h"
#include "ui_MainWindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(this, &MainWindow::testSignal1, this, &QObjectEx::staticSlot);
connect(this, &MainWindow::testSignal2, this, &QObjectEx::staticSlot);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::showEvent(QShowEvent *event)
{
QMainWindow::showEvent(event);
emit testSignal1(10);
emit testSignal2(20);
}
// MainWindow.h
#pragma once
#include <QMainWindow>
#include "QObjectEx.h"
namespace Ui {class MainWindow;}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
void testSignal1(int);
void testSignal2(int);
protected:
void showEvent(QShowEvent *) override;
private:
Ui::MainWindow *ui;
};
// QObjectEx.h
#pragma once
#include <QObject>
class QObjectEx : public QObject
{
Q_OBJECT
public slots:
static void staticSlot(int i)
{
qDebug() << i;
}
};
Qt Designerで設定したシグナルおよびスロット
Qt Designerで設定したシグナルおよびスロットは、connect
関数を使用せずに呼び出すことができる。
ソースコード上でconnect
関数を使用せずにスロット関数が呼び出される理由を以下に示す。
まず、QWidget
クラスやQMainWindow
クラス等を継承した派生クラスにおいて、コンストラクタに自動生成されるソースコードで、以下の記述がある。
ui->setupUi(this);
次に、setupUi
メソッドの定義の最後に、以下の記述がある。
以下に示すQMetaObject::connectSlotsByName
メソッドは、オブジェクト(引数)が持つ全てのスロットに対して、
on_<子オブジェクト名>_<子オブジェクトのシグナル名>
を満たすスロット名の存在を確認する。
void setupUi(QMainWindow *MainWindow)
{
// ...略
QMetaObject::connectSlotsByName(MainWindow);
}