コンソールアプリケーションで試してみましょう
詳しい解説は、関連サイトでも、他のサイトでも説明がありますので、ここでは、サンプルプログラムに絞って話しを進めます。
サンプルファイルを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関連について詳しく知りたい方は、以下の本なども良いと思います。
Qtに関する日本語の本が少ないですね。「入門書」は、さすがに、このページを読まれるくらいの方は不要だと思います。
やっぱり、本+ネット+試してみる!!の3本柱でやっていく以外にないように思います。
コメントをどうぞ