通过学习上面两节的信号和事件理论知识,下面让我们来一块详细地分析最初编辑的 Hello World 示例程序吧。

void hello( … )

这是按钮被点击时要调用的回调函数。在这个示例中我们忽略了参数 widget 和 data,但是使用这些参数也不难。下一个示例会使用 data 参数来告诉我们哪个按钮被按下了。

void hello( GtkWidget *widget,
            gpointer   data )
{
    g_print ("Hello World\n");
}

接下来的一个回调函数有点特殊。

gint delete_event( … )

“delete_event”事件,是在窗口管理器发送这个事件给应用程序时发生。

我们在这里可以选择对这些事件做什么。可以忽略它们,可以做一点响应 或是简单地退出程序。

该回调函数返回的gint值让 GTK 知道该如何去做。如果返回 TRUE,让它知道我们不想让 “destroy” 信号被发出,保持程序继续运行。返回 FALSE,我们让 “destroy” 信号发出,这接着会调用 “destroy” 信号处理函数

gint delete_event( GtkWidget *widget,
                   GdkEvent  *event,
           gpointer   data )
{
    g_print ("delete event occurred\n");

    return TRUE;
} 

void destroy( … )

这里是另一个回调函数,它通过调用 gtk_main_quit() 来退出程序。这个 gtk_main_quit() 函数告诉 GTK 当控制权返回给它时就从 gtk_main 退出。

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

int main( … )

我假设你知道 main() 函数…是的,像其它程序一样,所有的 GTK 程序有一个 main() 函数

int main( int   argc,
          char *argv[] )
{ 

接下来声明两个指向 GtkWidget 类型的结构的指针。它们被用于创建一个窗口和一个按钮。

    GtkWidget *window;
    GtkWidget *button;

这里又是 gtk_init()。跟前面一样,这个初始化工具包,分析命令行里的参数。它从参数列表中删除任何可以识别的参数,并且修改 argc 和 argv,使这些被删除的参数好像从来就不存在一样,而允许你的程序分析剩余的参数。

SofTool.CN Notes:
这句话的意思应该是:gtk_init()可以过滤掉gtk自己能用的参数,剩下的参数才让我们来用 ^_^

    gtk_init (&argc, &argv);

创建一个新窗口。这个很直观。它为 GtkWidget *window 结构分配了内存,这样 window 现在指向了一个有效的结构。它建立了一个新窗口,但是这个窗口直到在程序后面部分我们调用 gtk_widget_show(window) 后才会显示出来。

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

这有两个连接一个信号处理函数到一个对象 (本例中,就是 window ) 的示例。这里,”delete_event” 和 “destroy” 信号被捕获。当我们用窗口管理器去关闭窗口或调用函数 gtk_widget_destroy() 并将 window 构件作为对象传给它来销毁时,”delete_event” 信号发出。当我们在 “delete_event” 信号处理函数中返回 FALSE 值时,”destroy” 信号发出。G_OBJECT 和 G_CALLBACK 是宏,为我们执行类型转换和检测,同时也增加了代码的可读性。

    g_signal_connect (G_OBJECT (window), "delete_event",
                      G_CALLBACK (delete_event), NULL);
    g_signal_connect (G_OBJECT (window), "destroy",
                      G_CALLBACK (destroy), NULL);

接下来这个函数用于设置容器对象的属性。设置窗口边框宽度为10个象素。在设置构件属性这一章还有其它类似函数。
再次,GTK_CONTAINER也是一个执行类型转换的宏

    gtk_container_set_border_width (GTK_CONTAINER (window), 10);

这个函数调用创建一个新按钮。在内存中分配空间给一个新的 GtkWidget 结构,初始化它,并使 button 指针指向它。它显示后上面会有个 “Hello World” 标签。

    button = gtk_button_new_with_label ("Hello World");

在这,我们让这个按钮做一些有用的事。我们给按钮设置信号处理函数,因此当按钮发出 “clicked” 信号时,hello() 函数被调用。我们忽略了 data 参数,简单地传送 NULL 给 hello() 回调函数。显而易见,当我们用鼠标点击按钮时,信号 “clicked” 被发出。

SofTool.CN Notes:
完成关联: button按钮 - 单击事件 - 回调函数

    g_signal_connect (G_OBJECT (button), "clicked",
                      G_CALLBACK (hello), NULL);

我们也要使用这个按钮退出程序。这将演示 “destroy” 信号怎样由窗口管理器引发,或由我们的程序引发。当我们按下按钮时,和上面一样,它首先调用 hello() 回调函数,然后是这个函数,这依赖于它们被设置连接的顺序。你可以拥有许多回调函数,所有的回调按你设置连接的顺序依次执行。因为 gtk_widget_destroy() 函数只接受 GtkWidget *widget 作为唯一的参数,我们这里用 g_signal_connect_swapped() 函数代替正统的 g_signal_connect()。

    g_signal_connect_swapped (G_OBJECT (button), "clicked",
                              G_CALLBACK (gtk_widget_destroy),
                              G_OBJECT (window));

这是一个组装调用,在组装构件这一章将作深入讲解。不过它相当容易理解。它简单地告诉 GTK 要把按钮放在窗口里,也就是它显示的地方。注意一个 GTK 容器只能包含一个构件。还有其它的构件,在后面介绍,设计为用来以各种方法布局多个构件。

    gtk_container_add (GTK_CONTAINER (window), button);

一切准备就绪。所有信号处理函数连接好了,按钮也放进了窗口,我们让 GTK 在屏幕上“显示”这些构件。窗口构件最后显示,这样整个窗口会一下弹出,而不是先见到窗口弹出后再见到按钮。虽然这个简单的示例中,你不会注意到。

    gtk_widget_show (button);

    gtk_widget_show (window);

接着,当然,我们调用 gtk_main() 函数来等待来自 X 服务器的事件,当这些事件到来时,调用构件发出信号。

   gtk_main ();

最后返回,调用函数 gtk_quit() 后控制权返回到这里。

    return 0;

执行过程分析

现在,当我们用鼠标点击一个 GTK 按钮,构件发出一个 “clicked” 信号。为了让我们利用这个信息,程序设置了一个信号处理器来捕获那个信号,它按我们的选择依次调用函数。在我们的示例中,当按下按钮时,以 NULL 作为参数调用函数 hello(),然后调用该信号的下一个处理函数,该函数调用 gtk_widget_destroy() 函数,把窗口构件作为参数传递给它,销毁窗口构件。这导致窗口发出 “destroy” 信号,它被捕获,并且调用我们的 destroy() 回调函数,简单地退出 GTK。

如果用窗口管理器去关闭窗口,它会引发 “delete_event”。这会调用我们的 “delete_event” 处理函数。如果我们在函数中返回 TRUE,窗口还是留在那里,什么事也不发生。返回 FALSE,会使 GTK 发出 “destroy” 信号,它当然会调用 “destroy” 回调,退出 GTK。