Qtの基礎 - シグナルとスロット

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

概要

シグナルおよびスロットの詳細は、以下の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スタイルではSIGNALSLOTというマクロを使用する。
このマクロは、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::ConnectionTypeQt::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ベースの比較
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);
 }