ホーム

OFF-SOFT.net

OFF-SOFT.net

ウェブやソフトウェアに関するサポート&情報サイトです。サイト構築からソフトウェアの作成、利用まであなたの助けになるかも・・・・しれません。たぶん・・。

Qt (2) シグナルとスロットを調べる

公開日| 2009年05月21日 | コメントはまだありません。
概要 :
 Qtの最もQtらしい特徴は、mocとuicという2つのコンパイラを持つことです。

 今回の記事では、その一つであるmocの機能について記述します。
Qtでは、クラス間(オブジェクト指向的に言えば、オブジェクト間)での情報のやり取りを実現するために シグナルとスロットという考え方を用います。

Windows SDKでは、メッセージでの処理に相当します。Windowsでは、HWNDというユニークなIDをキーとして、送信側は、 SendMessage,PostMessageで情報の送信を行います。受信側は、GetMessage,PeekMessageなどで受信できます。
それは、MFCでも同じで、CWndを継承していてもいなくても、SendMessageは使えますし、ラップしたメソッド(SendMessage)もあります。

Qtでは、QObjectを継承したクラスであれば、シグナルとスロットを誰でも扱えます。
また、QObjectを継承したクラス間であれば、それが、たとえスレッドであっても同じように扱えます。
機能的(技術的ではありません)には、Windowsで言えば、シグナルは、SendMessageに相当します。
スロットは、Windowsで、GetMessageからコールバックハンドラをコールするように、そのハンドラのようなものです。

mocは、そのシグナルとスロットをより簡単にプログラマに記述させるための補助的なコンパイラです。
実際に、このコンパイラは、moc_xxxxx.cpp(xxxxx:QObjectを継承したクラス名)というファイルを出力し、 そのファイルをC/C++のコンパイラに渡す前処理を行います。

では、早速、サンプルソースを使って試してみましょう。
ここで使用するプログラムは、下記の関連サイトに記載されているサンプルプログラムを判りやすく、一部、変更したものです。

MOC:メタオブジェクトコンパイラ
UIC:ユーザインターフェイスコンパイラ

関連サイト: http://doc.trolltech.com/4.5/signalsandslots.html

ここで使用したサンプルソースコード:

コンソールアプリケーションで試してみましょう
詳しい解説は、関連サイトでも、他のサイトでも説明がありますので、ここでは、サンプルプログラムに絞って話しを進めます。

サンプルファイルを3つ準備します。

  • sample.cpp
    -- mainルーチンです。
  • counter.h
    -- QObject継承クラスヘッダです。
  • counter.cpp
    -- QObject継承クラスソースです。

ここで、QObject継承のクラスは、必ず、ヘッダとソースに分離しなければなりません。mocが正しく認識しません。

では、それぞれのソースコードを見てみましょう。

[counter.h]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "QObject"
#include "QTextStream"
 
class Counter : public QObject
{
	Q_OBJECT
 
public:
	Counter(const QString sname);
 
	int value() const;
 
public slots:
	void setValue(int value) ;
 
signals:
	void valueChanged(int newValue);
 
private:
	int m_value;
	QString m_name;
 
};
コンストラクタにオブジェクト名の引数をもらうようにしました。
これにより、後述の出力情報で、何がどう動いたかわかりやすくなると思います。

スロットの定義は、"public slot:"以降に記述します。
シグナルの定義は、"signals:"以降に記述します。


[counter.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
25
#include "counter.h"
 
Counter::Counter(const QString sname) 
{
	m_value = 0;
	m_name = sname;
}
 
int Counter::value() const
{
	return m_value;
}
 
void Counter::setValue(int value) 
{
	QTextStream out(stdout);
	if (value != m_value) {
		m_value = value;
		out << "send:[" << m_name << "] " << value << endl;
		emit valueChanged(value);
		out << "completed:[" << m_name << "] " << value << endl;
	} else {
		out << "Ignore:[" << m_name << "] " << value << endl;
	}
}
setValueのメソッドをコールしたとき、引数の数値と内部の属性に持つ数値が異なる場合のみ 設定して、シグナルを発行するようになっています。"emit"がシグナルの発行です。
これにより、後述の出力情報で、何がどう動いたかわかりやすくなると思います。

引数の数値と内部の属性に持つ数値が一致する場合は、無視します。

それぞれのルートでオブジェクト名をもらった引数を出力しています。

シグナルで定義されているvalueChangedの実態がないことに注意してください。
mocで自動的に作成されます。

"emit"は、予約語でもなんでもありません。Qtで独自に定義しているもので、 本来、あってもなくても良いのですが、どこでシグナルを発行しいるかわかるように記述することになっています。


[sample.cpp]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "QObject"
#include "counter.h"
 
 
int main(int argc, char *argv[])
{
	Counter a("a");
	Counter b("b");
 
	QObject::connect(&a, SIGNAL(valueChanged(int)),
	              &b, SLOT(setValue(int)));
	QObject::connect(&b, SIGNAL(valueChanged(int)),
	              &a, SLOT(setValue(int)));
 
	a.setValue(12);
	b.setValue(48);
}

QObject::connectは、シグナルとスロットを結びつける定義です。
各引数は、以下のとおりです。第5引数までありますが、ほとんど、デフォルトで良いので、ここでは説明を割愛します。
詳しく知りたい方は、Qtのマニュアルを参照してください。

第1引数 : シグナルを発行するオブジェクトアドレス
第2引数 : シグナルの定義
第3引数 : スロットで受信するオブジェクトアドレス
第4引数 : スロットの定義

シグナルは、SIGNAL(x)のマクロを使用し、必ず、xで指定するメソッドは、引数まで指定します。
スロットは、SLOT(y)のマクロを使用し、必ず、yで指定するメソッドは、引数まで指定します。

ここで、もし引数の指定を忘れた場合、実行時に以下のようなエラーが出力されます。
1
Object::connect: No such signal Counter::valueChanged() in .\sample.cpp:10
間違った引数の数で、動作が異なりますが、正しく動作しないことには変わりありません。
シグナル発行側のパラメータ数が多い場合は、とりあえず、スロットまで動作しようとします。 当然、正しく動作しないと思いますが、無視しても良いパラメータであれば、それは、正しく動作したように見えるでしょう。

また、逆は、スロットまで到達しません。シグナル発行側で無視されます。


さて、ソースコードの10 - 13行にQObject::connectの記述がありますが、よく見てください。

a.valueChanged -> b.setValue への流れが、10行目です。
b.valueChanged -> a.setValue への流れが、12行目です。

a,b.ともにそれぞれが、シグナルを発行し、受信します。 たすきがけの定義です。しかし、、[counter.cpp]のスロット(受信側)setValueでは、シグナルを、また、発行します。

つまり、a.valueChanged -> b.setValue -> b.valueChanged -> a.setValue となり、最後は、また先頭のシグナルを発行するわけですから、無限に処理が続きそうです。

しかし、[counter.cpp]のスロット(受信側)setValueでは、引数の数値と内部の属性に持つ数値が一致する場合は、無視します。
そのため、無限ループには陥りません。

また、この処理は、よくデッドロックに陥り易い処理です。相互に呼びあう処理になっています。
 オブジェクト a   -->   オブジェクト b  
                 <--      
さて、この実行結果はどうでしょう。思ったとおりに処理されるでしょうか。
前回のQtのコンソールアプリケーションの作成と同様、ここでもコンソールアプリケーションを作成し、実行してみましょう。

1
C:\temp> qmake "CONFIG+=console"
以下がその実行結果です。

1
2
3
4
5
6
7
8
9
10
send:[a] 12
send:[b] 12
Ignore:[a] 12
completed:[b] 12
completed:[a] 12
send:[b] 48
send:[a] 48
Ignore:[b] 48
completed:[a] 48
completed:[b] 48

きれいに動きました。スレッドでも大丈夫なように、この処理では、普通に大丈夫なようです。
(ここで試したものは、シングルタスク、シングルCPU環境下なので、本当は、もっといろんなことを試さないといけません)

通常は、"qmake"のあと、"nmake"で全てのビルドを実行していると思います。 ビルドを実行した場合、moc,uicがclの前に動作します。 moc,uicを個別に手動で実行することもできます。
以下は、そのコマンドの例です。

moc -o debug\moc_counter.cpp counter.h
uic -o ui_sqllogform.h sqllogform.ui

いずれも以下のようなフォーマットです。-oは、出力用のパラメータです。
-o <output filename> <input filename>
もっと詳しく知りたい場合は、リファレンスマニュアルを参照してください。

mocリファレンスマニュアル:"http://doc.trolltech.com/4.5/moc.html
uicリファレンスマニュアル:"http://doc.trolltech.com/4.5/uic.html

ここまでは、Qtの世界を簡単にチュートリアルのサンプルを使って説明しました。

次は、実際の画面を使って同じことをやってみましょう。
今回は、メニューのアップデートとメニューをクリックしたwindowsでいうハンドラを組んでみましょう。

コマンドシグナルとアップデイトシグナル
ここでは、メニューのアップデートとメニューのクリックでのシグナルとスロットを作成してみましょう。

便宜上、メニューのクリックのシグナルをコマンドシグナルと呼びます。メニューのアップデートの時のシグナルをアップデイトシグナル と呼びます。

QtはGUIライブラリですので、次に画面でサンプルを作成してみましょう。

同じようにサンプルファイルを3つ準備します。

  • sample.cpp
    -- mainルーチンです。
  • mainwindow.h
    -- QWidget継承クラスヘッダです。
  • mainwindow.cpp
    -- QWidget継承クラスソースです。

同じように、QWidget(QObject)継承のクラスは、必ず、ヘッダとソースに分離しなければなりません。mocが正しく認識しません。

では、それぞれのソースコードを見てみましょう。

[mainwindow.h]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include <QtGui>
 
class MainWindow : public QWidget
{
	Q_OBJECT
 
public:
	MainWindow();
 
private slots:
	void updateMenus();
 
private:
	QMenuBar *m_menu;
	QMenu *m_fileMenu;
	QAction *m_exit_act;
 
	bool m_active;
};
#endif
今回は、メニューを作成しますので、QMenuBar,QMenu,QActionが追加されています。
m_activeは、メニューアイテムを有効・無効にするための状態フラグです。


[mainwindow.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
25
26
27
#include "mainwindow.h"
 
MainWindow::MainWindow()
{
	m_menu = new QMenuBar( this );
 
	m_fileMenu = m_menu->addMenu(tr("&File"));
 
	m_exit_act = m_fileMenu->addAction("E&xit" );
	m_exit_act->setShortcut(tr("Ctrl+Q"));
	m_exit_act->setStatusTip(tr("Exit the application"));
 
	connect(m_exit_act, SIGNAL(triggered()), 
	         qApp, SLOT(closeAllWindows()));
	connect(m_fileMenu, SIGNAL(aboutToShow()),
	         this, SLOT(updateMenus()));
 
    setWindowTitle(tr("SampleWindow"));
 
	m_active=true;
}
 
void MainWindow::updateMenus()
{
	m_exit_act->setEnabled(m_active);
	m_active=!m_active;
}
5行目で、メニューバーを作成します。メニュー全体を管理するウィジェットです。
7行目で、メニューバーに"File"というポップアップメニューを追加します。
9行目から、"File"というポップアップメニューに"Exit"というメニューアイテムを追加します。

昔は、MFCと同じようにIDなどで管理できるようになっていましたが、現在では、アクションアイテムで 管理するようになっています。IDでもできなくはないのでしょうが、ここは、流れにそってアクションアイテムで 管理するようにしています。

MFCより、VCLに近い感じでしょうか。VCLを使ったC++でもこんな感じでしたね。

13行目は、メニューの"Exit"をクリックした時、qAppの"closeAllWindows"というメソッドを起動する宣言をしています。
つまり、コマンドシグナルとその対応するスロットを定義しています。
15行目は、ポップアップメニューの"File"を表示した時、このオブジェクト(this)の"updateMenus"というメソッドを起動する宣言をしています。
つまり、アップデイトシグナルとその対応するスロットを定義しています。

qAppは、MFCで言うところのAfxGetApp() と同じようなものです。
MFCでは、唯一のCWinAppもしくはCWinAppを継承したオブジェクトが生成された後でないと、正しくオブジェクトを取り出せません。
Qtも同じです。QApplicationがそれに相当します。この実態がないと、qAppは空です。
以下のmainの中で、最初にQApplicationを生成しているのは、そのためでもあります。

23行目からは、ポップアップメニュー"File"が表示されるたびにメニューアイテム"Exit"を有効・無効にトグルで切り替えています。

[mainwindow.cpp]
1
2
3
4
5
6
7
8
9
10
11
#include <QApplication>
 
#include "mainwindow.h"
 
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MainWindow mainWin;
    mainWin.show();
    return app.exec();
}

ここは、説明は必要ないかと思います。単純にアプリケーションを作って、画面を作って表示だけです。

さあ、実行してみましょう。

以下のように、メニューの"Exit"が有効・無効が表示する度に切り替わると思います。



各シグナルの定義は、 クラスリファレンスに全て記載されていますので、そちらで確認されると良いと思います。
ほとんど、必要なシグナルは、揃っていると思います。必要なら個別のシグナルも先に説明したように簡単に作成できます。

Qtクラスリファレンス:http://doc.trolltech.com/4.5/classes.html


もっと、Qt関連について詳しく知りたい方は、以下の本なども良いと思います。
Qtに関する日本語の本が少ないですね。「入門書」は、さすがに、このページを読まれるくらいの方は不要だと思います。
やっぱり、本+ネット+試してみる!!の3本柱でやっていく以外にないように思います。


コメント

コメントをどうぞ







  • はてなブックマークへ追加する
  • Facebookでシェアする
  • twitter でつぶやく
  • Google Plusでシェアする
  • Pocketでシェアする
ページトップへ