16.3 事件、信号和回调函数
所有的GUI库都有一个共同点:必须存在某种机制来响应用户动作并执行相应代码。一个命令行程序的奢侈做法是暂停执行,等待用户输入,然后釆用switch语句等方法让程序根据输入进行分支执行。但这种方法并不适用于GUI应用程序,因为应用程序必须不断响应用户输入,例如,它需要不断更新窗口区域。
现代窗口系统用事件和事件监听器系统来解决这个问题。其思想是,每次用户输入(通常是通过鼠标或是键盘)都触发一个事件。例如,一次击键会触发一个键盘事件。因此,程序员需要编写监听这些事件的代码,以及当事件被触发时要执行的代码。
正如你前面所看到的,X视窗系统会发出这些事件,但是它们对GTK+程序员并没有太大帮助,因为它们都是非常底层的。当鼠标按钮被点击时,X发出一个包含鼠标指针坐标的事件,而你真正需要知道的是用户何时激活了一个构件。
因此,GTK+有它自己的事件和事件监听器系统,它们被称为信号和回调函数。它们非常容易使用,因为你可以使用C语言的一个非常有用的特征——函数指针来设置信号处理器。
先看一些定义:GTK+信号是当某件事(如用户输入)发生时,由GtkObject对象发出的。一个与信号相连接,并且一旦当信号被发出,它就会被调用的函数被称为回调函数。
注意,GTK+信号与第11章中讨论的UNIX信号无关。
作为一个GTK+程序员,你需要关心的就是,如何编写和连接回调函数,因为发出信号的代码是内置在特定构件中的。
回调函数的原型通常如下所示:
void a_callback_function(GtkWidget *widget, gpointer user_data);
其中传递了两个参数:第一个参数是指向发出信号的构件的指针,第二个参数是当你连接回调函数时自己选择的一个任意指针。你可将该指针用于任何目的。
连接回调函数同样简单。你只需调用g_signal_connect,并传递如下几个参数:构件、信号名(作为字符串)、回调函数指针和你的任意指针:
有一点值得指出:连接回调函数没有任何限制。你可以将多个信号连接到同一个回调函数,也可以将多个回调函数连接到同一个信号。有关每个构件发出的信号的详细资料请参阅GTK+API文档。
在GTK+2之前的版本中,用于连接回调函数的函数是gtk_signal_connect,该函数已被g_signal_connect取代,你在新的代码中不应再使用该函数。
在下一个例子中,我们将使用函数g_signal_connect。
实 验 回调函数
在gtk2.c中,你将在窗口中添加一个按钮,并将这个按钮的“clicked”信号与回调函数连接,从而显示一条短信息:
输入这个程序的源代码并将它保存为gtk2.c。使用与前面gtk1.c示例类似的命令编译和链接这个程序。当运行这个程序时,你将看到一个带按钮的窗口,每次当你点击这个按钮时,它都会输出一条短消息(见图16-5)。
图 16-5
实验解析
gtk2.c的代码中引入了两个新特性:GtkButton和回调函数。GtkButton是一个简单的按钮构件,它可以包含文本(在本例中,它包含的文本是“Hello World”),并在鼠标点击这个按钮时发出被称为“clicked”的信号。
回调函数button_clicked通过g_signal_connect函数连接到按钮构件的“clicked”信号:
注意,按钮的名称Button 1作为用户数据传递给回调函数。
代码的其他部分处理按钮构件,它的创建方法与窗口类似,调用gtk_button_new_with_label函数,然后用gtk_widget_show使其可见。
通过调用gtk_container_add函数将按钮放置到窗口上。这个简单的函数将一个GtkWidget放到一个GtkContainer中,并以容器和构件作为参数:
正如你之前看到的,GtkWindow是GtkContainer的一个子类,因此你可以通过GKT_CONTAINER宏将窗口对象转换为GtkContainer类型:
通过gtk_container_add向一个容器里放置一个构件是很方便的,但更多的情况是,你需要在一个窗口的不同位置放置好几个构件以创建一个像样的外观。GTK+有专用于此目的的构件——盒(box)或者容器(container)。