5
13
2011
6

Cairo 初体验

本文来自依云's Blog,转载请注明。

其实我觊觎 Cairo 很久了。昨天看到这篇文章,就下决心试了下。

简单图形的绘制

这个确实很简单,随便从后文的参考资料处抄点代码过来即可:

static gboolean on_expose_event(GtkWidget *widget,
    GdkEventExpose *event, gpointer data){
  cairo_t *cr;

  cr = gdk_cairo_create(widget->window);

  cairo_set_source_rgb(cr, 0, 0, 0);
  cairo_set_line_width(cr, 1);

  cairo_rectangle(cr, 20, 20, 120, 80);
  cairo_stroke_preserve(cr);
  cairo_set_source_rgb(cr, 1, 1, 1);
  cairo_fill(cr);

  cairo_destroy(cr);

  return FALSE;
}

看函数名基本上知道是在做什么了。我需要一个半透明的矩形,于是查文档,找到cairo_set_source_rgba函数即可。那个 cr,我是折腾到最后才明白它代表了绘制的 context (环境/上下文);至于那个cairo_stroke_preserve,我最终弄明白 Cairo 的绘制方法后才明白的。不管是cairo_rectangle还是我后来用到的cairo_arc,都是“画”了一个路径。之所以要把“画”加上引号,是因为路径本身是看不到的。使用cairo_stroke来描绘路径,使用cairo_fill来填充,而后面带_preserve的版本,是说路径处理完了还留着,下一个操作继续用。我之前犯了个错误,把所有的矩形和圆的路径都画好了才绘制,结果矩形和圆之间有道连线,圆的半径也被画出来了。折腾好久才知道应该在每个路径完成后就绘制。

事件处理

翻了好久的GTK、GDK文档,还是没弄明白该怎么在鼠标拖动时绘制。Google 一下,才看到 Garfileo 的这篇文章,在兴奋之余很有郁闷,Garfileo 以前的 Cairo 相关文章我都收集了,可他为什么后来又换博客了呢。。。。

  gtk_widget_add_events(window, GDK_KEY_PRESS_MASK |
      GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
  g_signal_connect(window, "button-press-event",
      G_CALLBACK(drawing_start), NULL);
  g_signal_connect(window, "button-release-event",
      G_CALLBACK(drawing_finish), NULL);
  g_signal_connect(GTK_OBJECT(window), "motion_notify_event",
      G_CALLBACK(drawing_move), NULL);

就这样就可以了。然后我就开始在每个motion_notify_event发生时画一遍,结果在画的时候图像不停地闪。问了下 gtalk 好友老猫,才知道原来只要在 expose 事件时画就好了,而图像改变后,调用下gtk_widget_queue_draw即可。至于为什么这样图像就不会闪,我至今还不清楚。

右键菜单

我需要在几种不同的选区之间切换。本来用 RadioButton 挺好的,但是我现在只会在 GtkWindow 或者 GtkDialog 里画图。后来就想到了右键菜单。查阅文档后就写出以下代码:

  menu = gtk_menu_new();
  menu_rect = gtk_menu_item_new_with_label("矩形");
  menu_circ = gtk_menu_item_new_with_label("圆形");
  menu_poly = gtk_menu_item_new_with_label("多边形");
  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_rect);
  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_circ);
  /* TODO 绘制多边形 */
  /* gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_poly); */
  g_signal_connect(menu_rect, "activate",
      G_CALLBACK(switch_to_rect), NULL);
  g_signal_connect(menu_circ, "activate",
      G_CALLBACK(switch_to_circ), NULL);
  g_signal_connect(menu_poly, "activate",
      G_CALLBACK(switch_to_poly), NULL);

  ...

  }else if(event->button == 3){
    gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
  }

但是这样点右键时只会显示一个小小的白色小边,就像没有菜单项在里面一样。百思不得其解,于是求助于 Google,最后不知道在哪里看到了,原来还需要对menu调用gtk_widget_show_all。想想也是,对窗口调用gtk_widget_show_all只是把窗口里的东西全部显示出来了,但我这个右键弹出菜单不是在窗口里的呀。

一点题外话

因为要绘制多个图形,本来准备自己写个单链表的,刚写完 node 结构体,突然想到,GLib 里不是有各种数据结构吗?于是再查文档,找到 GSList,愉快地用上了。g_slist_foreach这个函数非常好用。

参考资料

Category: 编程 | Tags: cairo C代码 gtk | Read Count: 8917
Avatar_small
Garfileo 说:
May 13, 2011 05:33:21 PM

大概是因为在 expose 或 draw 信号里画,gdk 会把你画的东西攒到一起,然后一总画出来,所以不会闪。

Avatar_small
依云 说:
May 13, 2011 09:45:38 PM

啊,Garfileo 来了啊。久仰久仰~

这样的话肯定不是一个 gtk_widget_queue_draw 对应一次绘制了。那么 expose 事件是什么时候触发的呢?总不能说像量子物理那样,我看它的时候它就触发吧 ;-P

Avatar_small
Garfileo 说:
May 14, 2011 08:09:46 AM

有关 gtk+ 屏幕绘制的机理,在 gtk_widget_set_double_buffered 和 gdk_window_begin_paint_region 函数的说明中有一些解释。

expose 事件应该是当你无动于衷的时候会被 gtk+ 的主循环触发。当你使用 queue_draw 的时候,它也会被触发。

muzuiget 说:
May 14, 2011 09:31:15 AM

> 画的时候图像不停地闪……调用下gtk_widget_queue_draw即可。至于为什么这样图像就不会闪
我猜这个貌似就是传说中的"双倍缓冲”,不直接画到实际屏幕中,而是画到背后一张看不到的临时图像(off_screen)中,等所有绘图操作完成了,有需要时一次过把这个临时图像复制到实际屏幕去,这就避免闪烁问题。“有需要时”可能是鼠标划过widget,而gtk_widget_queue_draw就是让gtk马上复制图像。

Avatar_small
依云 说:
May 14, 2011 01:29:03 PM

我记得 expose 是 Xlib 的一个事件吧,我发现如果不 queue_draw 的话,切换工作区后回来的时候才会触发。

Avatar_small
依云 说:
May 14, 2011 01:33:27 PM

去翻了下 gtk_widget_queue_draw 的文档,原来这里已经说得很清楚了——Invalidates the rectangular area of widget defined by x, y, width and height by calling gdk_window_invalidate_rect() on the widget's window and all its child windows. Once the main loop becomes idle (after the current batch of events has been processed, roughly), the window will receive expose events for the union of all regions that have been invalidated.
看来是 expose 事件被触发的次数少于 motion_notify_event 的次数,于是就不闪了。


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

部分静态文件存储由又拍云存储提供。 | Theme: Aeros 2.0 by TheBuckmaker.com