GTK+中文社区(gtk.awaysoft.com)

 找回密码
 马上加入

QQ登录

只需一步,快速开始

查看: 2512|回复: 2

关于gtk控件显示与截图的一个问题

[复制链接]

该用户从未签到

发表于 2015-1-16 12:39:23 | 显示全部楼层 |阅读模式
本帖最后由 headmaster_1 于 2015-1-16 12:42 编辑

小弟现在在eclipse下做一个GUI开发的插件,就是通过拖拽编辑图形界面,最终生成一个GTK的图形界面应用,类似于glade的功能,但是图形编辑的代码是在eclipse下用java写的。


  1. <P style="LINE-HEIGHT: 30px; TEXT-INDENT: 2em">
  2. <BLOCKQUOTE>static GdkPixmap* copyPixmap(GdkPixmap *source, gint width, gint height) {
  3. if (source) {
  4. GdkPixmap* pixmap = gdk_pixmap_new(source, width, height, -1);
  5. GdkGC *gc = gdk_gc_new(source);
  6. gdk_draw_drawable(pixmap, gc, source, 0, 0, 0, 0, -1, -1);
  7. g_object_unref(gc);
  8. g_object_unref(source);
  9. return pixmap;
  10. }
  11. return NULL;
  12. }
  13. JNIEnv *m_envir;
  14. jobject m_callback;
  15. jmethodID m_IScreenshotCallback_storeImage;
  16. //
  17. typedef struct _GdkWindowPaint GdkWindowPaint;
  18. struct _GdkWindowPaint {
  19. GdkRegion *region;
  20. GdkPixmap *pixmap;
  21. gint x_offset;
  22. gint y_offset;
  23. };
  24. static void exposeAllWidgetsCallback(GtkWidget *widget, gpointer data);
  25. //
  26. #define PREPARE_EVENT \
  27. GdkEventExpose ev;\
  28. ev.type = GDK_EXPOSE;\
  29. ev.send_event = TRUE;\
  30. ev.area.x = 0;\
  31. ev.area.y = 0;\
  32. ev.count = 0;
  33. #define UPDATE_EVENT \
  34. gdk_window_get_geometry(ev.window, NULL, NULL, &ev.area.width, &ev.area.height, NULL);\
  35. ev.region = gdk_region_rectangle(&ev.area);
  36. static void exposeWidget(GtkWidget *widget) {
  37. GdkWindow *window = widget->window;
  38. if (!GTK_WIDGET_REALIZED(widget)) {
  39. return;
  40. }
  41. // g_warning ("type = %s", G_OBJECT_TYPE_NAME (widget));
  42. if (GTK_IS_SPIN_BUTTON(widget)) {
  43. // spin button
  44. GtkWidgetClass *clazz = (GtkWidgetClass *)GTK_SPIN_BUTTON_GET_CLASS(widget);
  45. GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
  46. {
  47. PREPARE_EVENT
  48. ev.window = spin->panel;
  49. UPDATE_EVENT
  50. clazz->expose_event(widget, &ev);
  51. }
  52. // spin button also contains GtkEntry, so give a chance to expose it too, so no 'else' statement
  53. }
  54. if (GTK_IS_ENTRY(widget)) {
  55. // single text
  56. GtkWidgetClass *clazz = (GtkWidgetClass *)GTK_ENTRY_GET_CLASS(widget);
  57. {
  58. PREPARE_EVENT
  59. ev.window = window;
  60. UPDATE_EVENT
  61. clazz->expose_event(widget, &ev);
  62. }
  63. //
  64. {
  65. PREPARE_EVENT
  66. ev.window = ((GtkEntry*)widget)->text_area;
  67. UPDATE_EVENT
  68. clazz->expose_event(widget, &ev);
  69. }
  70. } else if (GTK_IS_TEXT_VIEW(widget)) {
  71. // multi-line text
  72. {
  73. PREPARE_EVENT
  74. ev.window = gtk_text_view_get_window((GtkTextView *)widget, GTK_TEXT_WINDOW_TEXT);
  75. UPDATE_EVENT
  76. gtk_widget_send_expose(widget, (GdkEvent*)&ev);
  77. }
  78. } else if (GTK_IS_TREE_VIEW(widget)) {
  79. // tree
  80. GtkWidgetClass *clazz = (GtkWidgetClass *)GTK_TREE_VIEW_GET_CLASS(widget);
  81. GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
  82. {
  83. PREPARE_EVENT
  84. ev.window = gtk_tree_view_get_bin_window(tree_view);
  85. UPDATE_EVENT
  86. clazz->expose_event(widget, &ev);
  87. }
  88. } else {
  89. // everything else
  90. {
  91. PREPARE_EVENT
  92. ev.window = window;
  93. UPDATE_EVENT
  94. gtk_widget_send_expose(widget, (GdkEvent*)&ev);
  95. }
  96. }
  97. }
  98. static void exposeAllWidgets(GtkWidget *widget) {
  99. if (!GTK_IS_WIDGET(widget)) {
  100. return;
  101. }
  102. exposeWidget(widget);
  103. if (!GTK_IS_CONTAINER(widget)) {
  104. return;
  105. }
  106. GtkContainer *container = GTK_CONTAINER(widget);
  107. gtk_container_forall(container, exposeAllWidgetsCallback, 0);
  108. }
  109. static GdkPixmap* getPixmap(GdkWindow *window, int shouldCallback) {
  110. if (!gdk_window_is_visible(window)) {
  111. // don't deal with unmapped windows
  112. return NULL;
  113. }
  114. gint width, height;
  115. gdk_window_get_geometry(window, NULL, NULL, &width, &height, NULL);
  116. //
  117. GdkRectangle rect;
  118. rect.x = 0; rect.y = 0; rect.width = width; rect.height = height;
  119. //
  120. GdkRegion *region = gdk_region_rectangle(&rect);
  121. gdk_window_begin_paint_region(window, region);
  122. //
  123. region = gdk_region_rectangle(&rect);
  124. gdk_window_invalidate_region(window, region, TRUE);
  125. //
  126. gpointer widget = NULL;
  127. gdk_window_get_user_data(window, &widget);
  128. if (widget != NULL) {
  129. exposeAllWidgets((GtkWidget*)widget);
  130. }
  131. //
  132. gdk_window_process_updates(window, TRUE);
  133. //
  134. GdkWindowObject *private = (GdkWindowObject *)(window);
  135. GdkPixmap *internalPixmap = ((GdkWindowPaint *)private->paint_stack->data)->pixmap;
  136. if (internalPixmap == NULL) {
  137. return NULL;
  138. }
  139. //
  140. g_object_ref(internalPixmap);
  141. GdkPixmap *pixmap = copyPixmap(internalPixmap, width, height);
  142. gdk_window_end_paint(window);
  143. //
  144. if (shouldCallback) {
  145. (*m_envir)->CallVoidMethod(m_envir, m_callback, m_IScreenshotCallback_storeImage, wrap_pointer(m_envir, widget), wrap_pointer(m_envir, pixmap));
  146. }
  147. //
  148. return pixmap;
  149. }
  150. static GdkPixmap* traverse(GdkWindow *window, int shouldCallback){
  151. gint depth;
  152. gdk_window_get_geometry(window, NULL, NULL, NULL, NULL, &depth);
  153. // strange window
  154. if (depth == 0) {
  155. return NULL;
  156. }
  157. //
  158. GdkPixmap *pixmap = getPixmap(window, shouldCallback);
  159. if (pixmap == NULL) {
  160. return NULL;
  161. }
  162. //
  163. GdkGC *gc = gdk_gc_new(pixmap);
  164. GList *children = gdk_window_get_children(window);
  165. guint length = g_list_length(children);
  166. //
  167. guint i;
  168. for (i = 0; i < length; i++) {
  169. GdkWindow *win = g_list_nth_data(children, i);
  170. GdkPixmap* pix = traverse(win, shouldCallback);
  171. if (pix == NULL) {
  172. continue;
  173. }
  174. gint x, y, width, height;
  175. gdk_window_get_geometry(win, &x, &y, &width, &height, NULL);
  176. gdk_draw_drawable(pixmap, gc, pix, 0, 0, x, y, width, height);
  177. if (!shouldCallback) {
  178. g_object_unref(pix);
  179. }
  180. }
  181. g_object_unref(gc);
  182. return pixmap;
  183. }
  184. static void exposeAllWidgetsCallback(GtkWidget *widget, gpointer data) {
  185. exposeAllWidgets(widget);
  186. }
  187. static GdkPixmap* makeShot(GtkWidget* shellWidget) {
  188. GdkWindow *window = shellWidget->window;
  189. return traverse(window, m_callback != NULL);
  190. }
  191. JNIEXPORT JHANDLE JNICALL OS_NATIVE(_1makeShot)(
  192. JNIEnv *envir, jobject that, JHANDLE widgetHandle, jobject callback) {
  193. m_envir = envir;
  194. if (callback != NULL) {
  195. m_callback = (*envir)->NewGlobalRef(envir, callback);
  196. jclass clazz = (*envir)->GetObjectClass(envir, m_callback);
  197. m_IScreenshotCallback_storeImage = (*envir)->GetMethodID(envir, clazz, "storeImage", CALLBACK_SIG);
  198. }
  199. // make shot
  200. GdkPixmap* pixmap = makeShot((GtkWidget*)unwrap_pointer(envir, widgetHandle));
  201. // clean up
  202. if (callback != NULL) {
  203. (*envir)->DeleteGlobalRef(envir, m_callback);
  204. }
  205. m_callback = NULL;
  206. return (JHANDLE)wrap_pointer(envir, pixmap);
  207. }
复制代码


为了使图形编辑时控件的图形尽可能逼真,我采取了这样一个方案:在后台运行起一个GTK的程序,这个GTK程序是由java代码控制运行起来的(实际是java代码通过JNI调用C代码运行起来)。假设现在这个GTK程序有三个Widget:一个Window,一个Fixed,一个Button。我在创建了这个Button并调用gtk_widget_show()方法显示出这个Button后,马上调用了一段“截图”的代码,代码如下:

代码最下方的_1makeShot是提供给java代码调用的C接口,这个接口再调用了倒数第二个函数 static GdkPixmap* makeShot(GtkWidget* shellWidget)开始截图。也就是说这段截图的代码是通过把指向GtkWidget的指针作为参数,最终获取的返回结果是GdkPixmap*,即这个Widget的图形数据。java代码获取到这个图形数据后,再将其显示在我们的GUI开发工具上

但是现在遇到了一个问题:我调用上述截图代码截到的图有点“滞后”了,我每次在新创建一个控件后马上截到的图是空白,而过一会截到的图才是正确的图。

button = gtk_button_new();

gtk_widget_show(button);

makeShot(button);

这样一个逻辑最终截到的控件的图是空白。

如果我不将makeShot(button)紧跟在gtk_widget_show(button)的后面,过一会再调用makeShot(button)(比如在线程中睡一会)是可以截到正确的button的图的。

我在网上看到有人说gtk_widget_show()这个函数本身是异步的,它只是发了一个信号,具体的控件绘制工作会在后台进行。再根据我做的实验(即上一段所说的在调用gtk_widget_show()后睡一会再截图就能截到),出现这个现象的原因似乎就是由于控件的绘制是异步的,使得我截图的逻辑在控件绘制前就完成了。

但是我这个项目本身具备特殊性,需要我必须实时地去截控件的图。例如用户在编辑控件时改变了控件的大小,这时控件的图形必然发生了变化,我需要马上改变后台控件的大小,并马上截下这个改变了大小的控件的图并将其绘制在前端的窗口上。基本就是用户的操作导致了GUI编辑窗口的重绘,后台运行的GTK窗口也要发生变化,GUI应用要马上截控件的图并反映在图形编辑窗口上。

我曾经试过编写expose事件的回调函数,即

g_signal_connect(button, "expose_event", G_CALLBACK(callback), NULL)

我以为在调用expose_event的回调时控件的绘制已经完成了,所以这时应该可以正常截图。但是我关联了这个回调之后,发现控件根本绘制不出来了。看来是我对expose_event回调理解有误。

现在的问题是:

1、gtk_widget_show()实现控件绘制是异步的吗?

2、如果gtk_widget_show()是异步的,有没有什么方法能让控件的绘制变成同步的吗?

3、为什么编写expose_event的回调会让控件根本不再绘制?难道是这个事件本身有默认的回调函数,在这个默认的回调中会进行控件绘制,而我关联了自己的回调函数就没有调用控件绘制的代码,是这样吗?

各位大神帮帮忙~~~

该用户从未签到

 楼主| 发表于 2015-1-16 12:43:40 | 显示全部楼层
代码这块不知道怎么格式化啊,看上去不太好看
  • TA的每日心情
    奋斗
    2016-10-11 09:20
  • 签到天数: 271 天

    连续签到: 1 天

    [LV.8]以坛为家I

    发表于 2015-2-23 14:02:19 | 显示全部楼层
    gtk_widget_show() 确实是异步的。
    *滑块验证:
    您需要登录后才可以回帖 登录 | 马上加入

    本版积分规则

    申请友链|Archiver|小黑屋|手机版|GTK+中文社区 ( 粤ICP备13080851号 )

    我要啦免费统计

    GMT+8, 2024-11-21 17:01 , Processed in 0.021803 second(s), 8 queries , Redis On.

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表