如果要用 GTK+ 写一些自定义界面的程序(如QQ),免不了要实现一个背景透明的 widget。一个简单的办法是GtkEventBox+GtkImage,不过这种方法太丑陋了点。然而用通常的方法来创建GTK+ widget 的话,会自动给背景填上颜色,设置了 gdk_window_set_back_pixmap (window->window, NULL, FALSE); 也不行。
其 实 GTK+ 给我们提供了很好的背景透明 widget 的例子──GtkButton。如果注意的话,会发现 GtkButton 的背景(圆角处)是透明的,直接显示后面的图案。如果觉得不够明显,用 gtk_button_get_relief 将3D效果设成 GTK_RELIEF_NONE 就可以很清楚的看到了。
GtkButton 的做法是,创建一个不可见的窗口,也就是在 realize 调用 gdk_window_new 时将 wclass 设为 GDK_INPUT_ONLY 而不是 GDK_INPUT_OUTPUT。这样创建的窗体可以接受用户输入的一切事件,却不会输出任何东西,实现了透明。
为了能在透明的窗口上绘制,不能将 gdk_window_new 赋给 widget->window,因为这个窗口没有输出。因此需要将 widget->window 设为其父窗口,这样实际上就成了在父窗口上绘图。而新创建用于接收事件的窗口就保存在 widget 所增加的 event_window 内。
但是用 GDK_INPUT_ONLY 创建出来的窗口是无法接收 expose-notify-event 的,因此需要在 init 时将 widget 设为无窗口模式(GTK_NO_WINDOW),这样就能响应 expose 事件了。
另外在 map 时要手工调用 gdk_show_window 显示 event_window,在 size_allocate 时要用 gdk_window_move_resize 设定其大小。
以下是 GtkButton 的实现代码,只列出必要部分:
实例类型定义:
struct _GtkButton
{
GtkBin bin;
GdkWindow *event_window; // 用于保存接收事件的透明窗口
// 以下省略……
};
初始化:
static void
gtk_button_init (GtkButton *button)
{
GtkButtonPrivate *priv = GTK_BUTTON_GET_PRIVATE (button);
GTK_WIDGET_SET_FLAGS (button, GTK_CAN_FOCUS | GTK_RECEIVES_DEFAULT);
// 设为无窗口模式以响应 expose 事件
GTK_WIDGET_SET_FLAGS (button, GTK_NO_WINDOW);
// 以下省略……
}
realize:
static void
gtk_button_realize (GtkWidget *widget)
{
GtkButton *button;
GdkWindowAttr attributes;
gint attributes_mask;
gint border_width;
button = GTK_BUTTON (widget);
GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
border_width = GTK_CONTAINER (widget)->border_width;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.x = widget->allocation.x + border_width;
attributes.y = widget->allocation.y + border_width;
attributes.width = widget->allocation.width - border_width * 2;
attributes.height = widget->allocation.height - border_width * 2;
// 用 GDK_INPUT_ONLY 以实现透明窗口
attributes.wclass = GDK_INPUT_ONLY;
attributes.event_mask = gtk_widget_get_events (widget);
// 没有 GDK_EXPOSURE_MASK,因为加了也没用
attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_ENTER_NOTIFY_MASK |
GDK_LEAVE_NOTIFY_MASK);
// 也不用管 VISUAL 和 COLORMAP 了
attributes_mask = GDK_WA_X | GDK_WA_Y;
// 当父窗口设为该 widget 的窗口
widget->window = gtk_widget_get_parent_window (widget);
g_object_ref (widget->window);
// 新创建的窗口保存在 event_window 中
button->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
&attributes, attributes_mask);
// 整个 widget 也关联到 event_window 中
gdk_window_set_user_data (button->event_window, button);
widget->style = gtk_style_attach (widget->style, widget->window);
}
map 时显示事件窗口:
static void
gtk_button_map (GtkWidget *widget)
{
GtkButton *button = GTK_BUTTON (widget);
g_return_if_fail (GTK_IS_BUTTON (widget));
// 调用父类的 map
GTK_WIDGET_CLASS (parent_class)->map (widget);
if (button->event_window)
// 显示事件窗口,虽然看不见,但是需要显示出来以接收事件
gdk_window_show (button->event_window);
}
size_allocate:
static void
gtk_button_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkButton *button = GTK_BUTTON (widget);
// 省略若干大小计算代码
// 更新 widget 的大小属性
widget->allocation = *allocation;
if (GTK_WIDGET_REALIZED (widget))
// 更新 event_window 的大小,border_width 是GtkButton计算出来的特有量,非必需
gdk_window_move_resize (button->event_window,
widget->allocation.x + border_width,
widget->allocation.y + border_width,
widget->allocation.width - border_width * 2,
widget->allocation.height - border_width * 2);
// 省略若干其他调整代码……
}
好了,理论上就能显示透明背景的控件了,在 expose 处理函数里把前景画出来就行了。
不过,由于自定义了一个 event_window,需要有些收尾的处理。
unrealize:
static void
gtk_button_unrealize (GtkWidget *widget)
{
GtkButton *button = GTK_BUTTON (widget);
// 省略若干代码……
if (button->event_window)
{
// 销毁事件窗口
gdk_window_set_user_data (button->event_window, NULL);
gdk_window_destroy (button->event_window);
button->event_window = NULL;
}
// 调用父类函数
GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
}
unmap:
static void
gtk_button_unmap (GtkWidget *widget)
{
GtkButton *button = GTK_BUTTON (widget);
g_return_if_fail (GTK_IS_BUTTON (widget));
if (button->event_window)
// 有显示当然就有隐藏啦
gdk_window_hide (button->event_window);
GTK_WIDGET_CLASS (parent_class)->unmap (widget);
}
要注意的就是这些,其他细节参考GtkButton的源码就行了,如果不想下载的话可以从此处去。
水平有限,如有错漏还望指正:-)