[PR]テレビ番組表
今夜の番組チェック


Gtk+のスタイル

 どんなプログラミング言語でも、GUIアプリケーションの初めの動作にはだいたい似たような 「流れ」とゆーものがあります。それはおおむねこんな感じです:

  1. ウィンドウを作ります。
  2. ウィジェット(*1)を作ります。
  3. ウィジェットをウィンドウか、他のウィジェットの上に載せます。
  4. イベントハンドラとなるプロシージャをウィジェットのイベントと関連づけします(どのボタンが押されたらどんな処理をする、など)。
  5. イベントループに入ります。
これらは Tcl/Tk では、それぞれ
  1. 「toplevel」 コマンドに、
  2. 「label」や「entry」などのコマンドに、
  3. 「pack」や「place」などのコマンドに、
  4. 「bind」コマンドに
対応します。5. のイベントループは Tcl/Tk の wish の場合は明示的にはありません (コマンドラインで指定されたスクリプトの実行が終わったら、 とくに指示がなくても勝手にイベントループに入ってしまいます)。

 他のGUIツールキットの場合ですが、Xlib、OSF/Motif、Win32 API は、 いずれもこのような 1.〜 5. の手順を踏みます。 Java AWTの場合は、「手順」というのが明確ではないですが、 基本的には上の5つの処理を何らかの形でプログラミングします。 一方、VisualBasic や PowerBuilder の場合は、 イベントハンドラ以外は基本的に統合開発環境でマウス操作で全部作ってしまいますから、 「手順を踏む」という概念自体がないでしょう。

(*1)ボタンやテキスト入力欄などのGUI部品の呼び名をここでは「ウィジェット」 で統一します。これは OSF/Motif や Tcl/Tk などの用語で、 VisualBasic や Win32 API での「コントロール」とほぼ同義です。

で、本題の Gtk+ ですが、上の5段階はそれぞれ、

  1. gtk_window_new」関数
  2. gtk_label_new」や 「gtk_entry_new」などの関数に、
  3. gtk_container_add」や 「gtk_box_pack_start」などの関数に、
  4. gtk_signal_connect」関数に
  5. gtk_main」関数に
というように、忠実に従っています。
 上であげたGUIツールキットの中では、Gtk+に最も近いのはXLibです。 というか、Gtk+が使っている下位ライブラリの「GDK」は、 単なるXLibのラッパーで、 そのGDKを便利に使えるように便利な関数セットとして組み上げたのが Gtk+ ということができるでしょう。


まずは Hello, World

 ではおなじみの「Hello, World」です。

#include <stdio.h>
#include "gtk/gtk.h"
/* ほとんどの場合gtk/gtk.hだけインクルードすればOKです。 */

static void destroySignalProc(GtkWidget* w, gpointer data){
  /* プログラム終了 */
  gtk_main_quit();
}

static void clickedSignalProc(GtkWidget* w, gpointer data){
  fprintf(stdout, "Hello, World.\n");
}

int main(int argc, char* argv[]){
  GtkWidget* window, * cmda;

  /* gtk_initは必ず呼ぶ */
  gtk_init(&argc, &argv);
  /* ウィンドウを作成する */
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  /* 300x100にサイズを設定 */
  gtk_widget_set_usize(window, 300, 100);
  /* ボタンを作って載せる */
  cmda = gtk_button_new_with_label("Hello, World.");
  gtk_container_set_border_width(GTK_CONTAINER(window), 4);
  gtk_container_add(GTK_CONTAINER(window), cmda);

  /* 「シグナル」を定義 */
  gtk_signal_connect(GTK_OBJECT(window), "destroy",
		     GTK_SIGNAL_FUNC(destroySignalProc), NULL);
  gtk_signal_connect(GTK_OBJECT(cmda), "clicked",
                     GTK_SIGNAL_FUNC(clickedSignalProc), NULL);

  /* 画面にウィジェットを表示 */
  gtk_widget_show(cmda);
  gtk_widget_show(window);
  /* イベントループに入る */
  gtk_main();
}
/* end. */

コンパイルは普通 Makefile を書いて MAKEユーティリティで行います。 Makefile の記述方法はこんな感じです。

CC	= gcc
CFLAGS	= -I/usr/local/include -I/usr/local/lib/glib/include -g -O
LDFLAGS = -L/usr/local/lib
LIBS	= -lglib -lgdk -lgtk -lm
.c.o:
	$(CC) -o $@ $(CFLAGS) -c $*.c
hello: hello.o
	$(CC) -o $@ $(LDFLAGS) $< $(LIBS)

 なお、Gtk+をインストールすると /usr/local/bin などに置かれる 「gtk-config」 ツールを使う方法が一般的ですが、 私はそれが使えない(/usr/X11R6 と /usr/local に別々のバージョンの Gtk+ がインストールされていて、gtk-config に任せるとうまくいかない) ので、ここでは Makefile にスイッチべた書きの方法でやっています。

 さてソースコードの中身ですが、 ウィジェットは全部「GtkWidget」型のポインタとして定義され、

widget = gtk_なんとか_new(?引数?);
という形で作られます。 引数の数や型や意味はウィジェットの種類によって違うので、 これは覚えるか、マニュアルを手もとに置くかしておきましょう。

Gtk+はものすごく大量の関数がありますが、 重要なポイントだけおさえておけばすごく楽になります。 それは、必要な時にマクロによって「型変換」を行うルールです。例えば、

  gtk_container_add(GTK_CONTAINER(window), cmda);
この例では、gtk_container_add関数に渡すために、 GtkWidget* 型の window を、GTK_CONTAINER マクロによって GtkContainer* 型に変換しています。 このような規則は基本的にどんなGtk+の関数にもあてはまります。 例えば
  gtk_list_select_item(GTK_LIST(widget), 1);
とか
  gtk_text_set_word_wrap(GTK_TEXT(widget), TRUE);
このような例をたくさん見るとな〜んとなく型変換の規則性が見えてきますね。 ただし、当然、gtk_button_new_with_label で作ったボタンウィジェットを GTK_LIST でリストに型変換して gtk_list_select_itemに渡すなどということはできません。 このような間違った変換をしようとすると、 Gtk+では、コンパイル時にはエラーにはなりません。ただし、 実行時に 「GTK_LISTで変換すべき型が合わない」 などというエラーメッセージが出てきて怒られます。

 Tcl/Tkで言われている「イベント」 にはファイルイベントやタイマーイベントなどウィジェット動作以外のものもありますが、 Gtk+では基本的にイベントというとウィンドウイベント(ウィジェット動作) に限られます。 Gtk+では、イベントのことを「シグナル」と呼びます。 というか正確には、「イベント」と「シグナル」の両方が存在します。 使い分けですが、おおむね「ボタンが押される」「リストの選択項目が変わる」 などの高位の意味をもつ現象をシグナル、 マウスが動く、キーが押されるなど、 ウィンドウ操作における特定の意味的な傾向をもたない現象をイベントと呼んで区別しています。 シグナルハンドラ(イベントハンドラ)の登録の仕方はソースにあるように関数 gtk_signal_connect を使うだけです。 第2引数の「clicked」や「destroy」などはウィジェットのタイプごとに定義済みで、 たとえばボタンにはclickedがありますがラベルにはない、 というように決まっています。

 ウィジェットを載せるのはここでは関数 gtk_container_add を使っていますが、これは載せる側のウィジェットのタイプ(ここではwindow) が「コンテナ(container)」の系列だからです。 コンテナ系列のウィジェットタイプには他にも「frame」や「button」などがあり(*2) これらは1つのコンテナに1個のウィジェットしか載せることができません。 2個以上のウィジェットを載せられるウィジェットには 「GtkVBox」「GtkHBox」「GtkTable」などがあります。 例えば、2つのボタンが横に並んだウィンドウを作りたいなら、

  1. ウィンドウを作る(gtk_window_new)。
  2. GtkHBoxタイプのウィジェットを作る(gtk_hbox_new)。
  3. GtkButtonタイプのウィジェットを2つ作る(gtk_button_new_with_label)。
  4. 2つのGtkButtonウィジェットをGtkHBoxに載せる(gtk_box_pack_start)。
  5. GtkHBoxウィジェットをウィンドウに載せる(gtk_container_add)。
という手順が必要です。このへんの記述が長ったらしいですし、 また同じ「載せる」関数でも gtk_container_addgtk_box_pack_start などと使い分けないといけないのでややややこしいのですが、 要は慣れでしょう。Gtk+の場合は関数が非常にたくさんあり、 関数の使い分けを把握するのが上達の一番の近道かもしれません。 私は書籍を脇に置くのと一緒に、エディタで /usr/local/include/gtk にインストールされるヘッダファイルの宣言を見ながら関数の名前や引数の型を確認しています。 日本語の完全なリファレンスの整備がまもなくされるかも、 という情報もあるので、そうなるともっと便利になることでしょう。

(*2) 他のGUIライブラリと違う大きな特徴ですが、 Gtk+ では、ボタンやチェックボタンのボタン自身とボタンに書かれている文字列は別々のウィジェットです。 つまり、ボタンというコンテナに、ラベルが載っている、というのがボタンの正確な正体で、 ラベル(label)の代わりにイメージ(pixmap)をボタンに載せると、 絵のついたボタンが出るわけです。

 Gtk+プログラムの最後では gtk_main 関数によってイベントループに入ります。 gtk_main 関数に入ってしまうと、普通はもう二度と戻って来ません。 終了するには、上の関数 destroySignalProc でやっているように、 関数 gtk_main_quit を呼べばOKです。 上の処理では、ウィンドウの右上の×ボタンが押されると「destroy」 シグナルが発生し、gtk_main_quit によりプログラムが終了します。 これを書いていないと、×ボタンを押しても終了しないので注意が必要です。

 最後に、Gtk+はC言語をベースに作ってあるため、 先程のボタンをGTK_LISTでリストに型変換するなど、 関数に誤った型のデータを渡したりすると簡単に異常終了してしまうところですが、 Gtk+ではそれらの型のエラーをうまくトラップして、 警告メッセージを出すようになっています。 この機構のおかげで、XLib や OSF/Motif ではとても難しかった型の違いによる異常終了の原因究明が非常に楽になっています。 これも Gtk+ のとっつきやすさの秘密の一端かもしれませんね。

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