如何为GTK3嵌套应用中可缩放的GtkDrawingArea添加滚动条
解决方案:为GTK DrawingArea添加缩放+滚动功能
我来帮你解决这个滚动条的问题!你的核心问题是当前的缩放逻辑是把整个图形缩放到固定的300x300区域,而不是让图形本身尺寸随缩放因子变化,导致ScrolledWindow无法感知到内容需要滚动。下面是修改后的完整代码,以及关键实现说明:
修改后的完整代码(cf.c)
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <string.h> #include <ctype.h> #include <gtk/gtk.h> #define ZOOMING_STEP 1.2 typedef struct m_data { double scaling_factor; cairo_surface_t *circle_surface_p; GtkWidget *window_p; GtkWidget *scrolled_window_p; // 新增:保存ScrolledWindow指针用于获取滚动偏移 } m_data_struct; static void activate(GtkApplication *, gpointer); static gboolean zoom_it(GtkWidget *, GdkEvent *, gpointer); static gboolean draw_it(GtkWidget *, cairo_t *, gpointer); int main(int argc, char **argv) { GtkApplication *app_p; m_data_struct my_data; int status; my_data.scaling_factor = 1.0/11.0; app_p = gtk_application_new("calc.foil", G_APPLICATION_FLAGS_NONE); g_signal_connect(app_p, "activate", G_CALLBACK(activate), &my_data); status = g_application_run(G_APPLICATION(app_p), 0, NULL); g_object_unref(app_p); } static void activate(GtkApplication *app_p, gpointer g_data_p) { GtkWidget *frame_p, *notebook_p, *grid0_p, *grid1_p, *drawing_area_p; GtkWidget *scrolled_window_p; m_data_struct *my_data_p; cairo_t *cr_p; my_data_p = (m_data_struct *)g_data_p; my_data_p->window_p = gtk_application_window_new(app_p); gtk_window_set_title(GTK_WINDOW(my_data_p->window_p), "Fot Calculation"); gtk_container_set_border_width(GTK_CONTAINER(my_data_p->window_p), 8); notebook_p = gtk_notebook_new(); gtk_container_add(GTK_CONTAINER(my_data_p->window_p), notebook_p); grid0_p = gtk_grid_new(); gtk_notebook_append_page(GTK_NOTEBOOK(notebook_p), grid0_p, gtk_label_new("First Tab")); grid1_p = gtk_grid_new(); gtk_grid_attach(GTK_GRID(grid0_p), grid1_p, 2, 2, 2, 50); // 创建ScrolledWindow,设置固定显示尺寸300x300 scrolled_window_p = gtk_scrolled_window_new(NULL, NULL); gtk_widget_set_size_request(scrolled_window_p, 300, 300); my_data_p->scrolled_window_p = scrolled_window_p; frame_p = gtk_frame_new("Rectangle"); drawing_area_p = gtk_drawing_area_new(); // 连接必要的信号 g_signal_connect(drawing_area_p, "draw", G_CALLBACK(draw_it), g_data_p); g_signal_connect(drawing_area_p, "scroll-event", G_CALLBACK(zoom_it), g_data_p); gtk_widget_set_events(drawing_area_p, gtk_widget_get_events(drawing_area_p) | GDK_SCROLL_MASK); // 组装组件层级:ScrolledWindow -> Frame -> DrawingArea gtk_container_add(GTK_CONTAINER(frame_p), drawing_area_p); gtk_container_add(GTK_CONTAINER(scrolled_window_p), frame_p); gtk_grid_attach(GTK_GRID(grid1_p), scrolled_window_p, 1, 0, 1, 2); // 预创建大尺寸的图形surface my_data_p->circle_surface_p = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 3000, 3000); cr_p = cairo_create(my_data_p->circle_surface_p); cairo_set_line_width(cr_p, 10); cairo_rectangle(cr_p, 100, 100, 2900, 2900); cairo_set_source_rgb(cr_p, 0, 0, 0); cairo_stroke(cr_p); cairo_destroy(cr_p); gtk_widget_show_all(my_data_p->window_p); } static gboolean zoom_it(GtkWidget *widget_p, GdkEvent *event_p, gpointer g_data_p) { m_data_struct *my_data_p = (m_data_struct *)g_data_p; double new_scale = my_data_p->scaling_factor; // 根据滚轮方向调整缩放因子 if (((GdkEventScroll *)event_p)->direction == GDK_SCROLL_UP) { new_scale *= ZOOMING_STEP; } else if (((GdkEventScroll *)event_p)->direction == GDK_SCROLL_DOWN) { new_scale /= ZOOMING_STEP; } // 限制缩放范围,避免过度缩放 if (new_scale > 2.0) new_scale = 2.0; if (new_scale < 1.0/20.0) new_scale = 1.0/20.0; if (new_scale != my_data_p->scaling_factor) { my_data_p->scaling_factor = new_scale; // 计算图形的实际显示尺寸 int content_width = round(3000 * my_data_p->scaling_factor); int content_height = round(3000 * my_data_p->scaling_factor); // 更新DrawingArea的自然尺寸,让ScrolledWindow感知到内容大小 gtk_widget_set_size_request(widget_p, content_width, content_height); // 触发界面重绘和尺寸调整 gtk_widget_queue_resize(widget_p); gtk_widget_queue_draw(widget_p); } return TRUE; } static gboolean draw_it(GtkWidget *widget_p, cairo_t *cr_p, gpointer g_data_p) { m_data_struct *my_data_p = (m_data_struct *)g_data_p; GtkAdjustment *adj_h, *adj_v; double x_offset, y_offset; // 获取ScrolledWindow的滚动偏移值 adj_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(my_data_p->scrolled_window_p)); adj_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(my_data_p->scrolled_window_p)); x_offset = gtk_adjustment_get_value(adj_h); y_offset = gtk_adjustment_get_value(adj_v); // 清除背景为白色 cairo_set_source_rgb(cr_p, 1.0, 1.0, 1.0); cairo_paint(cr_p); // 平移坐标系以匹配滚动偏移,再应用缩放因子 cairo_translate(cr_p, -x_offset, -y_offset); cairo_scale(cr_p, my_data_p->scaling_factor, my_data_p->scaling_factor); // 绘制预创建的图形surface cairo_set_source_surface(cr_p, my_data_p->circle_surface_p, 0, 0); cairo_paint(cr_p); return FALSE; }
关键修改说明
正确使用GtkScrolledWindow
- 我们不再固定Frame的尺寸,而是给ScrolledWindow设置300x300的固定显示尺寸,这是用户希望的可视区域。当内部DrawingArea的尺寸超过这个值时,ScrolledWindow会自动显示滚动条。
动态更新内容尺寸
- 每次缩放时,根据缩放因子计算图形的实际显示尺寸(3000 * scaling_factor),并通过
gtk_widget_set_size_request更新DrawingArea的自然尺寸。这一步是让ScrolledWindow知道内容真实大小的核心。
- 每次缩放时,根据缩放因子计算图形的实际显示尺寸(3000 * scaling_factor),并通过
处理滚动偏移
- 在
draw事件中,我们获取ScrolledWindow的滚动偏移值,通过Cairo的translate操作调整绘制原点,确保当前视口显示的是图形的对应部分,再应用缩放因子完成绘制。
- 在
移除中间渲染surface
- 原来的代码通过中间surface将整个图形缩放到300x300区域,导致无法实现滚动查看局部。现在直接在draw事件中实时绘制,结合滚动偏移和缩放,完美实现缩放+滚动的预期效果。
编译命令
保持你原来的编译命令不变:
gcc `pkg-config --cflags gtk+-3.0` -lm -o cf cf.c `pkg-config --libs gtk+-3.0`
内容的提问来源于stack exchange,提问作者Flo
相关产品推荐
相关产品推荐

