Qt をはじめよう! 第10回: シグナルとスロット

前回前々回と、Qt のユーザーインターフェースの作成について学びました。

今回から数回に渡って Qt のオブジェクト間通信の仕組みであるシグナルとスロットについて学んでいきましょう。

シグナルとスロットとは

一般的に GUI プログラミングでは、一つのウィジェットやオブジェクトの状態が変わった際に何かしらの処理を行うために、他のウィジェットやオブジェクトにそれを通知する仕組みが必要です。前回、および前々回のサンプルアプリケーションの場合、ユーザーが Close ボタンを押した際にウィンドウを閉じる処理をするために「ボタンが押された」ことをどこかに通知する必要があります。

これまでの多くのツールキットでは、コールバック関数を登録しそれを呼び出す方法でこれを実現してきました。しかしコールバックには色々な問題があります。

シグナルとスロットは、コールバック関数に変わる新しいオブジェクト間通信の仕組みです。コールバック関数の問題であった引数の型の安全性を保証し、オブジェクト指向の重要な理念であるカプセル化の思想に基づいた汎用性の高いクラスの設計を可能にします。

シグナルとは

シグナルとは何かが発生したことを他のオブジェクトに通知するための C++ の関数です。

例:

  • ボタンが押された際に発生するシグナル [qt QAbstractButton clicked()]
  • ボタンのオン/オフが切り替わった際に発生するシグナル [qt QAbstractButton "toggled(bool on)"]
  • QLineEdit の内容が変更された際に発生するシグナル [qt QLineEdit "textChanged(const QString &text)"]

ANSI C の割り込み signal(2) とは全く別のものになります。

スロットとは

スロットはシグナルに反応して実行される C++ の関数です。

例:

  • ウィジェットを閉じるスロット [qt QWidget close()]
  • ウィジェットの表示/非表示を切り替えるスロット [qt "" "QWidget::setVisible(bool visible)" l=qwidget m=#visible-prop]
  • ラベルの文字列を変更するスロット [qt "" "QLabel::setText(const QString &text)" l=qlabel m=#text-prop]

シグナルとスロットの使用

Qt のプログラミングではシグナルとスロットを"接続"して使用します。この接続により、シグナルが発生した際にスロットが自動的に実行されます。この接続は [qt QObject connect()] を使用して行います。それでは前回使用したサンプルアプリケーションでシグナルをスロットに接続してみましょう。

Example::Example(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *layout = new QVBoxLayout;
QSlider *slider = new QSlider(Qt::Horizontal);
layout->addWidget(slider);
QPushButton *button = new QPushButton("Close");
layout->addWidget(button);
setLayout(layout);
connect(button, SIGNAL(clicked()), this, SLOT(close())); // [1]
}

[1] で QPushButton のインスタンス button が押された際に発生するシグナル clicked() を Example 自身のインスタンス this のウィンドウを閉じるためのスロット close() に接続しています。これにより、Close ボタンが押された際に、Example の close() が実行されウィンドウが閉じるようになります。

Example クラスは QObject の派生クラスなので、connect() の前の QObject:: は省略可能です。
実行をして、Close ボタンをクリックしてみてください。無事にウィンドウが閉じられ、アプリケーションが終了したでしょうか。

シグナルとスロットの接続について

QObject::connect() において、シグナルは SIGNAL マクロで、スロットは SLOT マクロで囲む必要があります。引数については型のみを記述し、const、& や変数名は記述しません。スロットの引数の型と順番は、シグナルの引数の型と順番に正しく対応している必要があります。スロットの引数がシグナルの引数より少ない場合は正しく動作し、余分なシグナルの引数は単純に無視されます。

シグナルとスロットの引数の型が合わなかった場合には接続は失敗します。QObject::connect() は false を返し、実行時に警告が出力されます。これにより、シグナルが発生しスロットが実行される際の引数の型の安全性が確保されます。

複数の異なるシグナルを一つのスロットに接続することや、一つのシグナルを複数の異なるスロットに接続することが可能です。また、シグナルをシグナルに接続することも可能です。

それではシグナル、スロットの接続の具体的な例をいくつか見てみましょう。

チェックボックスのオン・オフの切り替えに連動してラベルの表示・非表示を切り替える:

QLabel *label = new QLabel("Label");
QCheckBox *checkBox = new QCheckBox;
connect(checkBox, SIGNAL(toggled(bool)), label, SLOT(setVisible(bool)));

ラインエディットの文字列が変更された際にラベルにその文字列を表示する:

QLabel *label = new QLabel;
QLineEdit *lineEdit = new QLineEdit;
connect(lineEdit, SIGNAL(textChanged(QString)),
label, SLOT(setText(QString)));

おわりに

今回はシグナル/スロットと、その接続について紹介しました。
次回は独自のシグナルとスロットを作成してみましょう。


Blog Topics:

Comments