| ■ | Gtk+のスタイル |
他のGUIツールキットの場合ですが、Xlib、OSF/Motif、Win32 API は、 いずれもこのような 1.〜 5. の手順を踏みます。 Java AWTの場合は、「手順」というのが明確ではないですが、 基本的には上の5つの処理を何らかの形でプログラミングします。 一方、VisualBasic や PowerBuilder の場合は、 イベントハンドラ以外は基本的に統合開発環境でマウス操作で全部作ってしまいますから、 「手順を踏む」という概念自体がないでしょう。
(*1)ボタンやテキスト入力欄などのGUI部品の呼び名をここでは「ウィジェット」 で統一します。これは OSF/Motif や Tcl/Tk などの用語で、 VisualBasic や Win32 API での「コントロール」とほぼ同義です。
で、本題の Gtk+ ですが、上の5段階はそれぞれ、
| ■ | まずは 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. */
|
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つのボタンが横に並んだウィンドウを作りたいなら、
(*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)