第一件要做的是当然是取得一份GTK的原始码并且安装进您的系统中. 您可以从GIMP取得一份发行版, 或者是从Peter Mattis's的"家中" ftp.xcf.berkely.edu/pub/pmattis(however, it has been changed to ftp.gimp.org)取得一份. GTK使用GNU的autoconf来设定. 一但您解开档案, 输入configure --help来看看选项表列.
在介绍GTK的一开始, 我们尽可能挑最简单的程式. 这个程式将会产生200x200点的视窗, 而且没办法离开, 除非从shell中将它杀掉.
#include <gtk/gtk.h>
int main (int argc, char *argv[])
{
GtkWidget *window;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_show (window);
gtk_main ();
return 0;
}
所有程式理所当然一定会包含gtk/gtk.h, 其中宣告了所有变数, 函数, 及资料及结构. 这些东西您会在您的GTK应用软体中用到.
下一行
gtk_init (&argc, &argv);
呼叫函数gtk_init(gint *argc, gchar ***argv)将会启动GTK. 该函数设定了一些内定的值, 并且後续交给gdk_init(gint *argc, gchar ***argv) 继续处理. 该函数启动了一些函数库以供使用, 设定了内定的信号处理, 检查传给您的程式的命令列参数. 看看以下:
--display
--debug-level
--no-xshm
--sync
--show-events
--no-show-events
这些参数将会从参数表中删去, 所剩下的会传给您做後续的处理. 这样会产生标准的参数表(除了GTK所使用的)以供您使用.
下面这两行程式会产生并显示一个视窗.
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_show (window);
GTK_WINDOW_TOPLEVEL参数指定了我们承习视窗管理程式的外观. 即便我们产生一个0x0大小的视窗, 没有子视窗的视窗内定被设为200x200, 如此我们依然可以处理它.
gtk_widget_show()函数, 让GTK知道, 我们已经处理完设定其属性的工作, 并且可以显示它.
最後一行进入GTK的主要处理回圈.
gtk_main ();
gtk_main()是个在每个GTK应用软体中都会看到的一个函数. 当控制到达这里, GTK会"睡"一下来等待X事件的发生(诸如像按键被按下). 在我们最简单的例子里面, 事件会被忽略掉. 因为我们没有处理它.
好, 现在我们来写一个有一个视窗物件的视窗(一个按钮). 这是个GTK的标准hello world. 这会建立起一个新的GTK软体的良好基础.
#include <gtk/gtk.h>
/* 这是个callback函数. 其资料参数在本例中被忽略
* 以下有更多的callback函数. */
void hello (GtkWidget *widget, gpointer *data)
{
g_print ("Hello World\n");
}
/* another callback */
void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
/* GtkWidget用以储存视窗物件形态 */
GtkWidget *window;
GtkWidget *button;
/* 这在所有GTK应用软体中用到. 参数由命令列中解译出来并且送到该应用软体中. */
gtk_init (&argc, &argv);
/* 产生新视窗 */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* 当视窗收到"destroy"信号时(可由该软体或视窗管理程式所送出)
所会被呼叫到的destroy函数一如以下所定义的一般.
送到该函数的资料将会是NULL,并且在该函数中被忽略 */
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (destroy), NULL);
/* 设定视窗的边框的宽度 */
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* 产生一个新的按钮并带有"Hello World"的字在上面. */
button = gtk_button_new_with_label ("Hello World");
/* 当该按键收到"clicked"信号, 它会呼叫hello()这个函数.
并且以NULL做为其参数. hello()函数在以上已定义过. */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (hello), NULL);
/* 这会导致当"clicked"这个按钮被按下的时候,
呼叫gtk_widget_destroy(window)而使该视窗被关闭
当然了, 关闭的信号会从此处或视窗管理程式所送来 */
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (window));
/* 这个动作会把这个按钮结合到该视窗(a gtk container). */
gtk_container_add (GTK_CONTAINER (window), button);
/* 最後一步是显示最新产生的视窗物件... */
gtk_widget_show (button);
/* 及该视窗 */
gtk_widget_show (window);
/* 所有GTK程式都一定要有gtk_main(). 所有控制结束於此并等带事件的发生
(像按下一键或滑鼠的移动). */
gtk_main ();
return 0;
}
用以下命令来编译:
gcc -Wall -g helloworld.c -o hello_world -L/usr/X11R6/lib \
-lglib -lgdk -lgtk -lX11 -lXext -lm
函数库必须在内定的搜寻路径内, 如果找不到,
-L<library directory> 则gcc会去找这些目录,
看看所需要的函数库是否找得到.
例如, 在我的Debian Linux系统中, 我已经增加了
-L/usr/X11R6/lib
用来寻找X11函数库.
以下函数库是很重要的. linker在处理之前, 必须知道什麽函数要用那一个函数库.
函数库如下:
在我们更进一步探讨hello world之前, 我们要讲一下事件(events)及回呼函数(callbacks). GTK本身是个事件驱动的工具, 这意味著它会在gtk_main进入停歇状态, 一直到一个事件发生, 并且将控制交给适当的函数来处理.
控制权的交出是由"signals"来决定的. 当事件发生, 诸如按下滑鼠的一个按键, 对应的信号会由该视窗物件所送出. 这便是GTK的主要工作. 要使一个按下的动作执行一个命令, 我们设定一个信号处理函数来撷取这个信号, 并且呼叫适当的函数. 这工作是由像以下的函数来完成的:
gint gtk_signal_connect (GtkObject *object,
gchar *name,
GtkSignalFunc func,
gpointer func_data);
其第一个参数是会送出信号的物件, 第二个是希望接取的信号名称. 第三个是当信号送出时的接取函数, 第四个则是要送给该函数的资料.
第三个参数被称为"callback function", 而且必需是以下的形式:
void callback_func(GtkWidget *widget, gpointer *callback_data);
第一个参数是指向该物件的指标, 第二个是在gtk_signal_connect()的最後一个参数.
另外一个在hello world中有用到的函数是:
gint gtk_signal_connect_object (GtkObject *object,
gchar *name,
GtkSignalFunc func,
GtkObject *slot_object);
gtk_signal_connect_object()跟gtk_signal_connect()一样, 除了callback函术只有一个参数, 一个指向GTK物件的指标. 所以当使用这个函数来接到信号时, 该callback函数必须是以下形式:
void callback_func (GtkObject *object);
一般这个object是个widget(物件). 我们一般不设定callback给gtk_signal_connect_object. 他们是用来呼叫GTK函数来接受单一物件(widget or object)做为参数.
有两个函数来连接信号的目的只是希望允许callbacks可以有不同数量的参数. 许多GTK函数仅接受一个GtkWidget指标做为参数, 所以您可以使用gtk_signal_connect_object()来使用这些函数, 而在您的函数里面, 您会需要额外的资料提供给callback.
现在您知道这些理论了, 我们现在来根据这些理论, 把"hello world"这个范例弄清楚.
这是个当按钮被按下时, 会被呼叫到的callback函数. 参数的资料没有被用到.
void hello (GtkWidget *widget, gpointer *data)
{
g_print ("Hello World\n");
}
这是另一个callback函数, 它会呼叫gtk_main_quit()来离开程式.
void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}
int main (int argc, char *argv[])
{
下个部份, 宣告一个指标给GtkWidget. 这是准备用来产生视窗及按钮的.
GtkWidget *window;
GtkWidget *button;
这里是我们的gtk_init. 设定GTK toolkit初始值.
gtk_init (&argc, &argv);
产生新视窗. 这是蛮直接的. 记忆体配置给GtkWidget * window使其成为有效的资料. 它设定一个新的视窗, 但在我们呼叫gtk_widget_show(window)之前不会显示.
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
这里是将object(window)连接到信号处理器的范例. 此处"destroy"是该信号. 该信号是window manager要销去这个视窗时, 或我们送出gtk_widget_destroy()时会产生的. 当我们这样设定时, 我们可同时处理两种状况. 这里我们使用destroy函数, 这使我们可以使用window manager来离开这个程式.
GTK_OBJECT及GTK_SIGNAL_FUNC是分派巨集.
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (destroy), NULL);
下一个函数是用来设定container物件的属性. This just sets the window so it has a blank area along the inside of it 10 pixels wide where no widgets will go. There are other similar functions which we will look at in the section on Setting Widget Attributes
And again, GTK_CONTAINER is a macro to perform type casting.
gtk_container_border_width (GTK_CONTAINER (window), 10);
这个会产生一个新的按钮. 它配置记忆体给一个新的GtkWidget, 并初始化. 他将会有一个标签"Hello World".
button = gtk_button_new_with_label ("Hello World");
然後, 我们让这个按钮做一点事. 我们将他接到一个信号处理器, 因此它会送出"clicked"信号, 而我们的hello()函数会被呼叫到. 资料被忽略, 所以我们只喂NULL给hello(), 明显的, "clicked"信号当我们敲下滑鼠时被送出.
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (hello), NULL);
我们将用这个按钮来离开程式. 这将展示"destroy"信号可以是来自window manager, 或是我们的程式. 当按钮被"clicked", 跟上面一样, 它会呼叫hello() callback函数, 然後是这一个, 以它们被设定的先後顺序被呼叫到. 您可以有任意个callback函数, 它们会以被连接的先後顺序被执行到. 因为gtk_widget_destroy()函数仅接受 GtkWidget *widget做为参数, 我们使用gtk_signal_connect_object() , 而不用gtk_signal_connect().
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (window));
这是个封装呼叫, 我们在後面的文件中会解释. 不过这倒蛮容易理解的. 它就是告诉GTK按钮要放在要显示出来的那个视窗.
gtk_container_add (GTK_CONTAINER (window), button);
现在我们将所有东西照我们的意思来设定好了. 所有信号接好了, 按钮也放到该有的位置, 现在来"show"这个视窗吧. 这个整个视窗会一下子从萤幕蹦出来, 而不是先看到视窗, 然後按钮才跑出来.
gtk_widget_show (button);
gtk_widget_show (window);
还有当然了, 我们呼叫gtk_main()来等待X事件的发生, 当事件发生时, 它将会呼叫物件来送出信号.
gtk_main ();
最後, 程式终止於此. 在gtk_quit()被呼叫到後, 程式会离开.
return 0;
现在, 当我们在GTK上敲下滑鼠, 这个物件会送出"clicked"信号. 我们的程式设定了信号处理器来接取这个信号, 这样我们便可利用这个资讯. 在我们的范例中, 当按钮被"clicked", hello()函数被呼叫到, 并被传入一个NULL参数, 然後下一个处理函数被呼叫到. 它会呼叫gtk_widget_destroy()函数, 传入视窗物件做为参数, 并将该视窗物件销毁. 这会导致该视窗送出"destroy"信号, 收到该信号後, 会呼叫我们的destroy() callback函数, 而我们的destroy()会令程式离开GTK.
另一个方式当然是利用window manager来销毁该视窗. 这也会导致该视窗送出"destroy"信号, 然後呼叫destroy() callback, 然後离开.
这些信号与UNIX系统不太一样, 并非基於UNIX系统的信号系统, 虽然它们的术语是一样的.