0

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"));
            }))
    }
}

我需要同时访问结构的stateheaderbar属性imp::Window,并修改 的属性project_pathstate调用set_subtitle. headerbar我已经尝试了各种变体,使用变量和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 将受到严重阻碍,但我不知道如何

4

1 回答 1

0

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

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

有了这个结构,您可以将它包装在一个中,Rc<RefCell<T>>以便其他线程和范围可以访问它(只是不是线程安全的/同时,您需要一个 Mutex 或 Arc):

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

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

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 messenger 客户端),他们似乎正在使用类似的解决方案。

于 2022-02-12T18:05:21.093 回答