シグナル/スロット
Qtの代表的な仕組みに、シグナルとスロットがあります。
シグナル/スロットの詳細については、既にたくさんのサイトで解説されているので割愛しますが、簡単に言うと、あるオブジェクトから別のオブジェクトに対して、何らかの事象の発生を通知する仕組みといった感じでしょうか。
シグナル/スロットを設定するには、コード上でconnect()関数を用いる方法と、デザイン画面からUI操作(対象を右クリック→「スロットへ移動」)で設定する方法があります。
後者で設定した場合、コード中にconnect()関数が出現するわけでもないのに、なぜスロット関数が呼び出されるのか?というお話です。
QMetaObject::connectSlotsByName()
QWidgetやQMainWindowなどを継承したクラスは、コンストラクタ内の自動生成コードで以下のような呼び出しがあります。
ui->setupUi(this);
この関数の最後に、以下の呼び出しがあります。
void setupUi(QMainWindow *MainWindow) { if (MainWindow->objectName().isEmpty()) MainWindow->setObjectName(QString::fromUtf8("MainWindow")); // ~~~中略~~~ retranslateUi(MainWindow); QMetaObject::connectSlotsByName(MainWindow); } // setupUi
10行目のQMetaObject::connectSlotsByName()関数がポイントです。
何をやっているかと言うと、引数で渡されたオブジェクトが持っている全てのスロットに対し、以下を満たす関数名が存在するかをチェックします。
on_<子オブジェクト名>_<子オブジェクトのシグナル名>
※ < や > は、便宜上記載しています。
サンプル
と言っても文章では分かりにくいので、サンプルで説明します。
まずはデザイン。(クリックで拡大)
続いてコード。
#include <QTimer> #include "mainwindow.h" #include "ui_mainwindow.h" Label::Label(QWidget *parent) : QLabel(parent) { QTimer::singleShot(2000, [this]() { emit testSignal(); }); } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_testLabel_testSignal() { ui->testLabel->setText(QString(u8"テスト")); }
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QLabel> #include <QMainWindow> namespace Ui { class MainWindow; } class Label : public QLabel { Q_OBJECT public: explicit Label(QWidget *parent = nullptr); ~Label() {} signals: void testSignal(); }; class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); public slots: void on_testLabel_testSignal(); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
例によって、1ファイルに色々詰め込んですみません。
デザインでは、メインウィンドウに「testLabel」というオブジェクト名のラベルを配置し、口述するLabelクラスへと昇格させています。
ヘッダでは、QLabelを継承したLabelクラスを作成し、testSignalというシグナルを用意しています。
また、QMainWindowを継承したMainWindowクラスでは、on_testLabel_testSignal()というスロット関数を用意しています。
ソースでは、Labelのコンストラクタ内で、2秒後にtestSignalをemitするようにQTimer::singleShot()を設定し、MainWindowのon_testLabel_testSignal()スロット関数では、ラベルに「テスト」の文字を表示しています。
ソース内ではconnectをしていませんが、実行すると「テスト」と表示されます。
これは、MainWindowのスロット関数が、先ほど記載した関数名「on_<子オブジェクト名>_<子オブジェクトのシグナル名>」を満たすためです。
子オブジェクト名:testLabel
子オブジェクトのシグナル名:testSignal
→ on_testLabel_testSignal()
とはいえ、個人的にはコード上で明示的にconnectしている方が、分かりやすいかなと思います。