GTK button的源代码是具有代表性的,好好地看一遍对自己写GTK的widget有很大的帮助。(GTK2.16.6)
一、结构
GTK button的定义如下:
struct _GtkButton
{
GtkBin bin;
GdkWindow *GSEAL (event_window);
gchar *GSEAL (label_text);
guint GSEAL (activate_timeout);
// 其他的flag位省略
}
Gtk button是由GtkBin继承来的,GtkBin是只有一个child的容器,本身对界面显示处理没有操作,结合event_window成员可以联想到,Gtk Button就是一个具有按钮各项事件处理的容器,在此容器中放入widgets就可以构建出不同样式的按钮。
GSEAL定义:
GSEAL()宏定义在gdkconfig.h.win32中,如下:
#ifndef GSEAL
/* introduce GSEAL() here for all of Gdk and Gtk+ without the need to modify GLib */
# ifdef GSEAL_ENABLE
# define GSEAL(ident) _g_sealed__ ## ident
# else
# define GSEAL(ident) ident
# endif
#endif /* !GSEAL */
不必多说,就当这个宏不存在即可。
二、信号
GTK button的定义如下:
struct _GtkButtonClass
{
GtkBinClass parent_class;
void (* pressed) (GtkButton *button);
void (* released) (GtkButton *button);
void (* clicked) (GtkButton *button);
void (* enter) (GtkButton *button);
void (* leave) (GtkButton *button);
void (* activate) (GtkButton *button);
// 其它定义忽略
};
类结构中定义了扩展的信号,这个不用多想,但是不管是要写GTK应用还是创建新的Widget都应该明确一下信号规则。以按钮为例,Gtk的信号传递方式是这样的:
按钮按下 --> GDKwindow 发出GDK_BUTTON_PRESS --> GDK预设定的处理
--> 向GTK_Widget 发出 button_press_event 信号 --> GTK_Widget信号处理(处理1)
--> 向 button 发出 clicked信号 --> 调用具体widget的事件处理(处理2)
我们通常会通过g_signal_connect()设置“处理1”和“处理2”。但是他们有所不同:
“处理1”是GTK_WIDGET定义的信号,他们的callback函数一般定义如下:
gboolean user_function (GtkWidget *widget,
GdkEventButton *event,
gpointer user_data)
这种在返回TRUE时停止继续调用其他向下传递的处理,也就是不会调用clicked事件的处理(处理2)。也有void的信号调用定义,应该是不支持其他继承widget的信号。
“处理2”是基于GTK_WIDGET的widget定义的信号处理,这个依据不同widget而定,如button的clicked信号callback形式如下:
void user_function (GtkButton *button, gpointer user_data)
在写自己的Widget的时候,在class_init()定义WIGET CLASS的事件函数和本身的事件函数时需要注意一下,特别是返回FALSE。
三、私有变量定义
对OO类很容易理解,但是对于C的GTK来说,既然公开了struct,开发者总会对其成员有窥私的情节,至少我有,那么直接调用和赋值在所难免,崩溃也是经常的。
GTK button在C文件中定义了一个struct _GtkButtonPrivate{},在内部使用,通过export的函数操作,主要对图片显示操作,封装的还可以,但是我对其使用不太认同,明明可以派生出适合的按钮么,就像link_button,感觉开始不纯粹了。
结构变量通过g_type_class_add_private ()登记,使其可以在每个实例中复制一份。
class_init()
{
// ...;
// 在最后
g_type_class_add_private (gobject_class, sizeof (GtkButtonPrivate));
}
void g_type_class_add_private (gpointer g_class, gsize private_size);
g_class :
class structure for an instantiatable type
private_size :
size of private structure.
Registers a private structure for an instantiatable type;
When an object is allocated, the private structures for the type and all of its parent types are allocated sequentially in the same memory block as the public structures.
This function should be called in the type's class_init() function. The private structure can be retrieved using the G_TYPE_INSTANCE_GET_PRIVATE() macro.
The following example shows attaching a private structure MyObjectPrivate to an object MyObject defined in the standard GObject fashion.
G_TYPE_INSTANCE_GET_PRIVATE()
#define G_TYPE_INSTANCE_GET_PRIVATE(instance, g_type, c_type) ((c_type*)g_type_instance_get_private ((GTypeInstance*) (instance), (g_type)))
instance :
the instance of a type deriving from private_type.
g_type :
the type identifying which private data to retrieve.
c_type :
The C type for the private structure.
Gets the private structure for a particular type.
The private structure must have been registered in the class_init function with g_type_class_add_private().
This macro should only be used in type implementations.
四、GTK Widget的那些过程
new-->init-->size_request->size_allocate-->realize-->map-->expose_event,提醒自己的是以下几点:
1、size_allocate中要对容器本身的child进行size的计算。
2、gtk_widget_show就是做init之后的
3、在init中GTK_WIDGET_SET_FLAGS (button, GTK_NO_WINDOW);设置为无窗口模式。在realize中通过widget->window = gtk_widget_get_parent_window (widget);设置其窗口为父窗口,呃,这样其expose的绘图会发生在其父窗口内。这样就实现了一个背景透明的widget。
4、在realize中继续attributes.wclass = GDK_INPUT_ONLY;而不是 GDK_INPUT_OUTPUT。这样创建的窗体可以接受用户输入的一切事件,却不会输出任何东西,特别是默认的底色,也就不会覆盖透明部分。button->event_window = gdk_window_new (..&attributes..);之后gdk_window_set_user_data(button->event_window, button);使接收的事件传送给button。然后在map中gdk_window_show (button->event_window);启用button->event_window,虽然不显示任何东西。
5、expose_event依据设置通过gtk_paint_box()和gtk_paint_focus()在父窗口中画出具体的按钮图形,矩形。
五、有关背景透明Widget和不规则窗口的思考
现在有一个创建不规则形状按钮的问题。
1、常规做法
通过gtk_widget_shape_combine_mask()将一个具有gdk_window的gtk_widget,通常是GtkEventBox,结合bitmap产生。这个方法同样可以产生出不规则的主窗体。然后将button放入GtkEventBox中,替换button的expose_event,可以看出GtkButton的显示就是由其决定的,这样就行了。
2、有人提出利用GtkButton的背景透明做法实现
完全可以继承GtkButton,然后替换其expose_event,显示背景透明bitmap。可是button->event_window的事件接收区域如何处理成不规则的呢?
六、CLASS_INIT
这里主要设置具有共性的g_object类的数据,包括各种父类事件的callback、自定义信号默认callback和登记、property、style和private结构。
GObjectClass:constructor、set_property、get_property、dispose、finalize、constructed、notify、dispatch_properties_changed。
GtkObjectClass:destroy。就一个,这个一般都要定义一下。
GtkWidgetClass:比较多,也就除了前面两个Class的事件,基本上都是,注意用法。如有必要,定义一下其他父类的事件callback。
注意替换掉事件callback后,依据widget的构造,可能会需要手动调用中间各层父类的相同事件。
七、INIT
实例初始化,没必要废话,但是如果对于各层父类的signal不是替换的话,特别是GtkWidgetClass的,那么就要在这里为每一个实例connect信号处理函数。
八、GtkButton的构造
GtkButton自己构造完毕后是这样的GtkBin->GtkAlignment->GtkBox->GtkLabel and GtkImage。内部还复杂的转来转去,Label的内容保存在GtkButton->text中,每次construct依据它产生label;Image由其private数据管理。
但是无论如何GtkButton本身就是一个GtkBin,如果仅仅new出来,我们完全可以不必去使用他给出的方法设置其内容,这也就很好解释那些实现图片按钮方法,虽然我们自己构造出不同于button理想结构的图片按钮,但是仍很好的显示和使用。
阅读(2147) | 评论(1) | 转发(0) |