如何在 GTK-rs 中的回调中访问状态和模板子项

How to access state and template children in callback in GTK-rs

老实说,gtk-rs 的教程和示例确实不完整且参差不齐,因此我试图拼凑出如何在按钮内修改应用程序的状态以及某些子元素的状态打回来。所以,简而言之,我有:

// ...
mod imp {
    pub struct Window {
        #[template_child]
        pub headerbar: TemplateChild<gtk::HeaderBar>,
        #[template_child]
        pub open_button: TemplateChild<gtk::Button>,

        // Internal state    
        pub state: Rc<RefCell<ScribeDownWindowState>>,
    }

    #[derive(Default)]
    pub struct ScribeDownWindowState {
        pub project_path: Option<String>,
    }
}

在这个结构的 ObjectImpl 中,我有 constructed 方法,它调用父构造方法,然后在父对象上调用 setup_callbacks,即 Window 类型实际上是 GTK 继承层次结构的一部分:

mod imp;
glib::wrapper! {
    pub struct Window(ObjectSubclass<imp::Window>)
        @extends gtk::ApplicationWindow, gtk::Window, gtk::Widget,
        @implements gio::ActionGroup, gio::ActionMap;
}

impl Window {
    pub fn new<P: glib::IsA<gtk::Application>>(app: &P) -> Self {
        glib::Object::new(&[("application", app)]).expect("Failed to create ScribeDownWindow")
    }

    fn setup_callbacks(&self) {
        let state = self.imp().state;
        let headerbar = Rc::new(&self.imp().headerbar);
        self.imp().open_button
            .connect_clicked(clone!(@strong state, @strong headerbar => move |_| {
                let s = state.borrow_mut();
                s.project_path = Some("fuck".to_string());
                headerbar.set_subtitle(Some("fuck"));
            }))
    }
}

我需要访问 imp::Window 结构的 stateheaderbar 属性,并修改 stateproject_path 属性 ] 并在 headerbar 上调用 set_subtitle。我已经尝试了各种变体,使用变量和 Rcs 和 RefCells 的所有组合,我似乎无法克服这个错误(或它的一些排列):

error[E0759]: `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
  --> src/scribedown_window/mod.rs:22:39
   |
20 |     fn setup_callbacks(&self) {
   |                        ----- this data with an anonymous lifetime `'_`...
21 |         let state = self.imp().state;
22 |         let headerbar = Rc::new(&self.imp().headerbar);
   |                                  ---- ^^^
   |                                  |
   |                                  ...is captured here...
23 |         self.imp().open_button.connect_clicked(
   |                                --------------- ...and is required to live as long as `'static` here

是一种完成我需要完成的工作的方法,如果你不能修改按钮点击回调中的任何其他界面对象,你的 UI会受到严重阻碍,但我看不出 如何

这个问题的解决方案是创建一个结构来保存 UI 状态和应用程序状态,如下所示:

pub struct App {
    pub window: crate::scribedown_window::Window,
    pub state: State,
    pub document_list_model: Option<document_list::Model>,
}

有了这个结构,您可以将它包装在 Rc<RefCell<T>> 中,以便其他线程和作用域可以访问它(只是不能同时 thread-safely/at,您需要一个 Mutex 或 Arc那个):

let scribedown = Rc::new(RefCell::new(app::App {
    window: win,
    state: app::State {
        project: None,
        open_files: vec![],
    },
    document_list_model: None,
}));

现在,您只需将指向此中央状态持有者的引用计数指针传递给您想要的所有回调,回调本身将使状态保持活动状态并保持对它的访问,同时如果多个回调也会强制崩溃尝试同时修改 RefCell。请注意,为了使其工作,所有设置应用程序 UI 回调的方法都需要传递引用计数状态变量 scribedown,因此它们不能是 [=17= 的方法] struct taking &self,因为那是没有用的 and 借用它。它们可以是静态方法:

app::App::connect_all(scribedown.clone());

然后,为了连接回调,每个回调都需要它自己的指向要使用的状态的指针,最重要的是,因为您不能将克隆的引用计数指针移出封闭范围,而且您不想将原始引用计数指针移出,您需要创建一个外部 RCP,然后用于克隆实际的 RCP 以进行回调。最终看起来像这样:

// NOTE: the outer pointers to `sd`, formatted like `sd_for_*`, are
// done in order to prevent the callback from borrowing the original
// pointer when it creates its own pointer, which we need to keep free
// to continue making more pointers. This happens because using
// something inside a `move` callback borrows it.

// Connect open button callback
let sd_for_button = sd.clone();
{
    let osd = sd.borrow();
    let button = &osd.window.imp().open_button;
    button.connect_clicked(move |_| {
        // Launch dialog in new thread so it doesn't hang this one
        gtk::glib::MainContext::default()
    .spawn_local(Self::open_project_dialog(Rc::clone(&sd_for_button)));
    });
}

我不确定这是对此的“官方”或惯用解决方案,但我查看了 Fractal(一个用 GTK-rs 用 Rust 编写的 Matrix 信使客户端)的源代码,它们似乎使用类似的解决方案。