Hello, Qt World

 さてそれではC言語の初歩と同様、Qtで「Hello, World」 と表示するプログラムから始めたいと思いますが、 当サイトではいきなり全開でいきます。 な〜にが全開じゃ、って? Qtでは、「ボタンが押されたら何かする」 という基本的なGUIアプリケーションの初頭でいきなり早くも

というQtの根幹で、しかも(他のGUIツールキットと比べると、初歩にしては) とても難しい敵がいきなり顔を出してくるわけです。 そこで書籍などではこれらを注意深く避けたサンプルから入るものが多いのですが、 それだと、確かにクラスの継承もスロットもmocも出て来なくて簡単に作れるような気にさせますが、 その代わり何の役にもたたないプログラムしか作れません。 そこで、そのような回り道は避けて、最初からこいつらにぶち当たってみよう、 というのがここでのやり方です。

 えー、まずいきなりですが、「Hello, World」をするだけのプログラムであっても、 「ヘッダファイル(.h)」と「本体プログラム(.cpp)」の2つのソースファイルが必要になります。 「一緒に書けばええんとちゃうの」と言わずに、必ず2つに分けましょうね。 ここでは

hello.h
hello.cpp

の2つのファイルを作ります。まず「hello.h」のほうですが:

/* hello.h */
#include "qwidget.h"
class HelloWidget : public QWidget {
Q_OBJECT
public:
  HelloWidget(QWidget* parent, char* name=0);
public slots:
  void hello(void);
};

 簡単なアプリケーションなら、上の例で十分に使い回しが効きます。 Qtでは、Qtのウィジェットの種類ごとに(膨大な数の)ヘッダファイルが用意されているので、 必要なものをすべて、プログラマが #include する必要があります。 たとえば QWidget というウィジェットを使いたかったら qwidget.h をインクルードする、QPushButton というウィジェットを使いたかったら qpushbutton.h をインクルードする、…などなどで、 複雑なプログラムになるとこれがずらっと並ぶことになります。

 で、またもいきなりですが、Qt標準のウィジェットのひとつ QWidget を継承してクラスの定義を行います。 C++の文法については詳しくは省略します。 何か変なキーワードが2つありますが、これはこういうものだ、 ということでとりあえず先に進みます。

 今度はその hello.h で宣言した HelloWidget クラスの実装と、 プログラム本体を記述した hello.cpp を作ります。

/* hello.cpp */
#include <stdio.h>
#include "qapplication.h"
#include "qlabel.h"
#include "qpushbutton.h"
/* ↑使うウィジェットの種類に応じて、全部このように */
/*   インクルードする必要があります */
#include "hello.h"
/* 先程の hello.h も */

HelloWidget::HelloWidget(QWidget* parent, char* name=0){
  QLabel* laba = new QLabel(this);
  QPushButton* cmda = new QPushButton(this);
  QPushButton* cmde = new QPushButton(this);
  /* ウィジェットを作ります。各ウィジェットのコンストラクタ */
  /* には自分の親ウィジェットを渡します。この場合は、これ */
  /* (HelloWidget)の上にラベルを1つと、ボタンを2つ載せて */
  /* います。Qtでは、「載せる」という処理をする関数(メソッド)は */
  /* ありません。ウィンドウが表示されると、その上の子ウィジェット */
  /* は全部自動的に表示してくれます。*/

  laba->setText("Hello, World.");
  cmda->setText("Push Me");
  cmde->setText("Quit");
  /* ラベルとボタンにそれぞれ表示したいテキストをセットして */
  /* います。ここの場合は、QLabelの別のコンストラクタを使い */
  /* laba = new QLabel("Hello, World.", this); */
  /* のように初期化と同時にテキストのセットを行わせることも */
  /* できます。*/

  laba->move(120, 20);
  cmda->move(30,  60);
  cmde->move(170, 60);
  /* QWidgetはいわゆるレイアウトマネジメント(縦横に子をそろえて */
  /* 配置してくれる機能)はまったく行えません。そこで、最も簡単  */
  /* には、このように子のmoveメソッドで、親(ここではHelloWidget)*/
  /* の左上端からの相対位置(ピクセル数)で子を配置させます。     */
  /* ちなみに各ウィジェットの大きさは resize(width, height)     */
  /* というメソッドで自由に変更できるほか、位置とサイズを同時に */
  /* 変更できる setGeometry(x, y, width, height)というメソッド  */
  /* もあります。*/
  QObject::connect(cmda, SIGNAL(clicked()), this, SLOT(hello()));
  QObject::connect(cmde, SIGNAL(clicked()), this, SLOT(close()));
  /* これがうわさのシグナルとスロットです。後述! */

  this->resize(300, 100);
}

void HelloWidget::hello(void){
  fprintf(stdout, "Hello, World.\n");
}

int main(int argc, char* argv[]){
  QApplication* a = new QApplication(argc, argv);
  /* QApplicationのインスタンスを1つ用意 */
  /* Qtアプリケーションは必ずこれから始まります */

  HelloWidget* my = new HelloWidget(NULL);
  /* 上で作った HelloWidget のインスタンスも1丁 */

  a->setMainWidget(my);
  /* アプリケーションの「メインウィジェット」として */
  /* あるウィジェットをセットすると、このウィジェット */
  /* がメインの「ウィンドウ」となり、このウィジェット */
  /* が閉じられるとアプリケーションも自動的に終了します。*/

  my->show();
  /* ウィンドウを表示 */
  return a->exec();
  /* イベントループに入ります。*/
}
/* end. */

 プログラムの大体の紹介は中のコメントで済んでしまうのですが、 あとは最もやっかいで複雑な「シグナルとスロット」の話です。

  QObject::connect(cmda, SIGNAL(clicked()), this, SLOT(hello()));
  QObject::connect(cmde, SIGNAL(clicked()), this, SLOT(close()));

Qtでは、いわゆるイベントハンドラの登録をこのシグナルスロットという機構で行います。
 シグナルとは、「ボタンがクリックされた」「エントリに文字が入った」 などのいわゆるウィンドウイベントのことで、 最初から Qtの各ウィジェットには必要なシグナルは全部定義されています。 たとえばボタンには「clicked」というシグナルが、 リストボックスには「selected」というシグナルが用意されている、 といった具合です。
 一方スロットとは、いわゆるイベントハンドラとして登録する関数で、 例えばボタンがクリックされたら何かする、という時の「何か」 の内容を書く関数です。 こちらスロットについても、基本的なものは用意されています。 たとえば QWidget を含め全ウィジェットは close() というスロットをもっていて、 これが使われるとそのウィジェットは破棄されます。 またスライダー(他のGUIツールキットで「スケール」と呼ばれている、 横にびよ〜んと引っ張るウィジェットです)には setValue(int value) というスロットがあり、 setValue(100)などとするとこのスライダーの現在値を100に合わせることができます。
 実際のプログラムでは、シグナルを自分で定義することはほとんどありませんが、 普通は必要に応じて自分で新しいスロットを定義していくことになります。 なぜなら、ボタンが押されたら何かする「何か」を書くのがアプリケーションの主目的なのですから、 これを自分が書きたいように書けなくては意味がありませんね。 そのために自分で新しいスロットを定義する、ということが必要になります。
 では、実際のシグナルとスロットの接続ですが、例えば 「ボタンbuttonが押されたら、ウィンドウwindowを閉じる」 という処理を登録するのはこうなります:

  QObject::connect(button, SIGNAL(clicked()), window, SLOT(close()));

一方、「リストボックスlstbの項目が選択されたら、 そのインデックスをスライダーsliderで示す」という処理ではこうなります:

  QObject::connect(lstb, SIGNAL(selected(int)), slider, SLOT(setValue(int)));

シグナルとスロットにはそれぞれ「引数」と意味があり、 引数の型が合わなくては(正確には完全に合っているかスロットの方が少なくなければ) いけません。 ここで、シグナルとスロットの括弧の中には (int) のように値ではなく引数の型を書かないといけないのに注意が必要です。

 というようなことを踏まえて、それでは 「ボタンcmdaが押されると、Hello, World と出力する」 という処理はどうなるでしょうか?

  QObject::connect(cmda, SIGNAL(clicked()), this, SLOT(hello()));

 もちろん、標準 Qt には「Hello, World と出力する」 などというけったいなスロットは定義されていません (そんなものいちいち用意していたらきりがありませんね)。 そこでもうお分かりと思いますが、今回定義した HelloWidget クラスで新しく 「hello」という名前のスロットを定義したわけです。 C++の文法的に見ると、

void HelloWidget::hello(void){
  fprintf(stdout, "Hello, World.\n");
}

このように、単なるHelloWidgetクラスのメソッドに見えますが、 実はほんとにスロットは単なるメソッドでもあって、 プログラム中で

int main(int argc, char* argv[]){
  /* ... */
  my->hello();
  /* ... */
}

このように直接呼び出すこともできます。 単なるメソッドを「スロット」としても解釈させる不思議な「おまじない」 が、冒頭の hello.h にあった 「public slots」というキーワード、 と、こういうわけなのです。


moc(メタオブジェクトコンパイラ)

 さて、ではこのプログラムのコンパイルですが、 よく考えると、hello.h で使われている「おまじない」である 「Q_OBJECT」「public slots:」 は、C++文法では明らかに文法エラーです。 そこで、これらを Qt独特のなんかとても深遠でよくわからん方法で適当に(適切に) 解釈してコンパイル可能なコードに変換してくれる Qt付属の変換ツールが 「moc(メタオブジェクトコンパイラ)」 と呼ばれるプログラムです。 これを毎回シェルのコマンドラインで使うのは結構面倒なので、 ここは最初から Makefile を書いて makeユーティリティにコンパイルを行わせましょう。

CC	= gcc
CXX	= g++
QTDIR	= /usr/local/qt-2.0.2
MOC	= ${QTDIR}/bin/moc
CFLAGS	= -I$(QTDIR)/include -g -O
LDFLAGS = -L$(QTDIR)/lib
LIBS	= -lqt
.SUFFIXES: .cpp .cxx .cc .C .c
.cpp.o:
	$(CXX) -c $(CFLAGS) -o $@ $<
SRCS	= hello.cpp moc_hello.cpp
OBJS	= hello.o   moc_hello.o

hello: $(SRCS) $(OBJS)
	$(CXX) -o $@ $(LDFLAGS) $(OBJS2) $(LIBS)
moc_hello.cpp : hello.h
	$(MOC) -o $@ $<

 つまり、まず hello.h を moc にかけて moc_hello.cpp (名前はなんでもOKです)なるソースファイルを1個作ります。 そして、その .cpp ファイルと、元の hello.cpp ファイルをコンパイルし、これらと Qt のライブラリをリンクすることでめでたく 「Hello, World」プログラムができあがります。

セクションのサブメニューに戻る
(first uploaded 2000/03/09 last updated 2002/03/21)