前回 は GUI デザイナ上でのシグナル/スロットの接続機能について学びました。
今回は GUI デザイナのシグナル/スロット関連機能のもう一歩進んだ使い方を学びましょう。
独自シグナル/スロットの接続
前回は既存のウィジェットで定義済みのシグナル/スロットを接続しましたが、自分で追加したシグナル/スロットの接続も可能です。これを試すための準備として、プロジェクトの作成時に生成される QWidget のサブクラス Widget にシグナルとスロットを1つずつ追加しましょう。
widget.h
17 18 19 20 21 | public slots: void setText(const QString &text); signals: void textLengthChanged(int textLength); |
widget.cpp
16 17 18 19 | void Widget::setText(const QString &text) { emit textLengthChanged(text.length()); } |
この記事の最後に全てのソースコードを掲載しており、上記の行番号はそこに対応しています。
文字列を設定するスロット(setText())を作成し、その中では設定された文字列の長さが変わった事を通知するシグナル(textLengthChanged())を発生させています。簡略化のため、設定された文字列を内部で保持したり、現状の文字列と新しい文字列の長さが本当に違うかどうかの判断などはここでは省略しています。
それではフォームを編集し、このシグナル/スロットを他のウィジェットと接続してみましょう。
今回はテキストを入力するための LineEdit とそこに入力された文字列の長さを表示するための Label を以下のように配置します。
次に、シグナル/スロットを接続します。まずはじめに LineEdit の textChanged シグナルを Widget クラスに追加した setText スロットに接続します。シグナルを発生させるウィジェット(LineEdit)からそれを受け取るウィジェット(フォーム自体)にマウスをドロップすると、シグナル/スロットの一覧のダイアログが表示されます。
Widget に作成したスロットはこの一覧には表示されていません。これは、デザイナがこのフォームを QWidget クラスとして扱っているためです。自分で作成したスロットを接続する場合はそのスロットの定義を手動で追加する必要があります。
このダイアログのスロット一覧の下にある「編集…」ボタンを押し、スロット一覧の編集画面を開きます。
次に、Slots の下にある「+」ボタンを押し、Slots の一覧に「setText(QString)」を追加しましょう。
第10回 で解説したとおり、接続するシグナル/スロットの記述に関しては括弧の中に引数の型のみを指定する形になります。
OK ボタンをクリックすると、元のダイアログのスロットの一覧にも追加した setText(QString) が表示されます。LineEdit の textChanged(QString) をこのスロットに接続します。
同じ要領で、Widget に作成した「textLengthChanged(int)」シグナルを Label の setNum(int) スロットに接続してみましょう。
フォームから Label へマウスをドラッグし、シグナルの「編集…」ボタンを押し、Signals に textLengthChanged(int) を追加し、そのシグナルと setNum(int) を接続します。
それでは動作を確認してみましょう。今回は Widget クラスに実装したシグナル/スロットを使用するため、デザイナのプレビューでは確認できません。プロジェクトをビルドし、アプリケーションを実行して確認します。
LineEdit に文字を入力し、配置したラベルに文字数が表示されている事を確認してください。
これで ui ファイルのデザインを適用するクラスで既に作成済みのシグナル/スロットをデザイナから使用する事ができるようになりました。
なお、この機能を使用する場合は、シグナル/スロットの定義を ui ファイルに埋め込む形になるため、
Widget に作成したシグナル/スロット名や引数の変更した場合にはこちらの定義もそれに追従して変更する必要があります。
また、ui ファイルを設定するクラスに存在しないシグナル/スロットを使用しようとしたり、タイプミスなどがあった場合にはアプリケーションの実行時に、接続に失敗した旨の警告メッセージが Qt Creator の”アプリケーション出力ペイン”に表示されますので、動作がおかしい場合にはこれらをチェックしてみてください。
シグナルに対するスロットの作成
UI のデザインをする際には、配置したウィジェットのシグナルが発生した際に(既存のスロットに接続するのではなく)新たにスロットを作成し、そこで様々な処理を行う場面が多く出てくると思います。デザイナにはこの作業を簡単に行える機能もありますので、次はこれを試してみましょう。
以下のようにフォームに PushButton を追加してください。
次に、この PushButton を右クリックし、コンテキストメニューの「Go to slot…」をクリックします。画面は Qt Creator 2.0 ですが、Qt Creator 2.1 の場合は「スロットへ移動…」と日本語化されています。
そのウィジェットが発生させるシグナルの一覧が表示されます。
ここでは clicked() シグナルを選択します。「OK」ボタンをクリックすると以下のように widget.cpp に画面が切り替わります。
Qt Creator によりスロットが追加され、widget.cpp には以下のコードが自動で生成されています。
21 22 23 24 | void Widget::on_pushButton_clicked() { } |
ここにボタンが押された際の処理を書きます。ここでは LineEdit の文字をクリアすることにします。
21 22 23 24 | void Widget::on_pushButton_clicked() { ui->lineEdit->clear(); } |
Qt Creator は追加したスロットの定義を widget.h に生成しています。こちらも確認してみましょう。
26 27 | private slots: void on_pushButton_clicked(); |
それでは、この追加したスロットが動作する事を確認しましょう。
アプリケーションを実行し、LineEdit に適当な文字を入力した後、PushButton をクリックし、LineEdit の内容がクリアされることを確認してみてください。
シグナル/スロットの自動接続機能
今回 Go to slot を使用してのスロット作成時には、シグナル/スロットの接続を行いませんでした。この接続に関しては 第15回 で解説したような接続のためのコードも ui_widget.h をはじめ、どこにも生成されていません。しかし、上記で確認した通り、PushButton をクリックした際には作成したスロットのコードが実行されています。それではこのシグナルとスロットの接続はどこで行われているのでしょうか。
答えは ui_widget.h の setupUi() メソッドにあります。
30 31 32 | void setupUi(QWidget *Widget) { ... |
42 43 44 45 46 47 | ...
QObject::connect(lineEdit, SIGNAL(textChanged(QString)), Widget, SLOT(setText(QString)));
QObject::connect(Widget, SIGNAL(textLengthChanged(int)), label, SLOT(setNum(int)));
QMetaObject::connectSlotsByName(Widget);
} // setupUi |
このメソッドの最後で実行している QMetaObject::connectSlotsByName() では、引数で渡されたオブジェクトに「on_<オブジェクト名>_<シグナル名>(<引数>)」という命名規則のスロットがある場合に一致するオブジェクト、スロットを検索し、自動で接続します。
今回の場合は、Widget クラスに(Qt Creator が追加した)「on_pushButton_clicked()」というスロットがあるので、「pushButton」というオブジェクト名の子オブジェクトを探し、そのオブジェクトに「clicked()」というシグナルがあるかを確認した上で pushButton の clicked() シグナルを Widget の on_pushButton_clicked() スロットに接続しています。
終わりに
今回はウィジェットに追加したシグナル/スロットを Qt Creator のデザイナから接続する方法と、デザイナから直接スロットを作成する方法について学びました。このデザイナが、単にユーザーインターフェースをデザインするだけではなく、アプリケーションを効率的に進めるための便利な機能を提供していることを理解していただけたでしょうか。
最後に、今回変更したソースコードを以下に掲載します。
widget.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #ifndef WIDGET_H #define WIDGET_H #include namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); ~Widget(); public slots: void setText(const QString &text); signals: void textLengthChanged(int textLength); private: Ui::Widget *ui; private slots: void on_pushButton_clicked(); }; #endif // WIDGET_H |
widget.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); } Widget::~Widget() { delete ui; } void Widget::setText(const QString &text) { emit textLengthChanged(text.length()); } void Widget::on_pushButton_clicked() { ui->lineEdit->clear(); } |
Related posts:















5 comments (read them below or add one)
“Go to slot…”(「スロットへ移動…」)を使って自動生成されるスロットの定義先は、どのようにして決定されているのでしょうか?
Qt GUI アプリケーションのプロジェクトテンプレートで開発を開始して、mainwindow.uiに配置したQTreeViewのdoubleClickedシグナルに応答する処理を書こうとgo to slotの機能を使ったのですが、なぜかスロット定義が自作のQWidget派生クラスに追加されてしまい、mainwindow.uiに対応しているMainWindowクラスの方に追加されませんでした。
スロットの場所が自作クラスでもさほど不都合はなかったので、そのままそこに処理を書き手動でconnectしてシグナルへの応答はできたのですが、接続コードを書くのは面倒なのでgo to slotでMainWindow.h/cppの方に定義が追加されるようにしたいのです。
SAM_tak さん。コメントありがとうございます。
Qt Creator は現時点での最新版(2.3.1)でしょうか?
“Go to slot…” で別のクラスにスロットが作成される現象はこちらでは確認したことがないので、再現可能な最小限のサンプルを作成してどこかにアップロードしていただけませんでしょうか。
>Qt Creator は現時点での最新版(2.3.1)でしょうか?
はい。Qt Creator は2.3.1、QtSDKは4.7.4です。
やはり、本来はMainWindow.cpp/hに定義が追加されるものなんですね。
以前は新規アクション追加→スロットへ移動でMainWindow.cpp/hに追加されていたのですが、今適当なアクションを作って試したところ別のファイルにスロットが追加されてしまいました…
再現するサンプルをお渡しできないものか、試してみます
原因が判明しました。
その誤ってスロットが追加されてしまうクラスのcppファイルで、ui_mainwindow.hをインクルードしていたのが良くなかったようです。
再現する最小のサンプルをつくってみようと思ったのですが、ui_mainwindow.hを直接インクルードしていれば必ず再現するというものでは無いようで、再現する確実な条件まではわかりませんでした。
おそらく、フォームデザイナーがそのフォームに対応するcpp/hを判定する際、cppに#include “ui_***.h”があるかどうかで判定しているのではないでしょうか?
プロジェクト構成によってはmainwindow.cppより先に他のファイルがその条件に合致してスロットの追加先に選ばれてしまうことがあるのだと思います。
mainwindow.cpp以外のファイルがui_mainwindow.hをインクルードしてるのは間違った設計でしたので、その必要が無いよう書き改めて事なきを得ました。
相談に乗っていただき、ありがとうございました^^
SAM_tak さん、色々試していただいてありがとうございます。
ご指摘の通り、デザイナーがスロットを追加するクラスは ui_*.h の include 文をヒントに探しているはずで、通常これはスロットを追加すべきクラスのファイルにのみあるべきものです。
複数のクラスにその include 文がある場合は、どのクラスにスロットを追加するのが適切かの判断が難しいため、”Go to slot…” の操作では正しく動かない可能性もありますね。
また何かお気づきの点があればお知らせください。