GTK绘图区域坐标因其他部件显隐偏移,居中线条绘制异常求助
我把GTK绘图区域和其他部件一起打包布局,当这些部件显示时,绘图区域的坐标会偏移,导致居中线条没法居中绘制。
截图显示:菜单栏显示时,绘图区域的居中线条向下偏移,不在绘图区域中心;隐藏菜单栏后,线条回到正确位置。
绘图相关代码
//Let's translate to origin of the drawing area allocation to avoid //the cross lines to be rotated too cairo_save(cr); cairo_identity_matrix(cr); cairo_translate(cr, allocation.x, allocation.y); //This to compensate the allocation menubar height gap //which prevents the horizontal line to be drawn at the center //I wonder why the coordinates in the drawing area are shifted //when the blessed menubar is hidden by the TAB key, shouldn't //they always be 0,0 at the top left? int menubar_height = gtk_widget_get_allocated_height(img->menubar); if (img->textbox->draw_horizontal_line) { if (gtk_widget_is_visible(img->menubar)) center_y = menubar_height + center_y; else center_y = allocation.height / 2; //Draw the horizontal centering line cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); cairo_set_line_width(cr, 1.0); cairo_move_to(cr, 0, center_y - 2); cairo_line_to(cr, allocation.width, center_y - 2); cairo_stroke(cr); cairo_set_source_rgb(cr, 0.8, 0.7, 0.3); cairo_move_to(cr, 0, center_y); cairo_line_to(cr, allocation.width, center_y); cairo_stroke(cr); cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); cairo_move_to(cr, 0, center_y + 2); cairo_line_to(cr, allocation.width, center_y + 2); cairo_stroke(cr); } // Draw the vertical centering line if (img->textbox->draw_vertical_line) { cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); cairo_set_line_width(cr, 1.0); cairo_move_to(cr, center_x - 2, 0); cairo_line_to(cr, center_x - 2, allocation.height + menubar_height); cairo_stroke(cr); cairo_set_source_rgb(cr, 0.8, 0.7, 0.3); cairo_move_to(cr, center_x, 0); cairo_line_to(cr, center_x, allocation.height + menubar_height); cairo_stroke(cr); cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); cairo_move_to(cr, center_x + 2, 0); cairo_line_to(cr, center_x + 2, allocation.height + menubar_height); cairo_stroke(cr); } cairo_restore(cr);
布局打包代码
/* Create the image area */ vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); g_object_set(G_OBJECT(vbox), "valign", GTK_ALIGN_CENTER); gtk_box_pack_start (GTK_BOX (right_horizontal_box), vbox, TRUE, TRUE, 0); img_struct->image_area = gtk_drawing_area_new(); gtk_box_pack_start(GTK_BOX(vbox), img_struct->image_area, FALSE, FALSE, 0); gtk_widget_set_hexpand(img_struct->image_area, FALSE); gtk_widget_set_vexpand(img_struct->image_area, FALSE); gtk_widget_set_halign(img_struct->image_area, GTK_ALIGN_CENTER); gtk_widget_set_valign(img_struct->image_area, GTK_ALIGN_CENTER); gtk_widget_set_events(img_struct->image_area, GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK); gtk_widget_set_can_focus(img_struct->image_area, TRUE); gtk_widget_grab_focus(img_struct->image_area);
疑问
为什么和绘图区域一同打包的其他部件显示时,绘图区域的(0,0)坐标会发生偏移?
原因分析
问题核心是混用了全局坐标与绘图区域局部坐标,再加上布局对齐设置放大了这个错误:
Cairo坐标矩阵的错误操作
GTK传给绘图回调的Cairo上下文,默认已经把原点设在了绘图区域的左上角(局部坐标(0,0))。但你调用cairo_identity_matrix(cr)重置矩阵后,又用cairo_translate(cr, allocation.x, allocation.y)把原点移到了绘图区域在父容器里的全局位置——这相当于把绘图坐标从「局部」切换成了「全局窗口坐标」,自然会受外部布局变化影响。布局对齐的间接影响
你给绘图区域和父容器都设置了GTK_ALIGN_CENTER对齐,当菜单栏显示/隐藏时,父容器会重新计算布局,绘图区域在父容器里的全局位置(allocation.x/allocation.y)会改变。而你用这个全局偏移来平移Cairo上下文,就导致内部绘图跟着全局位置跑,线条自然偏移。手动补偿逻辑冗余且错误
绘图区域的allocation.height是它自身的高度,和菜单栏完全无关。你用菜单栏高度修正center_y的操作,本质是在局部坐标里叠加了全局偏移,进一步打乱了绘图位置。
修复方案
去掉多余的坐标变换和手动补偿,直接用绘图区域的局部坐标绘制:
cairo_save(cr); // 移除cairo_identity_matrix和cairo_translate,使用默认的局部坐标 if (img->textbox->draw_horizontal_line) { // 直接基于绘图区域自身高度计算中心Y int center_y = allocation.height / 2; // 绘制水平居中线条 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); cairo_set_line_width(cr, 1.0); cairo_move_to(cr, 0, center_y - 2); cairo_line_to(cr, allocation.width, center_y - 2); cairo_stroke(cr); cairo_set_source_rgb(cr, 0.8, 0.7, 0.3); cairo_move_to(cr, 0, center_y); cairo_line_to(cr, allocation.width, center_y); cairo_stroke(cr); cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); cairo_move_to(cr, 0, center_y + 2); cairo_line_to(cr, allocation.width, center_y + 2); cairo_stroke(cr); } // 绘制垂直居中线条 if (img->textbox->draw_vertical_line) { // 直接基于绘图区域自身宽度计算中心X int center_x = allocation.width / 2; cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); cairo_set_line_width(cr, 1.0); cairo_move_to(cr, center_x - 2, 0); cairo_line_to(cr, center_x - 2, allocation.height); cairo_stroke(cr); cairo_set_source_rgb(cr, 0.8, 0.7, 0.3); cairo_move_to(cr, center_x, 0); cairo_line_to(cr, center_x, allocation.height); cairo_stroke(cr); cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); cairo_move_to(cr, center_x + 2, 0); cairo_line_to(cr, center_x + 2, allocation.height); cairo_stroke(cr); } cairo_restore(cr);
修改后,绘图区域内部的(0,0)始终是自身左上角,居中线条基于自身尺寸计算,不会再受外部布局变化影响。
内容的提问来源于stack exchange,提问作者GiuTor

