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

如何实现按住GtkNotebook标题拖动GtkWindow且不干扰子控件?

解决GtkNotebook标题拖动窗口时子控件失效的问题

你的问题出在将手势控制器直接绑定到了整个GtkNotebook上,导致所有点击标签区域的事件都会被手势捕获,包括点击关闭按钮的操作,从而阻断了按钮自身的信号响应。要同时实现标题拖动和子控件正常工作,需要调整手势的绑定范围,并添加事件目标判断,让手势只在点击标签的非控件区域时触发拖动。

核心修改点

  • 缩小手势绑定范围:将GtkGestureClick绑定到每个标签的容器(GtkBox)上,而非整个GtkNotebook,确保只处理标签区域的事件。
  • 添加事件目标判断:在拖动触发函数中,检查点击的目标是否为按钮类控件,若是则跳过拖动逻辑,让子控件正常响应自身事件。
  • 优化手势传播阶段:设置手势的传播阶段为GTK_PHASE_TARGET,确保子控件优先处理事件,手势仅在事件未被处理时生效。

修改后的完整代码

#include <gtk/gtk.h>
GtkWidget *window;
GtkWidget *notebook;

static gboolean close_page_cb(GtkWidget *widget, gpointer user_data)
{
    int page = gtk_notebook_get_current_page(GTK_NOTEBOOK(user_data));
    gtk_notebook_remove_page(GTK_NOTEBOOK(user_data), page);
    return TRUE;
}

static void start_move(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data)
{
    // 获取当前事件的目标控件
    GdkEvent *event = gtk_event_controller_get_current_event(GTK_EVENT_CONTROLLER(gesture));
    GtkWidget *target = gtk_event_controller_get_target(GTK_EVENT_CONTROLLER(gesture));

    // 如果点击的是按钮或按钮的子控件,直接返回,不触发拖动
    if (GTK_IS_BUTTON(target) || gtk_widget_is_ancestor(target, GTK_WIDGET(data))) {
        return;
    }

    GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
    GdkSurface *surface = gtk_native_get_surface(gtk_widget_get_native(widget));
    guint button = gdk_button_event_get_button(event);
    guint32 timestamp = gdk_event_get_time(event);

    gtk_widget_translate_coordinates(widget, GTK_WIDGET(gtk_widget_get_root(widget)),
                                    x, y, &x, &y);
    gdk_toplevel_begin_move(GDK_TOPLEVEL(surface), gdk_event_get_device(event), button, x, y, timestamp);
}

static void activate(GtkApplication *app, gpointer user_data)
{
    window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "example");
    gtk_window_set_default_size(GTK_WINDOW(window), 400, 200);

    GtkWidget *grid = gtk_grid_new();
    gtk_window_set_child(GTK_WINDOW(window), grid);
    gtk_widget_set_vexpand(grid, TRUE);
    gtk_widget_set_hexpand(grid, TRUE);

    // 创建Notebook
    notebook = gtk_notebook_new();
    gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_TOP);
    gtk_grid_attach(GTK_GRID(grid), notebook, 0, 6, 3, 3);
    gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), TRUE);
    gtk_widget_set_vexpand(notebook, TRUE);
    gtk_widget_set_hexpand(notebook, TRUE);

    // 添加标签页
    GtkWidget *tv = gtk_text_view_new();
    GtkWidget *label = gtk_label_new("example");
    GtkWidget *button = gtk_button_new_with_label("  X  ");

    GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_box_prepend(GTK_BOX(box), label);
    gtk_box_append(GTK_BOX(box), button);

    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), tv, box);
    g_signal_connect(button, "clicked", G_CALLBACK(close_page_cb), notebook);

    // 给标签容器添加拖动手势
    GtkGesture *gesture = gtk_gesture_click_new();
    // 设置手势仅在事件未被子控件处理时响应
    gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(gesture), GTK_PHASE_TARGET);
    g_signal_connect(gesture, "pressed", G_CALLBACK(start_move), button);
    gtk_widget_add_controller(box, GTK_EVENT_CONTROLLER(gesture));

    gtk_window_present(GTK_WINDOW(window));
}

int main(int argc, char **argv)
{
    GtkApplication *app = gtk_application_new("org.gtk.example", G_APPLICATION_FLAGS_NONE);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    int status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);
    return status;
}

关键细节解释

  1. 手势绑定到标签容器:将手势添加到标签的GtkBox上,确保只有点击标签区域时才会触发手势检测,不会影响Notebook的其他部分。
  2. 目标控件判断:在start_move中通过gtk_event_controller_get_target获取点击的具体控件,如果是关闭按钮或其祖先控件,直接跳过拖动逻辑,让按钮的clicked信号正常触发。
  3. 传播阶段设置:GTK_PHASE_TARGET阶段让事件先传递给子控件处理,只有当子控件没有处理该事件时,手势才会响应,从机制上避免了事件被拦截。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 01:54:55