GTK+編程入門(2)—響應GTK+的信號(2015-7-24)
分類:GTK+
在這之前,先來看一個對上一個簡單程序的改進程序gtk.c。
#include <gtk/gtk.h>
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+中的事件和信號
在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_swapped函數
和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手動產生一個信號
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+中常用的具體事件類型
GdkEventButton類型
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類型
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事件
GdkEventConfigure事件在一個窗口的尺寸或位置改變時發生(引發“configure_event”信號),其類型定義如下:
typedef struct{
GdkEventType type;
GdkWindow *window;
gint8 send_event;
gint x, y; /* 相對於父窗口的新座標 */
gint width; /* 新的尺寸 */
gint height;
}GdkEventConfigure;
GdkEventExpose事件
GdkEventExpose事件在一個窗口變爲可見並需要重繪時發生(引發“expose_event“信號),它的類型定義如下:
typedef struct{
GdkEventType type;
GdkWindow *window;
gint8 send_event;
GdkRectangle area; /* 需要重繪的區域外圍矩形 */
GdkRegion *region; /* 需要重繪的區域,裁剪區 */
gint count; /* 後續的GDK_EXPOSE事件的個數 */
}GdkEventExpose;
GdkEventFocus事件
GdkEventFocus事件與焦點變更(引發“focus_in_event”和“focus_out_event”信號),它的類型定義如下:
typedef struct{
GdkEventType type;
GdkWindow *window;
gint8 send_event;
gint16 in; /* 獲得焦點時爲TRUE,失去焦點時爲FALSE */
}GdkEventFocus;