You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
}

关键修改说明

  1. 移除绘图区尺寸修改:完全删掉gtk_widget_set_size_request(area, w * scale, h * scale),绘图区始终保持500x300的尺寸,内存占用不会随缩放增长。
  2. 修正调整项更新逻辑:无论放大还是缩小,都用w * scale和h * scale作为调整项的upper值,你之前右键缩小的代码里用了w / scale,这是错误的,会导致滚动逻辑混乱。
  3. 移除手动Viewport:GtkScrolledWindow内部会自动创建并管理Viewport,手动添加容易出现调整项绑定错误。
  4. 同步滚动步长:缩放后同步调整step_increment和page_increment,让滚动步长匹配缩放比例,提升操作体验。
  5. 添加背景清除:绘制前先填充白色背景,避免缩放后残留旧的绘制内容。

该方案在Mac OS X Monterey和Windows 10下均能稳定运行,多次放大后内存占用不会暴涨,滚动操作完全匹配缩放后的内容。

内容的提问来源于stack exchange,提问作者kkemic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 10:16:04