Linux GUI编程笔记之GTK+篇(1)
----信号-回调函数和事件
GTK+(GIMP Toolkit)是一个在Linux平台上比较流行的GUI构建工具,著名的Gnome平台就是基于它的。虽然它是用C语言编写的,但是它却成功的应用了面向对象和回调函数这些技术。在它的主页www.gtk.org上有一个详细的教程下载(国内有网友也做了翻译),所以我在这里也不详细的介绍它的内容了。作为自己学习GTK+编程的笔记(我现在在看英文的GTK文档),我只是着重分析一些在GTK教程上比较难懂的,然后比较关键的知识点。有什么错误纰漏的地方,请各位网友指正。
首先,让我们来看一个简单的例子,然后引出我们今天的讨论的主题:
#include <gtk/gtk.h>
/* This is a callback function */
void hello( GtkWidget *widget,
gpointer data )
{
g_print ("Hello World\n");
}
int main( int argc,
char *argv[] )
{
GtkWidget *window;
GtkWidget *button;
/* init the program */
gtk_init (&argc, &argv);
/* make a new button */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (G_OBJECT (window), "destroy",
G_CALLBACK (gtk_main_quit), NULL);
gtk_container_set_border_width (GTK_CONTAINER (window), 10);
/* make a new button */
button = gtk_button_new_with_label ("Hello World");
g_signal_connect (G_OBJECT (button), "clicked",
G_CALLBACK (hello), NULL);
/* add the button to the window */
gtk_container_add (GTK_CONTAINER (window), button);
/* show the widgets */
gtk_widget_show (button);
gtk_widget_show (window);
gtk_main ();
return 0;
}
假设文件保存为: hello.c
然后编译: gcc -o hello hello.c `pkg-config --cflags --libs gtk+-2.0`
(关于这个奇怪的gcc编译选项,GTK+文档上有详细解释)
用: ./hello 运行就出现一个带有一个按钮的小窗口,如下图:
每个GTK+程序的框架都是差不多的。像我们这个程序,在main()函数中用gtk_init(&argc, &argv)初始化环境,然后定义一些控件让他们显示,最后用gtk_main()进入主循环,等待各种事件的发生(了解Windows程序设计的朋友会感觉这和Windows GUI编程模式差不多:)。这里最重要的地方就是把各种事件通过回调函数(callback function)和控件联系起来,使控件在某事件发生时(比如鼠标点击)产生某种响应。
什么是“回调函数”呢?在上面的程序中,void hello()函数就是一个回调函数。通过g_signal_connect()这个信号连接函数使之与button连接,在button被点击(clicked)时就触发hello()函数的运行(各种不同的GUI构建工具其基本原理都是这样的)。下面让我们来看看这个奇妙的信号连接函数是如何工作的:
这是第一种信号连接函数的原型声明:
gulong g_signal_connect( gpointer *object, //发出信号的控件
const gchar *name, //信号名称
GCallback func, //回调函数(对信号要采取的动作)
gpointer data ); //传给回调函数的数据
它对应的回调函数是:
void func( GtkWidget *widget, //发出信号的控件
gpointer callback_data ); //穿过来的数据
表示object这个控件在接收到name信号以后调用func函数。比如上面的例子:
g_signal_connect (G_OBJECT (button), "clicked",
G_CALLBACK (hello), NULL);
就是指button这个控件在接收鼠标点击这个动作时,就发出”clicked"这个事件,我们捕获这个事件后调用hello()这个回调函数。然后hello()函数里面第一个参数widget现在就是button,第二个参数data为NULL(从g_signal_connect()传过来的)。值得注意的是在GTK+中信号是用字符串形式表示的(比如这里的“clicked”)。
现在我们应该了解了GTK+程序的工作原理了,再复杂的Gnome程序其基本原理就是上面我们分析的信号捕捉=>函数回调 。还有一种信号连接函数是这样的:
gulong g_signal_connect_swapped( gpointer *object, //发出信号的控件
const gchar *name, //信号名称
GCallback func, //回调函数
gpointer *widget ); //传给回调函数的数据
它和g_signal_connect()作用相同,不同的只是它的回调函数只接收一个参数,它对应的回调函数是:
void callback_func( GtkObject *object );
这对于调用GTK+自带的一些只接收一个参数的函数(比如gtk_widget_destroy)来说是十分必要的,比如我们想让鼠标点击按钮时让窗口关闭,就可以加这样一个信号连接函数:
g_signal_connect_swapped (G_OBJECT (button), "clicked", //让鼠标发出"clicked"事件
G_CALLBACK (gtk_widget_destroy), //调用系统函数使窗口关闭
G_OBJECT (window)); //把window作为参数传给gtk_widget_destroy()
信号连接函数不仅可以和控件产生的信号进行连接,而且可以和特殊的X-windows”事件“进行连接。我们知道,Gnome/KDE之类的窗口管理程序自己可以产生一些事件(比如按Alt+F4会产生一个窗口关闭事件)。所以GTK也允许我们用g_signal_connect()使我们的程序对窗口管理器产生的事件作出正确的反应。下面是一些常见的X-windows事件:
button_press_event
button_release_event
scroll_event
delete_event
这些事件的意思一目了然,就像GTK各函数的意思一样(比如gtk_main_quit肯定是退出程序了:)
只是对事件作出反应的回调函数和前面的有点不同:
static gint callback_func( GtkWidget *widget, //接收事件的控件
GdkEvent *event, //事件名称
gpointer callback_data ); //传给回调函数的数据
这个函数的返回值为TRUE或FALSE,返回TRUE的时候表示GTK程序已经完成事件处理,窗口管理器不用再触发下面的事件,返回FALSE时继续调用后续函数。再GTK文档上有一个窗口管理器发出delete_event事件的例子,当callback函数返回TRUE时程序不再调用destroy事件,而返回FALSE时程序继续调用destroy事件使得程序退出。
好的,我们关于信号和事件处理部分已经讲玩了。但是我们只是一个劲地往程序里加事件,有没有方法使得我们能够去掉连在控件上的信号,事件呢?答案是肯定的。
注意g_signal_connect()函数的返回值gulong,还有许多比如gint, gchar等,这是GTK+对ANSI C数据类型的包装,使得GTK的移植性更好。这里gulong表示g_signal_connect()返回了一个信号ID,用来代表这个事件信号。我们可以利用这个ID来去掉控件上这个事件,这是去掉信号连接的函数原型:
void g_signal_handler_disconnect( gpointer object, //信号和哪个控件有一腿:)
gulong id ); //信号的ID
在GTK+的API里面还有一些事件,信号方面的函数,比如:
/* 暂停信号处理 */
void g_signal_handler_block( gpointer object,
gulong id );
/* 重新进行信号处理 */
void g_signal_handler_unblock( gpointer object,
gulong id );
如果你对这个有兴趣研究,可以参阅GTK的API文档,里面有简介明了的介绍。
OK! 终于完成了我们Linux GUI编程的第一课了:) 下一次让我们来学习一下如果在GTK窗口上摆放控件。
--------2004-7-28