如何在GtkGrid中移动小部件?

编程语言 2026-07-09

我现在在用gtk-rs编写一些Gtk程序。我试图通过删除并重新附着来移动网格中的控件,但在重新附着子控件时,界面上它根本没有移动,尽管从网格中查询位置时位置已经改变。

gtk = { version = "0.11.3", package = "gtk4", features = ["v4_22"] }
gtk-layer-shell = { version = "0.8.0", package = "gtk4-layer-shell" }
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

仍然停留在左上角,但从网格查询到的原始位置是 (0,1),所以重新附着可能是起作用的。如何才能让它真正工作?

解决方案

在构建网格时添加 column_homogeneous (true)row_homogeneous (true)

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

默认情况下,网格中的每一行和每一列都有各自的大小。由于其他行和列都是空的,这些大小被设为零,因此按钮总是出现在左上角,尽管它之前的行和列可能是零尺寸。

*_homogeneous 设置为 true 可以让所有行和列即使为空时也具有相同的大小。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章