最近在给公司重写一版用 Qt3 写的软件,界面比较老,功能也年久失修,商量了一下决定用 GTK 重写界面和功能。之前接触的都是 Qt,对 GTK 了解不多。个人觉得跟 Qt 的信号-槽机制相比,GTK 的的信号回调机制稍显不同。然后这篇文章就是自己理解的 GTK 信号回调机制。
事件和信号
GTK 是一个基于事件驱动的框架,就是说 GTK 程序会一直循环在 gtk_main
函数中,直至一个事件发生,然后跳转到对应的事件处理函数中,执行完毕后再次回到 gtk_main
的循环。这听起来很符合逻辑,但是 GTK 中除了事件外,还有一个概念是信号,特别是当你写几个 GTK 程序后就会发现你处理的几乎都是信号。
典型的例如。
1 | g_signal_connect (G_OBJECT (mainWindow), "delete_event", G_CALLBACK (gtk_main_quit), NULL); |
那么 "delete_event"
到底是事件还是信号——它被用于 g_signal_connect
中,但是名字带有 event
字样。
在 GTK 中,事件是 X11 中发生的,GTK 通过 GDK 将 X11 中的 XEvent
转化为 GdkEvent
,其类型 GdkEventType
定义在头文件 gdk/gdkevents.h
中。
GDK 是 Xlib 的一个封装。
而信号与事件不同,是 GTK 本身的概念。在 GTK 中,一个事件发生之后,会通过函数 gtk_widget_event
将事件转化为信号,并通过函数 g_signal_emit
将信号发射出去,如果有回调和该信号绑定,那么这个回调有可能被执行。
举例来说,当 GtkButton
上发生了鼠标点击的动作时,默认地事件和信号的顺序如下。
GDK_BUTTON_PRESS
事件产生 -> 调用 GDK 中针对该事件的回调"button_press_event"
信号发射 -> 调用 GTK 中针对该信号的回调"clicked"
信号发射 -> 调用 GTK 中针对该信号的回调
那么 "delete_event"
到底是事件还是信号?它是一个信号,但是只有在事件 GDK_DELETE
发生后才会被发射出去,所以它也代表一个事件。
这也是 GTK 一个稍微有点混乱的地方——事件的发生是通过信号的发射反应的。所以你如果想在 GTK 中处理事件,你需要处理信号。
回调机制
回调的绑定
和 Qt 的信号-槽机制不同,GTK 中采用回调机制来处理信号。GTK 中为信号绑定回调的方式都通过同一个函数 g_signal_connect_data
完成,其原形定义在头文件 gobject/gsignal.h
。
1 | gulong g_signal_connect_data (gpointer instance, const gchar *detailed_signal, |
因为信号处理在 Glib 而非 GTK 中,所以函数名以
g_
而非gtk_
开头。
但是更加常用的是在其上封装的三个宏,与 g_signal_connect_data
定义在同一个头文件中。
1 |
其中。
g_signal_connect
: 为信号绑定一个回调函数,该回调将先于默认回调执行。g_signal_connect_after
: 和g_singal_connect
类似,但是该回调将在默认回调之后执行。g_signal_connect_swapped
:回调先于默认回调执行,但是回调的参数位置应该和前两个绑定函数的回调参数位置交换。
g_signal_connect_swapped
中 swapped
的效果如下。
1 | void handler (GtkWidget *widget, gpointer data); |
个人觉得
g_signal_connect_swapped
最好少用,它只会把水搅浑。
回调的形式
在 Gtk 中,回调的形式有两种,在反应事件的信号回调中,handler 需要额外增加一个 GdkEvent *
参数,用来传入发生的事件。
这两种回调之间的区别如下(假设使用 g_signal_connect
绑定)。
1 | void buttonClickedHandler (GtkWidget *button, gpointer data); |
回调的返回值
除了增加了 GdkEvent *
作为参数外,处理事件的回调函数多了一个 gboolean
类型的返回值,这个返回值用于控制该事件处理过程是否继续。
返回值的情况如下。
返回值 | 含义 |
---|---|
TRUE |
该事件已经处理完毕,不再继续调用其他和该事件绑定的回调 |
FALSE |
需要继续执行其他与该事件绑定的回调函数 |
回调的调用顺序
因为 GTK 先捕获事件再转化为信号,所以直接反应事件的信号在其他信号之前被发射,所以同一个 GtkWidget
上处理事件的回调总在其他信号回调之前被执行。
所以“回调的形式”一节的代码片中,假设 buttonClickedHandler
和 keyPressEventHandler
分别被绑定到一个 GtkButton
的 "event"
和 "clicked"
信号上,如果 keyPressEventHandler
返回 TRUE
,那么buttonClickedHandler
将不会被调用。