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

如何为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;
}

关键修改说明

  1. 正确使用GtkScrolledWindow

    • 我们不再固定Frame的尺寸,而是给ScrolledWindow设置300x300的固定显示尺寸,这是用户希望的可视区域。当内部DrawingArea的尺寸超过这个值时,ScrolledWindow会自动显示滚动条。
  2. 动态更新内容尺寸

    • 每次缩放时,根据缩放因子计算图形的实际显示尺寸(3000 * scaling_factor),并通过gtk_widget_set_size_request更新DrawingArea的自然尺寸。这一步是让ScrolledWindow知道内容真实大小的核心。
  3. 处理滚动偏移

    • 在draw事件中,我们获取ScrolledWindow的滚动偏移值,通过Cairo的translate操作调整绘制原点,确保当前视口显示的是图形的对应部分,再应用缩放因子完成绘制。
  4. 移除中间渲染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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 11:37:54