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

gtk-rs中GtkGrid控件拖拽移动无效:如何正确移动网格内控件

Gtk4 Grid中拖拽移动Button后UI不更新的解决办法

我正在使用gtk-rs(依赖配置为gtk4 0.11.3、gtk4-layer-shell 0.8.0)开发Gtk应用,尝试通过移除并重新attach的方式移动GtkGrid内的Button控件。实现拖拽逻辑后,拖拽结束时计算偏移量更新控件位置,grid.query_child显示控件位置已成功变更,但UI上该Button始终停留在初始的左上角位置;多次拖拽后,查询到的位置持续更新,但界面无任何变化。

依赖配置

gtk = { version = "0.11.3", package = "gtk4", features = ["v4_22"] }
gtk-layer-shell = { version = "0.8.0", package = "gtk4-layer-shell" }

Rust代码

use gtk::glib;
use gtk::glib::{CLONE_MACRO_LOG_DOMAIN, clone};
use gtk::{Application, ApplicationWindow, Button, GestureDrag, Grid, prelude::*};
use gtk_layer_shell::{Layer, LayerShell};
use std::cell::{Cell};
use std::env;

const CELL_SIZE: i32 = 80;
const CELL_GAP: i32 = 16;
const COLS: i32 = 4;
const ROWS: i32 = 8;

fn main() -> gtk::glib::ExitCode {
    unsafe {
        env::set_var("G_MESSAGES_DEBUG", CLONE_MACRO_LOG_DOMAIN);
        env::set_var("G_MESSAGES_DEBUG", "all");
    }

    let application = Application::builder()
        .application_id("application")
        .build();

    application.connect_activate(build_ui);
    application.run()
}

// convert col/row count into width/height
fn widget_size(cols: i32, rows: i32) -> (i32, i32) {
    (
        CELL_GAP * (cols - 1) + CELL_SIZE * cols,
        CELL_GAP * (rows - 1) + CELL_SIZE * rows,
    )
}

// build window
fn build_ui(app: &Application) {
    let window = ApplicationWindow::builder()
        .application(app)
        .can_focus(false)
        .title("Grid Layout")
        .build();

    // doesn't matter here, the problem is no different with or without layer shell
    window.init_layer_shell();
    window.set_layer(Layer::Top);

    // calculate a 4x8 grid widget size, 
    // that is (4 cols + 3 gaps) x (8 rows + 7 gaps)
    let (grid_width, grid_height) = widget_size(4, 8);

    let grid = Grid::builder()
        .row_spacing(CELL_GAP)
        .column_spacing(CELL_GAP)
        .width_request(grid_width)
        .height_request(grid_height)
        .build();

    let button = Button::builder()
        .label("drag")
        .width_request(CELL_SIZE)
        .height_request(CELL_SIZE)
        .build();

    // button position
    let position = Cell::new((0, 0));
    let (x, y) = position.get();
    // the original position
    grid.attach(&button, x, y, 1, 1);

    let drag = GestureDrag::new();

    // on drag end, calculates how many col/row should offset from relative position
    drag.connect_drag_end(clone!(
        #[weak]
        button,
        #[weak]
        grid,
        move |_, offset_x, offset_y| {
            let (c, r, _, _) = grid.query_child(&button); // the original position
            println!("origin {} {}", c, r);

            grid.remove(&button);

            let x_movement = (offset_x / (CELL_SIZE + CELL_GAP) as f64) as i32;
            let y_movement = (offset_y / (CELL_SIZE + CELL_GAP) as f64) as i32;
            println!("offset {} {}", x_movement, y_movement);

            position.update(|(x, y)| (x + x_movement, y + y_movement)); // cache the position

            let (x, y) = position.get();
            println!("new {} {}", x, y); // new position
            grid.attach(&button, x, y, 1, 1);

            let (c, r, _, _) = grid.query_child(&button); // confirm result of re-attaching
            println!("result {} {}", c, r);
            grid.queue_draw();
        }
    ));

    button.add_controller(drag);
    window.set_child(Some(&grid));
    window.present();
}

拖拽运行日志

第一次拖拽:

origin 0 0
offset 0 1
new 0 1
result 0 1

第二次拖拽:

origin 0 1
offset 0 1
new 0 2
result 0 2

解决方案

问题出在移除控件后重新attach时,没有让Grid重新计算布局。虽然grid.query_child显示位置已更新,但Gtk的布局系统没有触发重排,导致UI不刷新。

方案1:触发Grid重新计算布局

queue_draw()仅负责重绘控件像素,不会触发布局计算。改用queue_resize()通知布局系统重新计算所有子控件的位置,从而更新UI。

修改拖拽结束回调的最后一行:

// 替换原有的 grid.queue_draw();
grid.queue_resize();

方案2:直接修改控件的Grid属性(推荐)

无需移除再重新添加Button,直接通过set_child_property更新控件在Grid中的列和行位置,布局系统会自动触发更新,这是更高效的方式:

修改拖拽结束回调中的代码,移除grid.remove(&button)和grid.attach(&button, x, y, 1, 1),替换为:

grid.set_child_property(&button, "column", x).unwrap();
grid.set_child_property(&button, "row", y).unwrap();

优化后的完整回调代码:

drag.connect_drag_end(clone!(
    #[weak]
    button,
    #[weak]
    grid,
    move |_, offset_x, offset_y| {
        let (c, r, _, _) = grid.query_child(&button);
        println!("origin {} {}", c, r);

        let x_movement = (offset_x / (CELL_SIZE + CELL_GAP) as f64) as i32;
        let y_movement = (offset_y / (CELL_SIZE + CELL_GAP) as f64) as i32;
        println!("offset {} {}", x_movement, y_movement);

        position.update(|(x, y)| (x + x_movement, y + y_movement));
        let (x, y) = position.get();
        println!("new {} {}", x, y);

        // 直接更新Grid中的位置属性
        grid.set_child_property(&button, "column", x).unwrap();
        grid.set_child_property(&button, "row", y).unwrap();

        let (c, r, _, _) = grid.query_child(&button);
        println!("result {} {}", c, r);
    }
));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 21:37:30