对于glib,我们经常用到信号通知机制来触发事件,在GObject中,可以自己定义signal信号,用于事件触发中。
一般在class_init时,由g_signal_new配合一些参数生成一个新信号句柄,然后使用g_signal_connect连接对象和处理方式(回调函数),最后由g_signal_emit发出信号触发。
一,其中最关键的是就g_signal_new,在这个过程中,我们应该先了解下相关的参数,其一般模型如下:
guint
g_signal_new (const gchar *signal_name,
GType itype,
GSignalFlags signal_flags,
guint class_offset,
GSignalAccumulator accumulator,
gpointer accu_data,
GSignalCMarshaller c_marshaller,
GType return_type,
guint n_params,
...)
通过上面的函数模型可以看出,其参数个数是可变的。而这些参数有些是必须的,有些可设为NULL,下面可以结合一个基于clutter的实例来解释一下各个参数的含义(SECTION 1和2是从clutter源代码中提取出来的定义示例):
1, const gchar *signal_name: 该参数是定义信号的名字,它由分隔符以及ASCII码中的字母和数字构成,且第一个字符必须是字母,实例中定义的名字为"myself-signal"。(分隔符可以是"-"或"_"——事实上,系统会先调用g_strdelimit把"_"转化为"-"再存储signal_name。因此,在调用g_singal_emit_by_name时,detailed_signal参数中的分隔符必须是"-");
2, GType itype:该参数是signal所依附的类的在GType类型系统中注册时得到的ID,也就是*_get_type()函数的返回值。而在clutter工程中,class_init时可使用G_OBJECT_CLASS_TYPE(klass)来获得,实例中使用CLUTTER_TYPE_ACTOR获取。(clutter _actor应该才是stage和texture、text的基类);
3, GSignalFlags signal_flags:该参数是信号的属性标记,共有七种,其中有提到"per-object handler",将在下面class_offset中介绍:
· G_SIGNAL_RUN_FIRST:调用回调函数时,"per-object handler"对应的回调函数将第一个调用;
· G_SIGNAL_RUN_LAST:调用回调函数时,"per-object handler"对应的回调函数将在用户用g_signal_connect连接的回调函数之后调用,并在用户用g_signal_connect _after连接的回调函数之前调用;
· G_SIGNAL_RUN_CLEANUP:调用回调函数时,"per-object handler"对应的回调函数将最后一个调用;
· G_SIGNAL_NO_RECURSE:信号发射时,如果信号的上次发射还没有结束,那么本次信号发射将不再进行,而只是使上次的信号发射重新开始。[wyj1]
· G_SIGNAL_DETAILED:信号名字可以使用"signal_name::detailed"的形式。
· G_SIGNAL_ACTION:程序员可以在代码中自由地调用g_signal_emit族的函数来发射信号,而不需要把g_signal_emit族的函数放在一段代码中再来调用。
· G_SIGNAL_NO_HOOKS:信号发射过程中不支持钩子函数。
4, guint class_offset:该参数是itype对应的类的class结构中的一个函数指针相对于class结构的实例的首地址的偏移量。该函数指针所对应的函数常被称为"per-object handler","default (signal) handler"或"object methond handler",并将在信号发出后被调用(如调用g_signal_emit_by_name)。常配合宏G_STRUCT_OFFSET使用(该宏能够返回结构体变量的成员相对于该结构体的变量的首地址的偏移量)。如SECTION1中,G_STRUCT_OFFSET (ClutterActorClass, paint),就是指在clutterActorClass类中,"per-object handler”为 paint的函数的偏移地址。而如果将该参数设为0,则表示该类没有"per-object handler"。实例中由于在类中没有定义相应的"per-object handler”,故设为0;
5, GSignalAccumulator accumulator:该参数是一个函数指针,其对应的函数将在该信号的每个handler执行完以后执行。其函数模型可参见SECTION 3。该函数的返回类型为gboolean。如果其返回值为FASLE,则signal发射过程就会被中止(即不再调用后面的hander),否则会继续下去。事实上,"delete-event"等带有event后缀的signal就是利用了这一点——这些信号的某个回调函数如果返回了TRUE,则以后的回调函数就不会被调用。我的理解是,此函数的功能就是决定信号还要不要继续往下走?(注意,如果该signal有accumulator,则回调函数类型(由c_marshaller反映)必须有返回值,否则该signal不能有accumulator,也即在调用g_signal_new时以NULL作为该形参的实参)
6, gpointer accu_data:该参数将作为用户自定义参数传入accumulator所指向的函数中。
7, GSignalCMarshaller c_marshaller:该参数是一个GSignalCMarshall类型的函数指针,其值反映了回调函数的返回值类型和额外参数类型(所谓“额外参数”,即指除回调函数中instance和user_data以外的参数)。
l 例如,g_closure_marshal_VOID_VOID说明该signal的回调函数为以下的callback类型:typedef void (*callback) (gpointer instance, gpointer user_data);
l g_closure_marshal_VOID_POINTER----------typedef void (*callback) (gpointer instance, gpointer arg1, gpointer user_data);
l g_cclosure_marshal_VOID__CHAR ()---------void (*callback) (gpointer instance, gchar arg1, gpointer user_data);
l g_cclosure_marshal_VOID__INT ()-----------void (*callback) (gpointer instance, gint arg1, gpointer user_data);
l 以前列举了部分,可参考http://developer.gnome.org/gobject/unstable/gobject-Closures.html。如果默认提供的GClosureMarshall中没有你需要的,你可以用glib-genmarshall生成它,具体可参见devhelp中有关glib-genmarshall的说明。如: 为信号生成一个参数转换函数为custom_marshal_VOID__OBJECT:
[socol@localhost ~]$:cat marshal.list
VOID:OBJECT
[socol@localhost ~]$:glib-genmarshal --header --prefix=custom_marshal marshal.list > custom_marshal.h
[socol@localhost ~]$:glib-genmarshal --header --prefix=custom_marshal marshal.list > custom_marshal.c
8, GType return_type:该参数的值应为回调函数的返回值在GType类型系统中的ID。G_TYPE_NONE表示没有返回值,G_TYPE_BOOLEAN表示返回值为gboolean;
9, guint n_params:该参数的值应为回调函数的额外参数的个数,如SECTION 1为0,则表示后面没有参数,SECTION 2为1,表示后面还有一个参数。
二,定义好一个signal后,就是要将该信号与对象object和回调函数callback关联起来,使用g_signal_connect或者g_signal_connect_after函数。
/* * g_signal_connect:
* @instance: the instance to connect to.
* @detailed_signal: a string of the form "signal-name::detail".
* @c_handler: the #GCallback to connect.
* @data: data to pass to @c_handler calls.
* Connects a #GCallback function to a signal for a particular object.
* The handler will be called before the default handler of the signal.
* Returns: the handler id
*/
#define g_signal_connect(instance, detailed_signal, c_handler, data) \
g_signal_connect_data ((instance), (detailed_signal), (c_handler), (data), NULL, (GConnectFlags) 0)
/**
* The handler will be called after the default handler of the signal.
* Returns: the handler id
*/
#define g_signal_connect_after(instance, detailed_signal, c_handler, data) \
g_signal_connect_data ((instance), (detailed_signal), (c_handler), (data), NULL, G_CONNECT_AFTER)
实例中如下:
g_signal_connect (stage, "key-press-event", G_CALLBACK (_keyboard_cb), (gpointer)text);
g_signal_connect (texture, "myself-signal", G_CALLBACK (on_paint), NULL);
三,最后就是发射相关信号触发事件。
两种信号发射方式:
1,使用signal name: g_signal_emit_by_name(texture, "myself-signal");
2,使用signal handle: g_signal_emit(texture, mysignal, 0);
clutter实例:
#include <clutter/clutter.h>
ClutterActor *stage, *texture;
gint mysignal;
static void
on_paint (ClutterActor *actor, gconstpointer *data)
{
printf("on paint\n");
}
static void
_keyboard_cb(ClutterActor *actor, ClutterEvent * event, gpointer *data)
{
printf("keyboard cb\n");
g_signal_emit_by_name(texture, "myself-signal");
// g_signal_emit(texture1, mysignal, 0);
}
gint main(gint argc, gchar *argv[])
{
clutter_init (&argc, &argv);
stage = clutter_stage_get_default ();
clutter_actor_set_size (stage, 1280, 720);
g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
GType type = CLUTTER_TYPE_ACTOR;
printf("type=%d\n", type);
texture = clutter_texture_new_from_file("/res/play.png",NULL);
clutter_actor_set_position(texture,100,230);
texture1 = clutter_texture_new_from_file("/res/play.png",NULL);
clutter_actor_set_position(texture1,100,330);
text = clutter_text_new_with_text ("sans 22", "hello");
clutter_actor_set_position(text,100,130);
clutter_text_set_color(CLUTTER_TEXT(text), &text_color);
ClutterActorClass *klass;
GObjectClass *object_class;
klass = CLUTTER_ACTOR_GET_CLASS (…);
object_class = G_OBJECT_CLASS (klass);
mysignal = g_signal_new ("myself-signal",
CLUTTER_TYPE_ACTOR, //G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0
);
g_signal_connect (stage, "key-press-event", G_CALLBACK (_keyboard_cb), text);
g_signal_connect (texture, "myself-signal", G_CALLBACK (on_paint), NULL);
clutter_container_add (CLUTTER_CONTAINER (stage), texture, NULL);
}
SECTION 1:
/**
* ClutterActor::paint:
* @actor: the #ClutterActor that received the signal
* The ::paint signal is emitted each time an actor is being painted.
* Subclasses of #ClutterActor should override the class signal handler
* and paint themselves in that function.
* It is possible to connect a handler to the ::paint signal in order
* to set up some custom aspect of a paint.
* Since: 0.8
*/
actor_signals[PAINT] =
g_signal_new (I_("paint"),
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ClutterActorClass, paint),
NULL, NULL,
_clutter_marshal_VOID__VOID,
G_TYPE_NONE, 0);
SECTION 2:
/**
* ClutterActor::key-press-event:
* @actor: the actor which received the event
* @event: a #ClutterKeyEvent
*
* The ::key-press-event signal is emitted each time a keyboard button
* is pressed while @actor has key focus (see clutter_stage_set_key_focus()).
*
* Return value: %TRUE if the event has been handled by the actor,
* or %FALSE to continue the emission.
*
* Since: 0.6
*/
actor_signals[KEY_PRESS_EVENT] =
g_signal_new (I_("key-press-event"),
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ClutterActorClass, key_press_event),
_clutter_boolean_handled_accumulator, NULL,
_clutter_marshal_BOOLEAN__BOXED,
G_TYPE_BOOLEAN, 1,
CLUTTER_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
SECTION 3:
gboolean
_clutter_boolean_handled_accumulator (GSignalInvocationHint *ihint,
GValue *return_accu,
const GValue *handler_return,
gpointer dummy)
{
gboolean continue_emission;
gboolean signal_handled;
signal_handled = g_value_get_boolean (handler_return);
g_value_set_boolean (return_accu, signal_handled);
continue_emission = !signal_handled;
return continue_emission;
}