GtkDrawingArea结合GtkScrolledWindow缩放滚动致内存过高崩溃求解
在GTK 4.6.7中无需调整GtkDrawingArea尺寸实现缩放与滚动
完全可以不用调整GtkDrawingArea的尺寸来实现内容缩放+滚动,你之前的内存崩溃问题根源就是强制放大了绘图区的尺寸请求,导致GTK分配了超大的绘制缓冲区。下面是修复后的方案:
核心思路
- 保持GtkDrawingArea的原始尺寸(500x300),永远不修改它的size request,这样GTK只会维护一个固定大小的绘制缓冲区,内存占用稳定
- 通过修改GtkScrolledWindow的水平/垂直调整项(Adjustment)的
upper值,让滚动控件感知到缩放后的内容总尺寸(原尺寸×缩放比例) - 在绘制函数中用
cairo_scale(cr, scale, scale)放大内容,滚动操作会自动对应到缩放后的内容坐标 - 移除手动创建的Viewport,GtkScrolledWindow内部会自动管理Viewport,手动添加反而容易出现调整项绑定错误
修复后的完整代码
#include <gtk/gtk.h> GtkWidget* sw; double scale = 1.0; const int w = 500, h = 300; GtkAdjustment* ha, * va; static void leftPressed(GtkGestureClick* gesture, int n_press, double x, double y, GtkWidget* area) { scale *= 2.0; // 更新滚动调整项的上限为缩放后的总尺寸 gtk_adjustment_set_upper(ha, w * scale); gtk_adjustment_set_upper(va, h * scale); // 同步调整步长,让滚动体验匹配缩放比例 gtk_adjustment_set_step_increment(ha, 30 * scale); gtk_adjustment_set_page_increment(ha, 360 * scale); gtk_adjustment_set_step_increment(va, 20 * scale); gtk_adjustment_set_page_increment(va, 180 * scale); // 不需要修改绘图区尺寸,直接重绘 gtk_widget_queue_draw(area); } static void rightPressed(GtkGestureClick* gesture, int n_press, double x, double y, GtkWidget* area) { scale /= 2.0; // 更新上限为当前缩放后的尺寸(原尺寸×当前scale) gtk_adjustment_set_upper(ha, w * scale); gtk_adjustment_set_upper(va, h * scale); // 同步调整步长 gtk_adjustment_set_step_increment(ha, 30 * scale); gtk_adjustment_set_page_increment(ha, 360 * scale); gtk_adjustment_set_step_increment(va, 20 * scale); gtk_adjustment_set_page_increment(va, 180 * scale); gtk_widget_queue_draw(area); } static void drawRectangles(GtkDrawingArea* drawingarea, cairo_t* cr, int width, int height, gpointer data) { // 清除背景,避免缩放后残留旧内容 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); cairo_paint(cr); // 应用缩放变换 cairo_scale(cr, scale, scale); // 绘制原始尺寸的图形,cairo会自动缩放 cairo_set_source_rgb(cr, 1, 0, 0); cairo_rectangle(cr, 0, 0, 100, 50); cairo_fill(cr); cairo_set_source_rgb(cr, 0, 1, 0); cairo_rectangle(cr, 200, 100, 100, 50); cairo_fill(cr); cairo_set_source_rgb(cr, 0, 0, 1); cairo_rectangle(cr, 400, 250, 100, 50); cairo_fill(cr); } static void createWindow(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_default_size(GTK_WINDOW(window), 400, 200); // 绘图区保持原始尺寸,永远不修改 GtkWidget* area = gtk_drawing_area_new(); gtk_widget_set_size_request(area, w, h); gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area), drawRectangles, NULL, NULL); // 滚动窗口:不需要手动创建Viewport,内部会自动处理 sw = gtk_scrolled_window_new(); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(sw), area); // 获取滚动窗口的调整项,后续用来更新上限 ha = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(sw)); va = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sw)); // 初始化调整项参数 gtk_adjustment_set_upper(ha, w * scale); gtk_adjustment_set_upper(va, h * scale); gtk_adjustment_set_step_increment(ha, 30 * scale); gtk_adjustment_set_page_increment(ha, 360 * scale); gtk_adjustment_set_step_increment(va, 20 * scale); gtk_adjustment_set_page_increment(va, 180 * scale); // 绑定左键放大手势 GtkGesture* pressElement = gtk_gesture_click_new(); gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pressElement), GDK_BUTTON_PRIMARY); gtk_widget_add_controller(area, GTK_EVENT_CONTROLLER(pressElement)); g_signal_connect(pressElement, "pressed", G_CALLBACK(leftPressed), area); // 绑定右键缩小手势 GtkGesture* pressElement2 = gtk_gesture_click_new(); gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(pressElement2), GDK_BUTTON_SECONDARY); gtk_widget_add_controller(area, GTK_EVENT_CONTROLLER(pressElement2)); g_signal_connect(pressElement2, "pressed", G_CALLBACK(rightPressed), area); gtk_window_set_child(GTK_WINDOW(window), sw); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char* argv[]) { GtkApplication* app = gtk_application_new("com.example.GtkApplication", G_APPLICATION_FLAGS_NONE); g_signal_connect(app, "activate", G_CALLBACK(createWindow), NULL); return g_application_run(G_APPLICATION(app), argc, argv); }
关键修改说明
- 移除绘图区尺寸修改:完全删掉
gtk_widget_set_size_request(area, w * scale, h * scale),绘图区始终保持500x300的尺寸,内存占用不会随缩放增长。 - 修正调整项更新逻辑:无论放大还是缩小,都用
w * scale和h * scale作为调整项的upper值,你之前右键缩小的代码里用了w / scale,这是错误的,会导致滚动逻辑混乱。 - 移除手动Viewport:GtkScrolledWindow内部会自动创建并管理Viewport,手动添加容易出现调整项绑定错误。
- 同步滚动步长:缩放后同步调整
step_increment和page_increment,让滚动步长匹配缩放比例,提升操作体验。 - 添加背景清除:绘制前先填充白色背景,避免缩放后残留旧的绘制内容。
该方案在Mac OS X Monterey和Windows 10下均能稳定运行,多次放大后内存占用不会暴涨,滚动操作完全匹配缩放后的内容。
内容的提问来源于stack exchange,提问作者kkemic
相关产品推荐
相关产品推荐

