GTK+编程入门(3)—响应GKT+的信号

GTK+编程入门(2)—响应GTK+的信号(2015-7-24)

分类:GTK+

  在这之前,先来看一个对上一个简单程序的改进程序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+编程入门(3)—响应GKT+的信号_第1张图片

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;

你可能感兴趣的:(GTK+)