在这之前,先来看一个对上一个简单程序的改进程序gtk.c。
#include
static void hello(GtkWidget *widget, gpointer data){
/* 输出信息 */
g_printf("Hello World!\n");
}
static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data){
g_printf("delete event occurred.\n");
/* 如果返回FALSE,GTK+会发出一个“destroy”信号 */
return TRUE;
}
static void destroy(GtkWidget *widget, gpointer data){
/* 输出构件的名字 */
g_printf("%s :exit!\n", gtk_widget_get_name(widget));
/* 退出主循环 */
gtk_main_quit();
}
int main(int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *button;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
/* 注册回调函数 */
g_signal_connect(window, "delete-event", G_CALLBACK(delete_event), NULL);
g_signal_connect(window, "destroy", G_CALLBACK(destroy), NULL);
/* 设置窗口边距 */
gtk_container_set_border_width(GTK_CONTAINER(window), 10);
/* 设置构件名称 */
gtk_widget_set_name(GTK_WIDGET(window), "MainWindow");
/* 创建一个按钮 */
button = gtk_button_new_with_label("Hello World!");
/* 注册单击按钮事件的回调函数 */
g_signal_connect(button, "clicked", G_CALLBACK(hello), NULL);
g_signal_connect_swapped(button, "clicked", G_CALLBACK(gtk_widget_destroy), window);
/* 把构件添加到窗口内 */
gtk_container_add(GTK_CONTAINER(window), button);
/* 显示按钮 */
gtk_widget_show(button);
/* 显示窗口 */
gtk_widget_show(window);
/* 进入GTK+主循环 */
gtk_main();
return 0;
}
它的编译和运行:
biantiao@lazybone1994-ThinkPad-E430:~/sh/GTK+$ gcc -o ex_gtk ex_gtk.c `pkg-config --cflags --libs gtk+-3.0`
biantiao@lazybone1994-ThinkPad-E430:~/sh/GTK+$ ./ex_gtk
Hello World!
MainWindow :exit!
biantiao@lazybone1994-ThinkPad-E430:~/sh/GTK+$
在GTK+中,一个事件(event)就是一个从 X Window传来的信息。事件是通过信号(signal)来传递的。当一个事件(比如单击鼠标)发生时,事件所作用的控件(比如被单击的按钮)就会发出一个信号(比如“clicked”)来通知应用程序。如果应用程序已经将该信号与另一个回调函数连接起来,GTK+就会自动调用该回调函数执行相关的操作,从而完成一次由事件所引发的行为。与信号相连接的回调函数称为信号处理函数。当为事件所发出的信号连接了信号处理函数时,称响应了某个事件。
GTK+中有通用于所有构件的公共信号(比如:“destroy”),也有专属于某类构件的专有信号(比如:toggle buttong具有的toggled信号)。
如上所述,在GTK+中要让应用程序响应某个事件,必须事先给该事件发出的信号连接一个信号处理函数。这需要用到g_signal_connect函数。其原型如下:
gulong g_signal_connect(gpointer *object, const gchar *name, GCallback func, gpointer func_data);
函数各参数和返回值含义如下:
1. object:发出信号的构件
2. name:信号名称
3. func:事件发生时将调用的信号处理函数
4. func_data:事件发生时传递给信号处理函数的用户数据
5. 返回值:成功返回信号处理函数的ID(非0值);失败时返回0
把例子当中的连接信号函数的代码揪出来看看。
/* 注册回调函数 */
g_signal_connect(window, "delete-event", G_CALLBACK(delete_event), NULL);
g_signal_connect(window, "destroy", G_CALLBACK(destroy), NULL);
在这里发出信号的构件是window,信号的名称分别为”delete-event”和”destroy”,传递给信号处理函数的用户数据为NULL。
G_CALLBACK()是什么东东?
信号处理函数以GCallback类型声明。实际上,在GTK+中,不同的信号所对应的信号处理函数的类型可能是不同的。作为一种设计策略,GTK+中使用GCallback类型表示通用的回调函数(信号处理函数)类型。其定义为void (*GCallback)(void)
同时,GTK+定义了一个通用回调函数类型转换宏G_CALLBACK(),在实际调用g_signal_connect函数时,应将一个具有以下形式的具体的回调函数经G_CALLBACK()宏进行强制类型转换后传递给func参数。
void callback_func(GtkWidget *widget,
... /*其它参数*/
gpointer callback_data);
虽然不同信号所对应的信号处理函数的类型可能不同,但是其第一个参数和最后一个参数是固定的。第一个参数widget为发出信号的构件;最后一个参数callback_data是用户数据,当信号处理函数被调用时,它将得到g_signal_connect函数连接信号时提供的func_data参数。
再把定义的信号处理函数揪出来看看。
static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data){
g_printf("delete event occurred.\n");
/* 如果返回FALSE,GTK+会发出一个“destroy”信号 */
return TRUE;
}
在GTK+中,事件具有一个“传播”的过程。一个事件可能在一个构件上先后引发不同的信号。同时,对每个事件,信号先由它直接作用的构件引发,然后是它的直接父构件,然后是父构件的父构件,依次向上递归,这个过程称为事件“冒泡”。因此,一个事件可能在多个构件上面分别引用多个信号。
另外,GTK+允许为一个构件的一个信号连接多个信号处理函数,当相应信号被传播时,这些信号处理函数将按连接的顺序依次被调用。
GTK+事件的信号处理函数必须返回一个gint型整数值。最后一个运行的信号处理函数决定了信号引发的返回值。如果返回的是TRUE,GTK+主循环会停止当前事件的传播过程,否则将继续事件的传播。
例如,对于实例程序,连接delete-event信号的信号处理函数delete_event,它最后返回TRUE终止了事件的传播。如果返回FALSE,GTK+会发出一个“destroy”信号,该信号会使主窗口关闭。
和g_signal_connect函数一样,它也可用于连接信号和信号处理函数,其原型:
gulong g_signal_connect_swapped(gpointer *object,
const gchar *name,
GCallback func,
gpointer *callback_data;
);
该函数和g_signal_connect函数的区别在于回调函数,g_signal_connect_swapped函数要求连接的信号处理函数具有以下形式:
void callback_func(gpointer callback_data,
... /* 其它参数 */
GtkWidget *widget);
与g_signal_connect函数连接信号处理函数相比较,两者的第一个参数和最后一个参数的位置干好相反,在GTK+程序中一般不用g_signal_connect_swapped函数连接信号和信号处理函数,该函数的仅用于连接“只带一个构件或对象作为参数”的GTK+内置应用接口函数作为信号处理函数的时候。例如,实例中的
g_signal_connect_swapped(button, "clicked", G_CALLBACK(gtk_widget_destroy), window);
在这个调用中,第四个参数,即传递给信号处理函数的“用户数据”是window。而信号处理函数是gtk_widget_destroy,它是GTK+内置的应用接口函数,作用是销毁一个构件。其原型为:
void gtk_widget_destroy(GtkWidget *widget);
作用是销毁一个构件。
g_signal_emit_by_name函数的作用是手动产生一个信号以区别GTK+自动产生的信号。该函数的原型如下:
void g_signal_emit_by_name(gpointer instance,
const gchar *detailed_signal, ...);
这个函数的instance参数为信号所作用的目标,一般是一个构件。detailed_signal是表示信号的具体字符串,如“destroy”。省略号部分表示两个可选的参数,前一个参数为信号的“用户数据”,后一个参数为存放信号处理函数的返回值的地址。
在GTK+中,使用共用体GdkEvent类型来表示一个事件,该类型定义如下:
typedef union _GdkEvent
{
GdkEventType type; /* 事件类型 */
GdkEventAny any; /* 通用事件头部 */
GdkEventExpose expose; /* 以下为具体的事件类型 */
GdkEventVisibility visibility;
GdkEventMotion motion;
GdkEventButton button;
GdkEventScroll scroll;
GdkEventKey key;
GdkEventCrossing crossing;
GdkEventFocus focus_change;
GdkEventConfigure configure;
GdkEventProperty property;
GdkEventSelection selection;
GdkEventOwnerChange owner_change;
GdkEventProximity proximity;
GdkEventDND dnd;
GdkEventWindowState window_state;
GdkEventSetting setting;
GdkEventGrabBroken grab_broken;
};
其中type成员是一个枚举值,用于指明事件类型。事件类型GdkEventType列出了GTK+中所有的事件类型,其定义如下:
typedef enum{
GDK_NOTHING = -1,
GDK_DELETE = 0,
GDK_DESTROY = 1,
GDK_EXPOSE = 2,
GDK_MOTION_NOTIFY = 3,
GDK_BUTTON_PRESS = 4,
GDK_2BUTTON_PRESS = 5,
GDK_3BUTTON_PRESS = 6,
GDK_BUTTON_RELEASE = 7,
GDK_KEY_PRESS = 8,
GDK_KEY_RELEASE = 9,
GDK_ENTER_NOTIFY = 10,
GDK_LEAVE_NOTIFY = 11,
GDK_FOCUS_CHANGE = 12,
GDK_CONFIGURE = 13,
GDK_MAP = 14,
GDK_UNMAP = 15,
GDK_PROPERTY_NOTIFY = 16,
GDK_SELECTION_CLEAR = 17,
GDK_SELECTION_REQUEST = 18,
GDK_SELECTION_NOTIFY = 19,
GDK_PROXIMITY_IN = 20,
GDK_PROXIMITY_OUT = 21,
GDK_DRAG_ENTER = 22,
GDK_DRAG_LEAVE = 23,
GDK_DRAG_MOTION = 24,
GDK_DRAG_STATUS = 25,
GDK_DROP_START = 26,
GDK_DROP_FINISHED = 27,
GDK_CLIENT_EVENT = 28,
GDK_VISIBILITY_NOTIFY = 29,
GDK_SCROLL = 31,
GDK_WINDOW_STATE = 32,
GDK_SETTING = 33,
GDK_OWNER_CHANGE = 34,
GDK_GRAB_BROKEN = 35,
GDK_DAMAGE = 36,
GDK_EVENT_LAST /* 用作哨兵 */
}GdkEventType;
GdkEvent的any成员GdkEventAny类型定义如下:
struct GdkEventAny{
GdkEventType type; //事件类型
GdkWindow * window; //事件的目标窗口
gint8 send_event; //手动引发(用XSendEvent)或由GDK引发
}
GdkEvent类型的成员中,除了type和any成员外,其他成员都表示某一个具体的事件类型。GTK+中每一个具体的类型均以GdkEventAny结构体的三个成员开头,因此,GdkEventAny是一个通用的事件的头部。
GTK+类型对应与鼠标操作相关的事件,如鼠标按键(引发“clicked”,“button_press_event”信号),鼠标移动(引发“motion_notify_event”信号)其类型定义如下:
typedef struct{
GdkEventType type; //通用事件的三个头部
GdkWindow *window;
gint8 send_event;
guint32 time; //事件发生事件(毫秒计)
gdouble x; //相对事件窗口的坐标,可能为负
gdouble y;
gdouble *axes; //设备坐标,对于鼠标为NULL
guint state; //修改键屏蔽值,指示哪个组合键或鼠标按键是按下的
guint button; //被按下或释放的鼠标键:从1到5编号
GdkDevice *device; //硬件设备(如图形输入板或鼠标)
gdouble x_root; //相对于根窗口的绝对坐标
gdouble y_root;
}GdkEventButton;
GdkEventKey类型对应与键盘操作相关的事件,如键盘按键按下(引发“key-press-event”信号)和键盘按键释放(引发“key-release-event”信号),其类型定义如下:
typedef struct {
GdkEventType type;
GdkWindow *window;
gint8 send_event;
gint32 time;
guint state; /* 修改键屏蔽值 */
guint keyvalue; /* 键值 */
gint length; /* string成员的长度 */
gchar *string; /* 按键的字符串表示(已弃用) */
guint16 hardware_keycode; /* 按键的原始编码 */
guint8 group; /* 键盘组 */
guint is_modifier : 1;
}GdkEventKey;
GTK+中另外两个常用的事件类型和构件的显示有关,它们是GdkEventConfigure和GdkEventExpose。还有一个较常见的为焦点变更事件相关的GdkEventFocus事件。
GdkEventConfigure事件在一个窗口的尺寸或位置改变时发生(引发“configure_event”信号),其类型定义如下:
typedef struct{
GdkEventType type;
GdkWindow *window;
gint8 send_event;
gint x, y; /* 相对于父窗口的新坐标 */
gint width; /* 新的尺寸 */
gint height;
}GdkEventConfigure;
GdkEventExpose事件在一个窗口变为可见并需要重绘时发生(引发“expose_event“信号),它的类型定义如下:
typedef struct{
GdkEventType type;
GdkWindow *window;
gint8 send_event;
GdkRectangle area; /* 需要重绘的区域外围矩形 */
GdkRegion *region; /* 需要重绘的区域,裁剪区 */
gint count; /* 后续的GDK_EXPOSE事件的个数 */
}GdkEventExpose;
GdkEventFocus事件与焦点变更(引发“focus_in_event”和“focus_out_event”信号),它的类型定义如下:
typedef struct{
GdkEventType type;
GdkWindow *window;
gint8 send_event;
gint16 in; /* 获得焦点时为TRUE,失去焦点时为FALSE */
}GdkEventFocus;