gtk4/rust 从 ListView 中删除 EditableLabel 时断言失败

gtk4/rust failed assertion when removing EditableLabel from ListView

在下面的 gtk4 代码中,我试图通过删除对应的 StringObject 从显示中删除 EditableLabel 。这应该在 connect_editing_notify 回调指定的编辑完成后发生。

如果我 运行 程序并使用 return 键退出编辑模式,那么它会按预期工作:标签消失。但是,如果我通过选择不同的标签退出编辑模式,那么我会得到一个重复的控制台打印,说明断言失败:

(viewdb:13773): Gtk-CRITICAL **: 17:35:26.754: gtk_widget_get_parent: assertion 'GTK_IS_WIDGET (widget)' failed

(viewdb:13773): Gtk-CRITICAL **: 17:35:26.754: gtk_widget_get_parent: assertion 'GTK_IS_WIDGET (widget)' failed

(viewdb:13773): Gtk-CRITICAL **: 17:35:26.754: gtk_widget_get_parent: assertion 'GTK_IS_WIDGET (widget)' failed

如何在编辑后彻底删除 EditableLabel?

use gtk::glib::clone;
use gtk::prelude::*;

use gtk::{
    Application, ApplicationWindow, EditableLabel, ListView, SignalListItemFactory,
    SingleSelection, StringList, StringObject, Widget,
};

fn build_ui(app: &Application) {
    let model: StringList = vec!["One", "Two", "Three", "Four"].into_iter().collect();

    let selection_model = SingleSelection::new(Some(&model));

    let factory = SignalListItemFactory::new();
    factory.connect_setup(move |_, listitem| {
        let label = EditableLabel::builder().build();

        listitem.set_child(Some(&label));

        listitem
            .property_expression("item")
            .chain_property::<StringObject>("string")
            .bind(&label, "text", Widget::NONE);

        label.connect_editing_notify(
            clone!( @strong model, @strong listitem => move |lbl: &EditableLabel| {
                if !lbl.is_editing() {
                    let position = listitem.position();
                    model.remove(position); // remove model entry cooresponding to edit.
                }
            }),
        );
    });

    let view = ListView::new(Some(&selection_model), Some(&factory));

    let window = ApplicationWindow::builder()
        .application(app)
        .child(&view)
        .build();

    window.present();
}

fn main() {
    let app = Application::builder().build();
    app.connect_activate(build_ui);
    app.run();
}

在项目仍在处理某些事件时从其父项目中删除项目有时会导致问题。

简单的解决方法是在延迟函数中删除。在 C 语言中,用 g_idle_add() 来完成这对正确使用来说很麻烦。但在 Rust 中是 super-easy:

gtk::glib::source::idle_add_local_once(
    clone!(@strong model => 
        move || model.remove(position)
    )
);

但请注意,由于 idle 函数是 运行 未来某个未指定的时间,因此 position 的值(一个简单的索引在列表)当回调为 运行 时将引用不同的项目。

我没有测试它,但我认为将 listitem 本身保留在回调中更明智:

gtk::glib::source::idle_add_local_once(
    clone!(@strong model, @weak listitem => 
        move || {
            let position = listitem.position();
            if position != INVALID_LIST_POSITION {
                model.remove(position)
            }
        }
    )
);

弱克隆将确保只有在项目仍然存在时才会调用它。