見出し画像

Qt5再入門: シグナル/スロット

Qtアプリケーションが持つ特徴的な仕組みに「シグナル/スロット」があります。シグナルは、何かしらの変化を他に通知する仕組みで、スロットはその変化を受け取る仕組みです。

シグナル/スロットはとても奥の深い仕組みですが、ここではほんの入口のところだけ触れて、こんな風に使えるんだよ〜的なことをお話しします。

アレが発生したら、コレを実行する

シグナル/スロットは、JavaScriptの「イベントリスナーを登録する」作業と似ているかもしれません。例えば、JavaScriptではbuttonタグのonClickにリスナー関数を登録して、クリックした時の動作を定義しますが、シグナル/スロットもQPushButtonのclickedというシグナルを受け取ったら、それで動作するスロット関数を対応づけます。

シグナル

シグナルはvoid型を返すメソッドのように記述します。

class Hoge1 : public QObject {
Q_OBJECT
public:
  void someEventHasOccurred() { emit fuga1(); }
signals:
  void fuga1();
};

signalsというQt独特のキーワード以下がシグナルになりますが、fugaという関数は実体がなく、インプリメントすることはありません。シグナルを発動するには、Qtキーワード emit を使い、それが関連付いているスロットを実行することになります。

前述のようにシグナルは、JavaScriptのイベントのようなものですが、Qtにおいてはイベントとシグナルは明確に分けていて、私の愛読書「入門 Qt4 プログラミング」の「7章 イベント処理」にも記述があります。

詳しくは公式サイトや前著などを参考にしていただくとして、私個人の捉え方としては、大雑把に「QtウィジェットやQObject同士が送出するのがシグナル、Qt外からくるものをイベント」と線引きしています。QPushButtonがマウスのクリックを捕捉するのがイベントで、そのイベントによってQPushButtonはクリックされたとclickedシグナルを送出(emit)します。

スクリーンショット 2021-08-28 8.39.56

上図のように、イベントを受け取るのはもっぱら既存のウィジェットであることが多いので、ウィジェットを利用するだけなら、イベントはあまりお目にかからないでしょう(以前紹介したQCloseEventは別ですが)。

スロット

次にスロットですが、原則publicでslotsキーワードを付けます。

class Hoge2 : public QObject {
Q_OBJECT
public:
  void doSomething() { std::cout << "Hello, World!" << std::endl; }
public slots:
  void fuga2() { doSomething(); }
};

前述のHoge1:fuga1シグナルとHoge2:fuga2スロットが接続されていると、Hoge1::someEventHasOccurredを実行すると、Hoge2::doSomethingが実行され、「Hello, World!」が標準出力に表示されます。

シグナルをスロットに接続

では実際にどう接続するのか、サンプルコードを見てみます。これまで作成してきたMainWindowクラスのコンストラクタの抜粋です。

MainWindow::MainWindow(QWidget *parent)
 : QMainWindow(parent)
 , ui_(new Ui::MainWindow)
{
 ui_->setupUi(this);
// ...
 connect(
       ui_->actionE_xit, &QAction::triggered,
       this, &MainWindow::close
       );
 connect(
       ui_->actionAppend_Bookmark, &QAction::triggered,
       this, &MainWindow::appendBookmark
       );
 connect(
       ui_->actionOpen_Path, &QAction::triggered,
       this, &MainWindow::openPath
       );
 connect(
       pModel_, &BookmarkModel::dataChanged,
       this, &MainWindow::resizeBookmarkViewColumn
       );
 connect(
       pModel_, &BookmarkModel::rowsRemoved,
       ui_->bookmarksView, &QTableView::resizeColumnsToContents
       );

 loadSettings();
// ...
}

connectというメソッドがずらりと並んでいます。これは、QObjectの静的メソッドとして定義されていて、MainWindowも元をたどるとQObjectにたどり着くので、ここで使えます。

connect関数にはいくつかバリエーションがあります。ここでは、ファンクタベースと呼ばれるスタイルについて説明します。

ファンクタベースのconnect関数は、送り手のオブジェクトとシグナル、受け手のオブジェクトとスロットを、以下のシンボルに沿って定義します。

QMetaObject::Connection QObject::connect(
  const QObject *sender, PointerToMemberFunction signal,
  const QObject *receiver, PointerToMemberFunction method,
  Qt::ConnectionType type = Qt::AutoConnection)

例えば、さきほどのコンストラクタコードで、以下のように定義しています。

 connect(
       ui_->actionE_xit, &QAction::triggered,
       this, &MainWindow::close
       );

1つ目の引数 ui_->actionE_xit は送り手のオブジェクトです。
2つ目の引数 &QAction::triggered はシグナルで、この場合、「actionE_xitオブジェクトのtriggeredシグナルが発せられたら」という意味になります。
3つ目の引数 this は、受け手のオブジェクトです。
4つ目の引数 &MainWindow::close はスロットで、この場合、「シグナルを受け取ったらMainWindowのクローズメソッドを実行する」という意味になります。

QActionとは、アクションやコマンドのような事象を扱うクラスで、単体でも使えますし、メニューやツールバーの一員としても定義できます。前述のコード例では、メニューの「ファイル→終了」として作成したものが ui_->actionE_xit です。UI上のメニューから「ファイル→終了」をクリックすると、QAction::triggered シグナルが発せられます。このシグナルを connect 関数によってMainWindow の close に接続しているので、MainWindow が閉じ、アプリケーションを終了することができます。

まとめ

冒頭にも書いたように、シグナル/スロットは「何かをしたら何かをする」というシンプルな動作を実現する仕組みですが、一度掘り出すととても奥が深い話になります。

私が今回ご紹介したファンクタベース以外にもいろいろなバリエーションがあります。興味を持たれたら、公式サイトやWeb上の情報に目を通されてみてはいかがでしょうか。

この記事が気に入ったらサポートをしてみませんか?