现在的位置: 首页 > 综合 > 正文

【GTK】GTK入门资料(超赞)

2013年10月09日 ⁄ 综合 ⁄ 共 38204字 ⁄ 字号 评论关闭

1. 简介
GTK (GIMP Toolkit) 起源於开发用来做为GIMP (General Image Manipulation Program)的一套工具. GTK建立在GDK (GIMP Drawing Kit)的上层, 基本上是将Xlib功能包装起来. 它被称为GIMP toolkit是因为原来是写来开发GIMP, 但现在被许多免费软体计划所使用. 原作者为

Peter Mattis petm@xcf.berkeley.edu
Spencer Kimball
spencer@xcf.berkeley.edu

Josh MacDonald
jmacd@xcf.berkeley.edu

GTK基本上是物件导向应用软体程式设计介面(API). 虽然完全用C所写成, 他是用classes及callback函数的观念所实作出来的(指向该函数).

还 有另一个被称为glib的函数库被用到, 该函数库包涵了一些标准X函数的替代函数, 及一些额外的处理链结表的函数等等. 这些替代函数是用来增加GTK的可移植性, 因为有些函数需要用到非标准的功能, 诸如g_strerror(). 有些则包含一些libc版本的加强的功能, 诸如g_malloc有加强的除错功能.

这份导引是尽可能去详尽描述GTK的功能, 虽然实在没有办法尽善尽美. 这份导引假设读者对C语言有很相当的基础, 并且知道如何去写C语言程式. 如果读者有过X的程式经验, 会大大有帮助, 但并非绝对需要 (译注: 这一点就好像是要先学MFC或SDK的问题一样). 如果您以GTK做为进入X程式设计的入门的话, 请给我们一些建议, 有关於您在本导引所学到及发现的东西, 及过程中有何困扰. 同时, 目前GTK也有C++ API(GTK--)正在发展, 所以如果您喜欢用C++, 您可能要先去看一看. 同时也有一套Objective
C wrapper, guile bindings版本也有, 但我不建议您走这条路.

同时我也很想知道, 您在由本文学习GTK上有何问题, 我会感谢您告诉我如何改进这些种种的缺点.

2. 开始
第 一件要做的是当然是取得一份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事件的发生(诸如像按键被按下). 在我们最简单的例子里面, 事件会被忽略掉. 因为我们没有处理它.

2.1 用GTK来写Hello World
好, 现在我们来写一个有一个视窗物件的视窗(一个按钮). 这是个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;
}

2.2 编译Hello World
用以下命令来编译:

gcc -Wall -g helloworld.c -o hello_world -L/usr/X11R6/lib //
-lglib -lgdk -lgtk -lX11 -lXext -lm

函数库必须在内定的搜寻路径内, 如果找不到, -L<library directory> 则gcc会去找这些目录, 看看所需要的函数库是否找得到. 例如, 在我的DebianLinux系统中, 我已经增加了 -L/usr/X11R6/lib用来寻找X11函数库.

以下函数库是很重要的. linker在处理之前, 必须知道什麽函数要用那一个函数库.

函数库如下:

glib函数库(-lglib), 包含一些有用的函数, 这个例子中只用到g_print(), 因为GTK是建在glib之上, 所以您几乎都一定会用到它. 详见glib一段.
GDK函数库(-lgdk), Xlib的包装程式.
GTK函数库(-lgtk), 视窗物件函数库, 基於GDK之上.
xlib函数库(-lXlib) 基本上为GDK所用.
Xext函数库(-lXext). 包含了shared memory pixmaps及其它的一些X extensions.
math函数库(-lm). 为GTK所用, 有多方面用途.

2.3 Signals及Callbacks的原理
在我们更进一步探讨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.

2.4 步过Hello World
现在您知道这些理论了, 我们现在来根据这些理论, 把/"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系统的信号系统, 虽然它们的术语是一样的. 

3. 下一步

3.1 资料型态
有 些东西您可能在前面的范例中已经看到, 这需要多解释一下. 像gint, gchar等等. 这些是为了取得绝对乾净的独立性, 如资料大小等等. 像/"gint32/"就是个很好的范例, 其目的是维持到任意平台均为32bits, 不管是64 bit alpha或是32 bit i386. 其定义是极其直接而且直觉的. 它们都被定义在glib/glib.h (被gtk.h所include).

您也看到像在GtkWidget这一类的东西. GTK是物件导向的设计, 而widget则是其中的物件.

3.2 更多关於信号处理函数
我们来看看gtk_signal_connect宣告.

gint gtk_signal_connect (GtkObject *object, gchar *name,
GtkSignalFunc func, gpointer func_data);

看到gint的返回值? 这是个标明您的callback函数的标签值. 像之前所说的, 每个信号及物件可以有好几个callback, 每个会以它们所接上的顺序被轮流执行到. 您可以用以下这个函数来移除这个callback函数:

void gtk_signal_disconnect (GtkObject *object,
gint id);

你可以透过您想要移除的widget handler,给定id, 来解除信号处理函数.
您也可以用:

gtk_signal_disconnect_by_data (GtkObject *object,
gpointer data);

这玩意我倒没用过, 我真得不晓得要怎麽用 :)

另一个函数可以解除所有的处理函数:

gtk_signal_handlers_destroy (GtkObject *object);

这个函数到底是自己解释了自己的功能. 它移除了该物件所有的信号处理函数.

3.3 Hello World的加强版
我们来看看一个稍经改良的hello world, 这是个callback的不错的例子. 这也会介绍到我们下一个主题, 封装物件.

#include

/* 新改进的callback. 输入到该函数的资料被输出到. */
void callback (GtkWidget *widget, gpointer *data)
{
g_print (/"Hello again - %s was pressed//n/", (char *) data);
}

/* another callback */
void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}

int main (int argc, char *argv[])
{
/* GtkWidget is the storage type for widgets */
GtkWidget *window;
GtkWidget *button;
GtkWidget *box1;

/* this is called in all GTK applications. arguments are parsed from
* the command line and are returned to the application. */
gtk_init (&argc, &argv);

/* create a new window */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

/* 这是个新函数, 它设定title到新视窗上/"Hello Buttons!/" */
gtk_window_set_title (GTK_WINDOW (window), /"Hello Buttons!/");

/* 用这样会比较简单一点. */
gtk_signal_connect (GTK_OBJECT (window), /"destroy/",
GTK_SIGNAL_FUNC (destroy), NULL);

/* 设定边框宽度. */
gtk_container_border_width (GTK_CONTAINER (window), 10);

/* 我们产生一个box来封装物件. 这一点会在/"packing/"详述.
这个box实际上看不见, 它只是用来当成是个工具来安排物件 */
box1 = gtk_hbox_new(FALSE, 0);

/* 将box放到主视窗中. */
gtk_container_add (GTK_CONTAINER (window), box1);

/* 产生一个新按钮并带有标签/"Button 1/". */
button = gtk_button_new_with_label (/"Button 1/");

/* 当按钮被按下的时候, 我们呼叫/"callback/"函数
* 并以其指标做为参数送到/"button 1/" */
gtk_signal_connect (GTK_OBJECT (button), /"clicked/",
GTK_SIGNAL_FUNC (callback), (gpointer) /"button 1/");

/* instead of gtk_container_add, we pack this button into the invisible
* box, which has been packed into the window. */
gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0);

/* always remember this step, this tells GTK that our preparation for
* this button is complete, and it can be displayed now. */
gtk_widget_show(button);

/* do these same steps again to create a second button */
button = gtk_button_new_with_label (/"Button 2/");

/* call the same callback function with a different argument,
* passing a pointer to /"button 2/" instead. */
gtk_signal_connect (GTK_OBJECT (button), /"clicked/",
GTK_SIGNAL_FUNC (callback), (gpointer) /"button 2/");

gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0);

/* The order in which we show the buttons is not really important, but I
* recommend showing the window last, so it all pops up at once. */
gtk_widget_show(button);

gtk_widget_show(box1);

gtk_widget_show (window);

/* rest in gtk_main and wait for the fun to begin! */
gtk_main ();

return 0;
}

将 这个程式以相同的参数编译, 您会看到没有任何方法来离开程式, 您必须使用视窗管理程式或命令列来杀掉它. 对读者来说, 加个/"Quit/"按钮会是个不错的练习. 您也可以玩一玩gtk_box_pack_start()这个东西. 试试拉一拉视窗, 看看有什麽变换.

另外有个蛮有用的define给gtk_window_new()用 - GTK_WINDOW_DIALOG. 它的互动行为有点不太一样.

4. 封装物件
当 我们制作一套软体, 您会希望在视窗内放超过一个以上的按钮. 我们第一个范例/"hello world/"仅用一个物件, 因此我们能够很简单使用gtk_container_add来/"封装/"该物件到视窗中. 但当您希够望放更多的物件到视窗中, 要如何控制它们的位置? 这里就要用到/"封装/"(Packing).

4.1 Packing Boxes的理论
大部份 的封装是由产生boxes来达成的. 这些是看不见的widget containers, 我们可以用两种形式来将我们的物件封装进去, vertical box及horizontal box. 当我们封装物件到一个horizontal box时, 物件是依我们呼叫的顺序由右至左平行的被新增进去. 在vertical box, 物件是由上至下. 您可以将物件插入box, 也可以将boxes插入box, 任意的组合用以产生所想要的效果.

要产生horizontal box,我们使用gtk_hbox_new(), 而vertical boxe使用gtk_vbox_new(). gtk_box_pack_start()及gtk_box_pack_end()函数是用来将物件放到containers里面. gtk_box_pack_start()函数会开始由左至右, 由上至下来封装物件. gtk_box_pack_end()则相反, 由下至上, 由右至左. 使用这些函数允许我们对右边或对左边较正, 而且可以用许多种方式来较正来取得所想要的效果. 一个object可以是另一个container或物件.
而且事实上, 许多物件本身也是containers. 像按钮就是, 不过我们一般只在按钮中用一个标签.

使用这些呼叫, GTK知道要把物件放到那里去, 并且会自动缩放及其它比例上的调整. 还有许多其它选项可以控制如何将物件封装在一起. 正如您所想的, 这些方法可以给您许多的弹性来制作视窗.

4.2 Boxes详述
由於这样的弹性, packing boxes在一开始使用的话会有点搞糊涂. 还有许多其它的选项,一开始还看不太出来它们如何凑在一起. 最後您会知道, 他们基本上有五种不同的型式.

每一行包含一个horizontal box (hbox)及好几个按钮. 所有按钮都是以同样的方式来包入hbox内.

这是gtk_box_pack_start的宣告.

void gtk_box_pack_start (GtkBox *box,
GtkWidget *child,
gint expand,
gint fill,
gint padding);

第一个参数是您要把object放进去的box, 第二个是该object. 现在这些物件将会都是按钮.

expand 参数在gtk_box_pack_start()或gtk_box_pack_end()中控制物件如何在box中排列. expand = TRUE的话它们会填满box中所有额外的空间. expand = FALSE的话, 该box会缩小到刚好该物件的大小. 设expand=FALSE您可做好左右较正. 否则它们会填满整个box. 同样的效果可用tk_box_pack_start或pack_end functions来达成.

fill参数在gtk_box_pack中控制额外空间. fill=TRUE该物件会自行产生额外空间, fill=FALSE则由box产生一个在物件周围的填白区域. 这只有在expand=TRUE时, 才会有作用.

当产生一个新的box, 该函数看起来像这样:

GtkWidget * gtk_hbox_new (gint homogeneous,
gint spacing);

homogeneous参数在gtk_hbox_new (and the same for gtk_vbox_new) 控制每个物件是否有同样的宽或高. 若homogeneous=TRUE, 则expand也会被开启.

空白(spacing)及填白(padding)有什麽不同呢空白是加在物件之间, 填白只加在物件的一边. 看以下这张图可能会明白一点:

这里是一些用来产生以上影像的程式. 我做了蛮多的注解, 希望您不会有问题. 将它编译然後玩玩它.

4.3 封装示范程式

#include /"gtk/gtk.h/"

void
destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}

/* Make a new hbox filled with button-labels. Arguments for the
* variables we/"re interested are passed in to this function.
* We do not show the box, but do show everything inside. */
GtkWidget *make_box (gint homogeneous, gint spacing,
gint expand, gint fill, gint padding)
{
GtkWidget *box;
GtkWidget *button;
char padstr[80];

/* create a new hbox with the appropriate homogeneous and spacing
* settings */
box = gtk_hbox_new (homogeneous, spacing);

/* create a series of buttons with the appropriate settings */
button = gtk_button_new_with_label (/"gtk_box_pack/");
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);

button = gtk_button_new_with_label (/"(box,/");
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);

button = gtk_button_new_with_label (/"button,/");
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);

/* create a button with the label depending on the value of
* expand. */
if (expand == TRUE)
button = gtk_button_new_with_label (/"TRUE,/");
else
button = gtk_button_new_with_label (/"FALSE,/");

gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);

/* This is the same as the button creation for /"expand/"
* above, but uses the shorthand form. */
button = gtk_button_new_with_label (fill ? /"TRUE,/" : /"FALSE,/");
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);

sprintf (padstr, /"%d);/", padding);

button = gtk_button_new_with_label (padstr);
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);

return box;
}

int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *button;
GtkWidget *box1;
GtkWidget *box2;
GtkWidget *separator;
GtkWidget *label;
GtkWidget *quitbox;
int which;

/* Our init, don/"t forget this! :) */
gtk_init (&argc, &argv);

if (argc != 2) {
fprintf (stderr, /"usage: packbox num, where num is 1, 2, or 3.//n/");
/* this just does cleanup in GTK, and exits with an exit status of 1. */
gtk_exit (1);
}

which = atoi (argv[1]);

/* Create our window */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

/* You should always remember to connect the destroy signal to the
* main window. This is very important for proper intuitive
* behavior */
gtk_signal_connect (GTK_OBJECT (window), /"destroy/",
GTK_SIGNAL_FUNC (destroy), NULL);
gtk_container_border_width (GTK_CONTAINER (window), 10);

/* We create a vertical box (vbox) to pack the horizontal boxes into.
* This allows us to stack the horizontal boxes filled with buttons one
* on top of the other in this vbox. */
box1 = gtk_vbox_new (FALSE, 0);

/* which example to show. These correspond to the pictures above. */
switch (which) {
case 1:
/* create a new label. */
label = gtk_label_new (/"gtk_hbox_new (FALSE, 0);/");

/* Align the label to the left side. We/"ll discuss this function and
* others in the section on Widget Attributes. */
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);

/* Pack the label into the vertical box (vbox box1). Remember that
* widgets added to a vbox will be packed one on top of the other in
* order. */
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);

/* show the label */
gtk_widget_show (label);

/* call our make box function - homogeneous = FALSE, spacing = 0,
* expand = FALSE, fill = FALSE, padding = 0 */
box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);

/* call our make box function - homogeneous = FALSE, spacing = 0,
* expand = FALSE, fill = FALSE, padding = 0 */
box2 = make_box (FALSE, 0, TRUE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);

/* Args are: homogeneous, spacing, expand, fill, padding */
box2 = make_box (FALSE, 0, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);

/* creates a separator, we/"ll learn more about these later,
* but they are quite simple. */
separator = gtk_hseparator_new ();

/* pack the separator into the vbox. Remember each of these
* widgets are being packed into a vbox, so they/"ll be stacked
* vertically. */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);

/* create another new label, and show it. */
label = gtk_label_new (/"gtk_hbox_new (TRUE, 0);/");
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
gtk_widget_show (label);

/* Args are: homogeneous, spacing, expand, fill, padding */
box2 = make_box (TRUE, 0, TRUE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);

/* Args are: homogeneous, spacing, expand, fill, padding */
box2 = make_box (TRUE, 0, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);

/* another new separator. */
separator = gtk_hseparator_new ();
/* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);

break;

case 2:

/* create a new label, remember box1 is a vbox as created
* near the beginning of main() */
label = gtk_label_new (/"gtk_hbox_new (FALSE, 10);/");
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
gtk_widget_show (label);

/* Args are: homogeneous, spacing, expand, fill, padding */
box2 = make_box (FALSE, 10, TRUE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);

/* Args are: homogeneous, spacing, expand, fill, padding */
box2 = make_box (FALSE, 10, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);

separator = gtk_hseparator_new ();
/* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);

label = gtk_label_new (/"gtk_hbox_new (FALSE, 0);/");
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
gtk_widget_show (label);

/* Args are: homogeneous, spacing, expand, fill, padding */
box2 = make_box (FALSE, 0, TRUE, FALSE, 10);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);

/* Args are: homogeneous, spacing, expand, fill, padding */
box2 = make_box (FALSE, 0, TRUE, TRUE, 10);
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);

separator = gtk_hseparator_new ();
/* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
break;

case 3:

/* This demonstrates the ability to use gtk_box_pack_end() to
* right justify widgets. First, we create a new box as before. */
box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
/* create the label that will be put at the end. */
label = gtk_label_new (/"end/");
/* pack it using gtk_box_pack_end(), so it is put on the right side
* of the hbox created in the make_box() call. */
gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0);
/* show the label. */
gtk_widget_show (label);

/* pack box2 into box1 (the vbox remember ? :) */
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
gtk_widget_show (box2);

/* a separator for the bottom. */
separator = gtk_hseparator_new ();
/* this explicitly sets the separator to 400 pixels wide by 5 pixels
* high. This is so the hbox we created will also be 400 pixels wide,
* and the /"end/" label will be separated from the other labels in the
* hbox. Otherwise, all the widgets in the hbox would be packed as
* close together as possible. */
gtk_widget_set_usize (separator, 400, 5);
/* pack the separator into the vbox (box1) created near the start
* of main() */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
}

/* Create another new hbox.. remember we can use as many as we need! */
quitbox = gtk_hbox_new (FALSE, 0);

/* Our quit button. */
button = gtk_button_new_with_label (/"Quit/");

/* setup the signal to destroy the window. Remember that this will send
* the /"destroy/" signal to the window which will be caught by our signal
* handler as defined above. */
gtk_signal_connect_object (GTK_OBJECT (button), /"clicked/",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (window));
/* pack the button into the quitbox.
* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */
gtk_box_pack_start (GTK_BOX (quitbox), button, TRUE, FALSE, 0);
/* pack the quitbox into the vbox (box1) */
gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0);

/* pack the vbox (box1) which now contains all our widgets, into the
* main window. */
gtk_container_add (GTK_CONTAINER (window), box1);

/* and show everything left */
gtk_widget_show (button);
gtk_widget_show (quitbox);

gtk_widget_show (box1);
/* Showing the window last so everything pops up at once. */
gtk_widget_show (window);

/* And of course, our main function. */
gtk_main ();

/* control returns here when gtk_main_quit() is called, but not when
* gtk_exit is used. */

return 0;
}

4.4 使用表格来封装
我们来看看另一个封装的方法 - 用表格. 在很多状况下, 这是极其有用的.

使用表格, 我们产生格线来将物件放入. 物件会照我们安排的位置排入.

我们第一个要看的是gtk_table_new这个函数:

GtkWidget* gtk_table_new (gint rows,
gint columns,
gint homogeneous);

第一个参数是多少列, 第二个是多少栏.

homogeneous 参数用来决定表格如何来定大小. 若homogeneous为TRUE, table boxes会被重定为在其中最大物件的大小. 若homogeneous为FALSE, 则其大小为, /"高/"为列中最高的物件, 及/"宽/"栏中最宽的物件大小.

列及栏的编号为从0到n. n是我们在gtk_table_new中所指定的值. 所以, 如果您指定rows = 2及columns = 2, 整个排列会看起来像这样:

0 1 2
0+----------+----------+
| | |
1+----------+----------+
| | |
2+----------+----------+

坐标系统开始於左上角. 要把物件放进box中, 可用以下函数:

void gtk_table_attach (GtkTable *table,
GtkWidget *child,
gint left_attach,
gint right_attach,
gint top_attach,
gint bottom_attach,
gint xoptions,
gint yoptions,
gint xpadding,
gint ypadding);

第一个参数(/"table/")是您才刚产生的表格, 而第二个(/"child/")是您想放进去的物件.

而left_attach 及right_attach参数指定要把物件放在那里, 及用多少个boxes. 如果您想要用右下角的表格, 可以这样填表. left_attach = 1, right_attach = 2, top_attach = 1, bottom_attach = 2.

现在, 如果您想要物件来使用上面2x2的表格, 您可以使用left_attach = 0, right_attach =2, top_attach = 0, bottom_attach = 1.

xoptions及yoptions是用来指定封装选项, 可以同时组合多个选项(用or).

这些选项是:

GTK_FILL - 如果table box大过物件, 且GTK_FILL 被指定了, 该物件会扩展成使用所有可用的空间.
GTK_SHRINK - 如果table widget小於该物件, (一般是使用者缩放该视窗), 那麽该物件将会一直被挤压到看不见为止. 如果GTK_SHRINK被指定了, 该物件会跟著table一起缩小.
GTK_EXPAND - 这会使table本身扩展, 并利用视窗中所有可用空间.
填空就像boxes, 产生一个在物件周边空白的区域.

gtk_table_attach()有许多选项. 这里有个捷径:

void gtk_table_attach_defaults (GtkTable *table,
GtkWidget *widget,
gint left_attach,
gint right_attach,
gint top_attach,
gint bottom_attach);

X及Y选项内定为GTK_FILL | GTK_EXPAND, X及Y填空则设为0. 其馀的参数则相同於以上的函数.

我们另外有gtk_table_set_row_spacing()及gtk_table_set_col_spacing(). 这些会在指定的栏及列插入空白.

void gtk_table_set_row_spacing (GtkTable *table,
gint row,
gint spacing);


void gtk_table_set_col_spacing (GtkTable *table,
gint column,
gint spacing);

对栏来说, 空格是在栏的右边. 而列则是在下面.

您也可以用以下函数来产生固定的空格.

void gtk_table_set_row_spacings (GtkTable *table,
gint spacing);

及,

void gtk_table_set_col_spacings (GtkTable *table,
gint spacing);

使用这些函数, 其最後一栏及最後一列并没有空格存在.

4.5 Table Packing范例
目前并无说明, 请参照testgtk.c

5. 物件概论

在GTK下,一般产生物件的步骤为:

gtk_*_new - 最普遍产生物件的函数.
连接信号到信号处理器.
设定物件属性.
要将物件包装到一个container可用gtk_container_add()或gtk_box_pack_start().
gtk_widget_show().
gtk_widget_show ()让GTK知道我们已经完成设定的工作, 并且已经准备好要显示. 您也可以用gtk_widget_hide来隐藏它. 显示物件的顺序并不太重要, 但我建议最後才显示, 这样才不会看到这些视窗们一个一个被画出来. 子物件在使用gtk_widget_show使视窗出现之前是不会被显示出来的.

5.1 分派系统
再继续下去您会发现, GTK使用一种分派系统. 一般是用巨集来完成. 您可以看到诸如以下:

GTK_WIDGET(widget)
GTK_OBJECT(object)
GTK_SIGNAL_FUNC(function)
GTK_CONTAINER(container)
GTK_WINDOW(window)
GTK_BOX(box)
这些在函数中的都是分派参数. 您可以在范例中看到, 而且只要看到该函数就会知道它们是做什麽用的.

从以下的组织图来看, 所有GtkWidgets都是由GtkObject而来. 这意味著您可以在任何地方, 透过GTK_OBJECT()巨集要求一个物件.

例如:

gtk_signal_connect(GTK_OBJECT(button), /"clicked/",
GTK_SIGNAL_FUNC(callback_function), callback_data);

这样分派一个按钮给一个物件, 并且提供一个指标给callback函数.

许多物件同时也是containers. 如果您看看以下的组织图, 您会看到许多物件由GtkContainer而来 所有这一类的物件都可以用GTK_CONTAINER巨集产生使用containers.

5.2 物件组织图
这里是一些参考, 物件组织图.

GtkObject
+-- GtkData
| //-- GtkAdjustment
|
//-- GtkWidget
+-- GtkContainer
| +-- GtkBin
| | +-- GtkAlignment
| | +-- GtkFrame
| | | *-- GtkAspectFrame
| | |
| | +-- GtkItem
| | | +-- GtkListItem
| | | +-- GtkMenuItem
| | | | +-- GtkCheckMenuItem
| | | | *-- GtkRadioMenuItem
| | | |
| | | *-- GtkTreeItem
| | |
| | +-- GtkViewport
| | //-- GtkWindow
| | +-- GtkDialog
| | //-- GtkFileSelection
| |
| +-- GtkBox
| | +-- GtkHBox
| | //-- GtkVBox
| | +-- GtkColorSelection
| | //-- GtkCurve
| |
| +-- GtkButton
| | +-- GtkOptionMenu
| | //-- GtkToggleButton
| | //-- GtkCheckButton
| | //-- GtkRadioButton
| |
| +-- GtkList
| +-- GtkMenuShell
| | +-- GtkMenu
| | //-- GtkMenuBar
| |
| +-- GtkNotebook
| +-- GtkScrolledWindow
| +-- GtkTable
| //-- GtkTree
|
+-- GtkDrawingArea
+-- GtkEntry
+-- GtkMisc
| +-- GtkArrow
| +-- GtkImage
| +-- GtkLabel
| //-- GtkPixmap
|
+-- GtkPreview
+-- GtkProgressBar
+-- GtkRange
| +-- GtkScale
| | +-- GtkHScale
| | //-- GtkVScale
| |
| //-- GtkScrollbar
| +-- GtkHScrollbar
| //-- GtkVScrollbar
|
+-- GtkRuler
| +-- GtkHRuler
| //-- GtkVRuler
|
//-- GtkSeparator
+-- GtkHSeparator
//-- GtkVSeparator

5.3 没有视窗的物件
以下的物件跟视窗没有关系. 如果您希望接取它们的事件, 您需要使用GtkEventBox. 请见 EventBox物件

GtkAlignment
GtkArrow
GtkBin
GtkBox
GtkImage
GtkItem
GtkLabel
GtkPaned
GtkPixmap
GtkScrolledWindow
GtkSeparator
GtkTable
GtkViewport
GtkAspectFrame
GtkFrame
GtkVPaned
GtkHPaned
GtkVBox
GtkHBox
GtkVSeparator
GtkHSeparator

再过来我们会一个一个物件来示范如何产生及显示. 一个很好的范例是testgtk.c, 您可以在gtk/testgtk.c里面找到.

7. Tooltips物件
他们是当您停在某个物件(像按钮或其它物件)上几秒时, 会自动出现的一个小的文字视窗. 它们很容易使用, 因此我只解释一下, 而不给范例程式. 如果您想看看一些范例程式, 可参考GDK内的testgtk.c.

有些物件(像标签)无法与tooltips一起用.

第一个呼叫的函数会产生一个新的tooltip. 您只需要呼叫这个函数一次. GtkTooltip这个函数的返回值可用来产生许多个tooltips.

GtkTooltips *gtk_tooltips_new (void);

一旦您产生了一个新的tooltip, 您要设定到某个物件上, 只要呼叫这个函数即可.

void gtk_tooltips_set_tips (GtkTooltips *tooltips,
GtkWidget *widget,
gchar *tips_text);

第一个参数是您刚才产生的tooltip, 接著是您希望使用的物件, 然後是您希望显示的文字.

这里有个简短的范例:

GtkTooltips *tooltips;
GtkWidget *button;
...
tooltips = gtk_tooltips_new ();
button = gtk_button_new_with_label (/"button 1/");
...
gtk_tooltips_set_tips (tooltips, button, /"This is button 1/");

tooltip还有其它的一些函数. 我只简短的介绍一下.

void gtk_tooltips_destroy (GtkTooltips *tooltips);

销毁tooltips.

void gtk_tooltips_enable (GtkTooltips *tooltips);

使一套已失效的tooltips生效.

void gtk_tooltips_disable (GtkTooltips *tooltips);

使一套tooltips生效.

void gtk_tooltips_set_delay (GtkTooltips *tooltips,
gint delay);

设定要停留多少ms, tooltip才会出现. 内定值是1000ms, 即一秒.

void gtk_tooltips_set_tips (GtkTooltips *tooltips,
GtkWidget *widget,
gchar *tips_text);

改变一个tooltip的文字内容.

void gtk_tooltips_set_colors (GtkTooltips *tooltips,
GdkColor *background,
GdkColor *foreground);

设定tooltips的前景及背景颜色.

8. Container物件

8.1 笔记本物件
笔记本物件好几个/"页/"的集合, 它们互相交叠在一起, 并可包含不同的讯息. 这个物件在GUI越来越普及, 它是个在显示有类同功能的资讯时很有用的物件.

第一个您会用到的是产生一个新的笔记本物件.

GtkWidget* gtk_notebook_new (void);

一旦物件产生後, 共有12个函数可以操作该笔记本物件. 我们一个一个来看.

第一个是要如何来安排/"页标签/". 这些/"页标签/"或/"tabs/", 可以用四个位置, 上, 下, 左, 右.

void gtk_notebook_set_tab_pos (GtkNotebook *notebook, GtkPositionType pos);

GtkPostionType可以是以下四个, 很好认.

GTK_POS_LEFT
GTK_POS_RIGHT
GTK_POS_TOP
GTK_POS_BOTTOM
GTK_POS_TOP是内定值.

接下来我们来看如何加/"一页/"到笔记本上. 共有三种方法来加页到笔记本上.

void gtk_notebook_append_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label);

void gtk_notebook_prepend_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label);

这些函数新增一页到笔记本, append由後新增, prepend由前新增. *child是要插入笔记本的物件, *tab_label是页标签.

void gtk_notebook_insert_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label, gint position);

参数与_append_及_prepend_相同, 除了多出一个参数, 位置. 该参数用来指定要插在那里.

现在我们知道要如何新增一页, 再来看看如何移除.

void gtk_notebook_remove_page (GtkNotebook *notebook, gint page_num);

这个函数移除掉所指定的那一页.

要找出目前正在那一页, 可用以下函数:

gint gtk_notebook_current_page (GtkNotebook *notebook);

以下两个函数是向前或向後移动. 若目前在最後一页, 而您用gtk_notebook_next_page, 那麽笔记本会绕回第一页, 反之亦然.

void gtk_notebook_next_page (GtkNoteBook *notebook);
void gtk_notebook_prev_page (GtkNoteBook *notebook);

以下函数设定/"有效页/". 如果您希望笔记本开启到例如第五页, 您可以用这个函数. 内定页为第一页.

void gtk_notebook_set_page (GtkNotebook *notebook, gint page_num);

以下两个函数可新增及移除页标签及边框.

void gtk_notebook_set_show_tabs (GtkNotebook *notebook, gint show_tabs);
void gtk_notebook_set_show_border (GtkNotebook *notebook, gint show_border);

show_tabs及show_border可以是TRUE或FALSE(0或1).

现 在我们来看个范例, 它是从testgtk.c中展开的, 用了所有13个函数. 该程式产生一个笔记本及六个按钮, 包含11页, 以三种方式加页, appended, inserted,及prepended. 这些按钮允许您旋转页标签位置, 新增/移除页标签及边框, 移除一页, 以前向及後向改变页的位置, 及离开程式.

#include

/* This function rotates the position of the tabs */
void rotate_book (GtkButton *button, GtkNotebook *notebook)
{
gtk_notebook_set_tab_pos (notebook, (notebook->tab_pos +1) %4);
}

/* Add/Remove the page tabs and the borders */
void tabsborder_book (GtkButton *button, GtkNotebook *notebook)
{
gint tval = FALSE;
gint bval = FALSE;
if (notebook->show_tabs == 0)
tval = TRUE;
if (notebook->show_border == 0)
bval = TRUE;

gtk_notebook_set_show_tabs (notebook, tval);
gtk_notebook_set_show_border (notebook, bval);
}

/* Remove a page from the notebook */
void remove_book (GtkButton *button, GtkNotebook *notebook)
{
gint page;

page = gtk_notebook_current_page(notebook);
gtk_notebook_remove_page (notebook, page);
/* Need to refresh the widget --
This forces the widget to redraw itself. */
gtk_widget_draw(GTK_WIDGET(notebook), NULL);
}

void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}

int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *button;
GtkWidget *table;
GtkWidget *notebook;
GtkWidget *frame;
GtkWidget *label;
GtkWidget *checkbutton;
int i;
char bufferf[32];
char bufferl[32];

gtk_init (&argc, &argv);

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

gtk_signal_connect (GTK_OBJECT (window), /"destroy/",
GTK_SIGNAL_FUNC (destroy), NULL);

gtk_container_border_width (GTK_CONTAINER (window), 10);

table = gtk_table_new(2,6,TRUE);
gtk_container_add (GTK_CONTAINER (window), table);

/* Create a new notebook, place the position of the tabs */
notebook = gtk_notebook_new ();
gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP);
gtk_table_attach_defaults(GTK_TABLE(table), notebook, 0,6,0,1);
gtk_widget_show(notebook);

/* lets append a bunch of pages to the notebook */
for (i=0; i < 5; i++) {
sprintf(bufferf, /"Append Frame %d/", i+1);
sprintf(bufferl, /"Page %d/", i+1);

frame = gtk_frame_new (bufferf);
gtk_container_border_width (GTK_CONTAINER (frame), 10);
gtk_widget_set_usize (frame, 100, 75);
gtk_widget_show (frame);

label = gtk_label_new (bufferf);
gtk_container_add (GTK_CONTAINER (frame), label);
gtk_widget_show (label);

label = gtk_label_new (bufferl);
gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, label);
}

/* now lets add a page to a specific spot */
checkbutton = gtk_check_button_new_with_label (/"Check me please!/");
gtk_widget_set_usize(checkbutton, 100, 75);
gtk_widget_show (checkbutton);

label = gtk_label_new (/"Add spot/");
gtk_container_add (GTK_CONTAINER (checkbutton), label);
gtk_widget_show (label);
label = gtk_label_new (/"Add page/");
gtk_notebook_insert_page (GTK_NOTEBOOK (notebook), checkbutton, label, 2);

/* Now finally lets prepend pages to the notebook */
for (i=0; i < 5; i++) {
sprintf(bufferf, /"Prepend Frame %d/", i+1);
sprintf(bufferl, /"PPage %d/", i+1);

frame = gtk_frame_new (bufferf);
gtk_container_border_width (GTK_CONTAINER (frame), 10);
gtk_widget_set_usize (frame, 100, 75);
gtk_widget_show (frame);

label = gtk_label_new (bufferf);
gtk_container_add (GTK_CONTAINER (frame), label);
gtk_widget_show (label);

label = gtk_label_new (bufferl);
gtk_notebook_prepend_page (GTK_NOTEBOOK(notebook), frame, label);
}

/* Set what page to start at (page 4) */
gtk_notebook_set_page (GTK_NOTEBOOK(notebook), 3);

/* create a bunch of buttons */
button = gtk_button_new_with_label (/"close/");
gtk_signal_connect_object (GTK_OBJECT (button), /"clicked/",
GTK_SIGNAL_FUNC (destroy), NULL);
gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,1,2);
gtk_widget_show(button);

button = gtk_button_new_with_label (/"next page/");
gtk_signal_connect_object (GTK_OBJECT (button), /"clicked/",
(GtkSignalFunc) gtk_notebook_next_page,
GTK_OBJECT (notebook));
gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,1,2);
gtk_widget_show(button);

button = gtk_button_new_with_label (/"prev page/");
gtk_signal_connect_object (GTK_OBJECT (button), /"clicked/",
(GtkSignalFunc) gtk_notebook_prev_page,
GTK_OBJECT (notebook));
gtk_table_attach_defaults(GTK_TABLE(table), button, 2,3,1,2);
gtk_widget_show(button);

button = gtk_button_new_with_label (/"tab position/");
gtk_signal_connect_object (GTK_OBJECT (button), /"clicked/",
(GtkSignalFunc) rotate_book, GTK_OBJECT(notebook));
gtk_table_attach_defaults(GTK_TABLE(table), button, 3,4,1,2);
gtk_widget_show(button);

button = gtk_button_new_with_label (/"tabs/border on/off/");
gtk_signal_connect_object (GTK_OBJECT (button), /"clicked/",
(GtkSignalFunc) tabsborder_book,
GTK_OBJECT (notebook));
gtk_table_attach_defaults(GTK_TABLE(table), button, 4,5,1,2);
gtk_widget_show(button);

button = gtk_button_new_with_label (/"remove page/");
gtk_signal_connect_object (GTK_OBJECT (button), /"clicked/",
(GtkSignalFunc) remove_book,
GTK_OBJECT(notebook));
gtk_table_attach_defaults(GTK_TABLE(table), button, 5,6,1,2);
gtk_widget_show(button);

gtk_widget_show(table);
gtk_widget_show(window);

gtk_main ();

return 0;
}

8.2 卷动视窗
卷动视窗是用来产生在视窗内可卷动的区域. 您可以在卷动视窗中插入任意种物件, 而不管视窗大小如何, 这些物件因为在卷动区域内, 因此都可以被用到.

您可以用以下函数来产生卷动视窗:

GtkWidget* gtk_scrolled_window_new (GtkAdjustment *hadjustment,
GtkAdjustment *vadjustment);

第一个参数是水平调整方向, 第二个是垂直调整方向. 它们一般被设为NULL.

void gtk_scrolled_window_set_policy (GtkScrolledWindow *scrolled_window,
GtkPolicyType hscrollbar_policy,
GtkPolicyType vscrollbar_policy);

第一个参数是想要改变的视窗. 第二个是设定水平卷动的方式, 第三个是垂直卷动的方式.

policy可以是GTK_POLICY_AUTOMATIC, 或GTK_POLICY_ALWAYS. GTK_POLICY_AUTOMATIC会自动决定是否使用scrollbars. GTK_POLICY_ALWAYS则scrollbars始终在那里.

这里是个将100个双态按钮包进一个卷动视窗的范例.

#include

void destroy(GtkWidget *widget, gpointer *data)
{
gtk_main_quit();
}

int main (int argc, char *argv[])
{
static GtkWidget *window;
GtkWidget *scrolled_window;
GtkWidget *table;
GtkWidget *button;
char buffer[32];
int i, j;

gtk_init (&argc, &argv);

/* Create a new dialog window for the scrolled window to be
* packed into. A dialog is just like a normal window except it has a
* vbox and a horizontal seperator packed into it. It/"s just a shortcut
* for creating dialogs */
window = gtk_dialog_new ();
gtk_signal_connect (GTK_OBJECT (window), /"destroy/",
(GtkSignalFunc) destroy, NULL);
gtk_window_set_title (GTK_WINDOW (window), /"dialog/");
gtk_container_border_width (GTK_CONTAINER (window), 0);

/* create a new scrolled window. */
scrolled_window = gtk_scrolled_window_new (NULL, NULL);

gtk_container_border_width (GTK_CONTAINER (scrolled_window), 10);

/* the policy is one of GTK_POLICY AUTOMATIC, or GTK_POLICY_ALWAYS.
* GTK_POLICY_AUTOMATIC will automatically decide whether you need
* scrollbars, wheras GTK_POLICY_ALWAYS will always leave the scrollbars
* there. The first one is the horizontal scrollbar, the second,
* the vertical. */
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
/* The dialog window is created with a vbox packed into it. */
gtk_box_pack_start (GTK_BOX (GTK_DIALOG(window)->vbox), scrolled_window,
TRUE, TRUE, 0);
gtk_widget_show (scrolled_window);

/* create a table of 10 by 10 squares. */
table = gtk_table_new (10, 10, FALSE);

/* set the spacing to 10 on x and 10 on y */
gtk_table_set_row_spacings (GTK_TABLE (table), 10);
gtk_table_set_col_spacings (GTK_TABLE (table), 10);

/* pack the table into the scrolled window */
gtk_container_add (GTK_CONTAINER (scrolled_window), table);
gtk_widget_show (table);

/* this simply creates a grid of toggle buttons on the table
* to demonstrate the scrolled window. */
for (i = 0; i < 10; i++)
for (j = 0; j < 10; j++) {
sprintf (buffer, /"button (%d,%d)//n/", i, j);
button = gtk_toggle_button_new_with_label (buffer);
gtk_table_attach_defaults (GTK_TABLE (table), button,
i, i+1, j, j+1);
gtk_widget_show (button);
}

/* Add a /"close/" button to the bottom of the dialog */
button = gtk_button_new_with_label (/"close/");
gtk_signal_connect_object (GTK_OBJECT (button), /"clicked/",
(GtkSignalFunc) gtk_widget_destroy,
GTK_OBJECT (window));

/* this makes it so the button is the default. */

GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, TRUE, TRUE, 0);

/* This grabs this button to be the default button. Simply hitting
* the /"Enter/" key will cause this button to activate. */
gtk_widget_grab_default (button);
gtk_widget_show (button);

gtk_widget_show (window);

gtk_main();

return(0);
}

玩弄一下这个视窗. 您会看到scrollbars如何反应. 您也会想用用gtk_widget_set_usize()来设定视窗内定的大小.

    9. EventBox视窗物件
这只在gtk+970916.tar.gz以後的版本才有.

有些gtk物件并没有相关联的视窗, 它们是由其parent所画出来的. 因此, 他们不能收到事件. 如果它们大小不对, 他们无法收到事件来修正. 如果您需要这样的功能, 那麽EventBox就是您想要的.

初 看之下, EventBox物件看来好像毫无用途. 它在萤幕上什麽事也不做, 也不画, 对事件也不反应. 不过, 它倒提供一项功能 - 他提供一个X window来服务其子物件. 这很重要, 因为GTK物件很多都跟X window不相关联. 不用X window省下记忆体并加快其速度, 但也有其缺点. 一个物件没有X window无法接收事件, 而且无法裁切其内容. 虽然它叫``EventBox/"/"强调其事件处理功能, 这个物件也可用来做裁切.

要产生一个EventBox物件, 使用:

GtkWidget* gtk_event_box_new (void);

一个子视窗物件可被加到EventBox之下:

gtk_container_add (GTK_CONTAINER(event_box), widget);

以下的简单示范, 使用了一个EventBox - 一个标题, 并且设定成滑鼠在标题上点一下程式就会离开.

#include <gtk/gtk.h>

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

gtk_init (&argc, &argv);

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

gtk_window_set_title (GTK_WINDOW (window), /"Event Box/");

gtk_signal_connect (GTK_OBJECT (window), /"destroy/",
GTK_SIGNAL_FUNC (gtk_exit), NULL);

gtk_container_border_width (GTK_CONTAINER (window), 10);

/* 产生一个EventBox并加到其上层的视窗 */

event_box = gtk_event_box_new ();
gtk_container_add (GTK_CONTAINER(window), event_box);
gtk_widget_show (event_box);

/* 产生一个长标题 */

label = gtk_label_new (/"Click here to quit, quit, quit, quit, quit/");
gtk_container_add (GTK_CONTAINER (event_box), label);
gtk_widget_show (label);

/* 把它裁短 */
gtk_widget_set_usize (label, 110, 20);

/* And bind an action to it */
gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK);
gtk_signal_connect (GTK_OBJECT(event_box), /"button_press_event/",
GTK_SIGNAL_FUNC (gtk_exit), NULL);

/* 还有一件事, 要X window来处理 ... */

gtk_widget_realize (event_box);
gdk_window_set_cursor (event_box->window, gdk_cursor_new (GDK_HAND1));

gtk_widget_show (window);

gtk_main ();

return 0;
}

  10. 其它物件

10.1 标签
标签在GTK中用得很多, 而且很简单. 标签不送信号, 因为它们跟X window没有关系. 如果您要接取信号, 或裁切, 可用EventBox物件.

产生新的标签可用:

GtkWidget* gtk_label_new (char *str);

唯一个参数是您想要显示的文字.

在产生标签後要改变其文字, 可用:

void gtk_label_set (GtkLabel *label,
char *str);

第一个参数是刚才所产生的标签(使用GTK_LABEL巨集来分派), 第二个是新的字串.

新字串的空间会自动被配置.

要取得目前的字串可用:

void gtk_label_get (GtkLabel *label,
char **str);

第一个参数是标签, 第二个是返回字串的位置.

10.2 Progress Bars
Progress bars是用来显示某个作业的操作状态. 他们很容易使用, 您会看到以下的程式. 我们先来产生一个Progress Bar.

GtkWidget *gtk_progress_bar_new (void);

这样就产生了, 够简单的了.

void gtk_progress_bar_update (GtkProgressBar *pbar, gfloat percentage);

第一个参数是您要操作的Progress Bar, 第二个是完成度, 其值为0-1.

Progress Bars一般与timeouts及其它函数一起使用, (see section on Timeouts, I/O and Idle Functions) 这是因为多工的考量. gtk_progress_bar_update会处理这方面的事务.

这里是使用Progress Bar的范例, 并用timeouts来更新. 同时也会展示如何重设Progress Bar.

#include

static int ptimer = 0;
int pstat = TRUE;

/* This function increments and updates the progress bar, it also resets
the progress bar if pstat is FALSE */
gint progress (gpointer data)
{
gfloat pvalue;

/* get the current value of the progress bar */
pvalue = GTK_PROGRESS_BAR (data)->percentage;

if ((pvalue >= 1.0) || (pstat == FALSE)) {
pvalue = 0.0;
pstat = TRUE;
}
pvalue += 0.01;

gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue);

return TRUE;
}

/* This function signals a reset of the progress bar */
void progress_r (void)
{
pstat = FALSE;
}

void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}

int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *button;
GtkWidget *label;
GtkWidget *table;
GtkWidget *pbar;

gtk_init (&argc, &argv);

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

gtk_signal_connect (GTK_OBJECT (window), /"destroy/",
GTK_SIGNAL_FUNC (destroy), NULL);

gtk_container_border_width (GTK_CONTAINER (window), 10);

table = gtk_table_new(3,2,TRUE);
gtk_container_add (GTK_CONTAINER (window), table);

label = gtk_label_new (/"Progress Bar Example/");
gtk_table_attach_defaults(GTK_TABLE(table), label, 0,2,0,1);
gtk_widget_show(label);

/* Create a new progress bar, pack it into the table, and show it */
pbar = gtk_progress_bar_new ();
gtk_table_attach_defaults(GTK_TABLE(table), pbar, 0,2,1,2);
gtk_widget_show (pbar);

/* Set the timeout to handle automatic updating of the progress bar */
ptimer = gtk_timeout_add (100, progress, pbar);

/* This button signals the progress bar to be reset */
button = gtk_button_new_with_label (/"Reset/");
gtk_signal_connect (GTK_OBJECT (button), /"clicked/",
GTK_SIGNAL_FUNC (progress_r), NULL);
gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,2,3);
gtk_widget_show(button

抱歉!评论已关闭.