Qt/C++ による GUI ソフト 3:シグナルとスロット
このページでは,シグナルとスロットという仕組みを練習します.
シグナルとスロットが共に Qt のクラスのメンバである場合から始めて,スロットの自作,シグナルの自作,シグナルとスロット間での値の授受と続きます.
シグナルとスロットは,「関係のない」オブジェクト間を接続するのに利用される機能です.
シグナルは signals 指示子で宣言された関数宣言です.emit マクロにより,任意の場所で発行できます.
シグナルが発行されると,そのシグナルに接続されたスロットが呼び出されます.スロットは slots 指示子で宣言された関数です.
シグナルとスロットを接続するには,QObject::connect() という関数を使います.
インフォメーション
ドキュメント
以下は,メニューを動作させるのに使われる関数やクラスへのリンクです.
- QAction Class:「終了」などのメニューから実行される項目
- QObject Class:シグナルとスロットに関わる機能はこのクラス
- Signals & Slots:シグナルとスロットの解説ページ
インストールした Debian パッケージ
このページは Debian 13 trixie で作成しています.
| パッケージ名(バージョン) | 注 |
| g++(14.2.0) | GNU C++ コンパイラ |
| make(4.4.1) | コンパイルを制御するユーティリティ |
| qt6-base-dev(6.8.2) | Qt6 アプリケーションのビルドに使用されるヘッダ開発ファイル |
目次(ページ内リンク)
- シグナルとスロットとを接続:[終了(Q)] が発行シグナルとスロット QWidget::close() とを接続して,ウィンドウが閉じられるようにします
- スロットを自作:メニューで [開く(O)] を作成して,そこから自作スロットが呼び出せるようにします
- シグナルを自作:シグナルを自作して自作スロットが呼び出せるようにします
- 自作シグナルと自作スロット間で値の授受:シグナルとスロットに引数を与えてみたら動作しました
シグナルとスロットとを接続
シグナルとスロットの仕組みを利用して [ファイル(F)]-[終了(Q)] が動作するようにします.
QMenu に追加された QAction([終了] 等のメニューから実行される項目)は,クリックされることにより活性化し,シグナルを発行します.
QAction からシグナルが発行されると,そのシグナルに接続されたスロットが呼び出されます.
シグナルとスロットを接続するには,QObject::connect() という関数を使います.
ソースコード
2:メニューバーで作成したコードを簡略化して利用します.
main_window.h
シグナルとスロットの仕組みを使うには,Q_OBJECT というマクロを追加する必要があります.これを忘れるとコンパイルエラーになります.
QObject Class には,
- シグナル,スロット,またはプロパティを実装するすべてのオブジェクトには,Q_OBJECT マクロが必須である
- Q_OBJECT マクロ展開は,private: アクセス指定子で終わる(直後にメンバーを宣言すると,そのメンバも private になる)
- Qt ウィジェットを含め,QObject のすべてのサブクラスでは,シグナル,スロットを使用しているかどうかにかかわらず Q_OBJECT マクロを使用することを推奨する
という意味のことが記されています.
#include <QMainWindow>
#include <QMenuBar>
class Main_Window : public QMainWindow {
Q_OBJECT
private:
QMenuBar *menu_bar;
QMenu *file_menu;
QAction *action_quit;
public:
Main_Window(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
};
main_window.cc
connect() という関数を使い,アクション Main_Window::action_quit が発するシグナルと,スロット QWidget::close() とを接続します.
connect() については,このパラグラフの最後でまとめます.
#include "main_window.h"
Main_Window::Main_Window(QWidget *parent, Qt::WindowFlags f)
: QMainWindow(parent, f),
menu_bar(menuBar())
{
resize(320, 240);
setWindowTitle("Main_Window");
file_menu = new QMenu("ファイル(&F)", this);
menu_bar->addMenu(file_menu);
action_quit = new QAction("終了(&Q)", this);
file_menu->addAction(action_quit);
//Main_Window::action_quit が発するシグナルと,QWidget::close() とを接続
connect(action_quit, SIGNAL(triggered()), this, SLOT(close()));
}
main.cc
#include "main_window.h"
#include <QApplication>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Main_Window window(nullptr, Qt::Window);
window.show();
return app.exec();
}
QAction Class には,以下のシグナルが記載されています.
- void changed()
- void checkableChanged(bool checkable)
- void enabledChanged(bool enabled)
- void hovered()
- void toggled(bool checked)
- void triggered(bool checked = false)
- void visibleChanged()
tmp.pro
TEMPLATE = app
TARGET = my_window
INCLUDEPATH += .
QT += widgets
SOURCES += main.cc main_window.cc
HEADERS += main_window.h
ビルドと実行
手順は 1:メインウィンドウから不変です.ソースファイルが tmp というディテクトリにあるとします.
~/tmp$ qmake6
~/tmp$ make
~/tmp$ ./my_window &
実行してメニューバーで [ファイル(F)]-[終了(Q)] と操作するとウィンドウが閉じました.
QObject::connect()
QObject Class には,Static Public Members として以下の connect() 関数が挙げられています.
これら connect() 関数の戻り値は,接続へのハンドル QMetaObject::Connection です.ハンドルは,後で接続を切断するために使用できます.
1 個のシグナルは,複数のスロットやシグナルに接続することができます.
connect() を列挙すると,
- QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method, Qt::ConnectionType type = Qt::AutoConnection)
- QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
- QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
- QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection)
- QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection)
これら connect() 関数の戻り値は,QMetaObject::Connection Calass です.リファレンスマニュアルの説明には,「接続へのハンドルを表す.このハンドルは,接続が有効かどうかを確認したり,QObject::disconnect() を使って接続を切断したりするのに使える」と書いてあります.
このページでは 1 のタイプを使っています.クラス宣言の引数とコード内で関数呼び出しに使っている引数を対応付けて記すと,
- const QObject *sender : action_quit
- const QMetaMethod &signal : SIGNAL(triggered())
- const QObject *receiver : this
- const QMetaMethod &method : SLOT(close())
SIGNAL と SLOT というマクロの実体は,C++ のプリプロセッサ命令とのことです.
QMetaMethod クラスは,関数に関する情報を格納しておくためのクラスです.
7:レンダリング その2 では 3 番めの書式(ファンクタではなくてラムダ式ですが)を使っています.
スロットを自作
メニューに [ファイル(F)]-[開く(O)] を追加して,動作するようにします.もっとも,通常のソフトウェアのようにファイルダイアログが開いて…というような動作ではなく,端末に文字列を出力するだけです.
ソースコード
通信の流れをまとめておきます.
- [ファイル(F)]-[開く(O)] の本体である action_open が活性化すると,シグナル triggered() が発行される
- その triggered() と予め connect されている自作スロット slot_open() が呼び出される
スロットは,slots 指示子に続いて宣言される関数です.シグナルと接続できる以外は,通常の C++ 関数です.
main_window.h
#include <QMainWindow>
#include <QMenuBar>
class Main_Window : public QMainWindow {
Q_OBJECT
private:
QMenuBar *menu_bar;
QMenu *file_menu;
QAction *action_open; //[ファイル(F)]-[開く(O)] の本体
QAction *action_quit;
private slots:
void slot_open(); //[ファイル(F)]-[開く(O)] で呼び出される関数の宣言
public:
Main_Window(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
};
main_window.cc
ヘッダで宣言した自作スロット void slot_open() を実装します.端末に関数名を出力するだけですが.
connect() を使い,メニュー [開く(O)] の本体 *action_open と自作スロット slot_open() とを接続します
#include "main_window.h"
#include <iostream>
Main_Window::Main_Window(QWidget *parent, Qt::WindowFlags f)
: QMainWindow(parent, f),
menu_bar(menuBar())
{
resize(320, 240);
setWindowTitle("Main_Window");
file_menu = new QMenu("ファイル(&F)", this);
menu_bar->addMenu(file_menu);
action_open = new QAction("開く(&O)", this);
file_menu->addAction(action_open);
action_quit = new QAction("終了(&Q)", this);
file_menu->addAction(action_quit);
connect(action_quit, SIGNAL(triggered()), this, SLOT(close()));
//メニュー [開く(O)] の本体 *action_open と自作スロット slot_open() とを接続
connect(action_open, SIGNAL(triggered()), this, SLOT(slot_open()));
}
//[ファイル(F)]-[開く(O)] で呼び出されるスロット関数の定義
void Main_Window::slot_open() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
main.cc および window.pro
変更がないので省略します.
ビルドと実行
手順は上と同じです.
~/tmp$ qmake6
~/tmp$ make
~/tmp$ ./my_window &
実行してメニューバーで [ファイル(F)]-[開く(O)] と操作すると,端末に
void Main_Window::slot_open()
と出力されました.
シグナルを自作
上のコードでは QAction のトリガーイベントで発行されるシグナルと自作の関数を接続しました.
シグナルを自作することも可能です.Signals & Slots にサンプルコード付きで解説されています.
サンプルコードを作成してみました.コードを簡単にする目的で,同じクラス内でシグナルとスロットを作成しています.
これだと関数呼び出しの方が楽ですが,シグナルさえ発行しておけば,後でスロットを追加できるので処理を増やしていくのが楽とか,別のクラスの private 関数を呼び出すのに使うとか,いろいろ応用できそうです.
ソースコード
通信の流れをまとめておきます.
- [ファイル(F)]-[開く(O)] の本体である action_open が活性化すると,シグナル triggered() が発行される
- その triggered() と予め connect されたスロット slot_open() が呼び出される
- slot_open() 内でシグナル signal_opened が発行される
- signal_opend に予め connect されているスロット slot_after_opend() が呼び出される
main_window.h
#include <QMainWindow>
#include <QMenuBar>
class Main_Window : public QMainWindow {
Q_OBJECT
private:
QMenuBar *menu_bar;
QMenu *file_menu;
QAction *action_open;
QAction *action_quit;
//signals 指示子に続いて戻り値のない関数を宣言をすれば,シグナルが定義できる
signals:
void signal_opened();
private slots:
void slot_open();
void slot_after_opened(); //新たに作成する自作スロットの宣言.自作シグナル signal_opened と接続予定
public:
Main_Window(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
};
main_window.cc
#include "main_window.h"
#include <iostream>
Main_Window::Main_Window(QWidget *parent, Qt::WindowFlags f)
: QMainWindow(parent, f),
menu_bar(menuBar())
{
resize(320, 240);
setWindowTitle("Main_Window");
file_menu = new QMenu("ファイル(&F)", this);
menu_bar->addMenu(file_menu);
action_open = new QAction("開く(&O)", this);
file_menu->addAction(action_open);
action_quit = new QAction("終了(&Q)", this);
file_menu->addAction(action_quit);
connect(action_quit, SIGNAL(triggered()), this, SLOT(close()));
connect(action_open, SIGNAL(triggered()), this, SLOT(slot_open()));
//自作シグナルと自作スロットとを接続
connect(this, SIGNAL(signal_opened()), this, SLOT(slot_after_opened()));
}
void Main_Window::slot_open() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
//シグナルの発行.発行したい箇所で emit キーワードを使う
emit signal_opened();
}
//signal_opened と接続されたスロットの定義
void Main_Window::slot_after_opened() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
main.cc および window.pro
変更がないので省略します.
ビルドと実行
手順は上と同じです.
~/tmp$ qmake6
~/tmp$ make
~/tmp$ ./my_window &
実行してメニューバーで [ファイル(F)]-[開く(O)] と操作すると,端末に
void Main_Window::slot_open() void Main_Window::slot_after_opened()
と出力されました.
自作シグナルと自作スロット間で値の授受
上のコードで,シグナルとスロットに引数を付けてみたら動作したので,記しておきます.
ソースコード
main_window.h
#include <QMainWindow>
#include <QMenuBar>
class Main_Window : public QMainWindow {
Q_OBJECT
private:
QMenuBar *menu_bar;
QMenu *file_menu;
QAction *action_open;
QAction *action_quit;
signals:
void signal_opened(int val); //シグナルに引数を与えてみた
private slots:
void slot_open();
void slot_after_opened(int val); //スロットに引数を与えてみた
public:
Main_Window(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
};
main_window.cc
#include "main_window.h"
#include <iostream>
Main_Window::Main_Window(QWidget *parent, Qt::WindowFlags f)
: QMainWindow(parent, f),
menu_bar(menuBar())
{
resize(320, 240);
setWindowTitle("Main_Window");
file_menu = new QMenu("ファイル(&F)", this);
menu_bar->addMenu(file_menu);
action_open = new QAction("開く(&O)", this);
file_menu->addAction(action_open);
action_quit = new QAction("終了(&Q)", this);
file_menu->addAction(action_quit);
connect(action_quit, SIGNAL(triggered()), this, SLOT(close()));
connect(action_open, SIGNAL(triggered()), this, SLOT(slot_open()));
//自作シグナルと自作スロットとを接続してみた.シグナルとスロットの関数の引数は型のみを記す
connect(this, SIGNAL(signal_opened(int)), this, SLOT(slot_after_opened(int)));
}
void Main_Window::slot_open() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
//引数付きシグナルを発行してみた
int val = 123;
emit signal_opened(val);
}
void Main_Window::slot_after_opened(int val) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
std::cout << val << std::endl;
}
main.cc および window.pro
変更がないので省略します.
ビルドと実行
手順は上と同じです.
~/tmp$ qmake6
~/tmp$ make
~/tmp$ ./my_window &
実行してメニューバーで [ファイル(F)]-[開く(O)] と操作すると,端末に
void Main_Window::slot_open() void Main_Window::slot_after_opened(int) 123
と出力されました.