实现背景透明的GTK+ widget(转)

本文详细介绍了如何使用GTK+实现背景透明的widget,并通过分析GTK+内部机制,展示了如何利用GDK输入仅模式创建透明窗口,以及如何在透明窗口上绘制内容。通过实例代码演示了从初始化到映射、大小分配等关键步骤,最终实现了一个能够显示透明背景的控件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实现背景透明的GTK+ widget(转)  

引用自:

http://tigersoldier.is-programmer.com/2009/2/10/gtk-transpraent-custom-widget.7086.html

 

如果要用 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 ) ;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值