保存窗口状态

很多时候,我们希望窗口状态在会话之间持续存在。 如果用户调整窗口大小或将其最大化,他们可能会希望下次打开应用程序时,窗口仍保持同样的状态。 GTK 并没有提供这种功能,但幸运的是,手动实现它并不难。 我们基本上需要两个整数(高度 height 和 宽度 width) 和一个布尔值(is_maximized)来持久化。 我们已经知道如何通过使用 gio::Settings 来做到这一点。

文件名:listings/saving_window_state/1/org.gtk_rs.SavingWindowState1.gschema.xml

<?xml version="1.0" encoding="utf-8"?>
<schemalist>
  <schema id="org.gtk_rs.SavingWindowState1" path="/org/gtk_rs/SavingWindowState1/">
    <key name="window-width" type="i">
      <default>-1</default>
      <summary>Default window width</summary>
    </key>
    <key name="window-height" type="i">
      <default>-1</default>
      <summary>Default window height</summary>
    </key>
    <key name="is-maximized" type="b">
      <default>false</default>
      <summary>Default window maximized behaviour</summary>
    </key>
  </schema>
</schemalist>

由于我们不关心中间状态,因此只在构建窗口时加载窗口状态,并在关闭窗口时保存窗口状态。 这可以通过创建自定义窗口来实现。 首先,我们创建一个窗口,并添加用于访问设置和窗口状态的便捷方法。

文件名:listings/saving_window_state/1/custom_window/mod.rs

mod imp;

use gio::Settings;
use glib::Object;
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::{gio, glib, Application};

use crate::APP_ID;

glib::wrapper! {
    pub struct Window(ObjectSubclass<imp::Window>)
        @extends gtk::ApplicationWindow, gtk::Window, gtk::Widget,
        @implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable,
                    gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager;
}

impl Window {
    pub fn new(app: &Application) -> Self {
        // Create new window
        Object::builder().property("application", app).build()
    }

    fn setup_settings(&self) {
        let settings = Settings::new(APP_ID);
        self.imp()
            .settings
            .set(settings)
            .expect("`settings` should not be set before calling `setup_settings`.");
    }

    fn settings(&self) -> &Settings {
        self.imp()
            .settings
            .get()
            .expect("`settings` should be set in `setup_settings`.")
    }

    pub fn save_window_size(&self) -> Result<(), glib::BoolError> {
        // Get the size of the window
        let size = self.default_size();

        // Set the window state in `settings`
        self.settings().set_int("window-width", size.0)?;
        self.settings().set_int("window-height", size.1)?;
        self.settings()
            .set_boolean("is-maximized", self.is_maximized())?;

        Ok(())
    }

    fn load_window_size(&self) {
        // Get the window state from `settings`
        let width = self.settings().int("window-width");
        let height = self.settings().int("window-height");
        let is_maximized = self.settings().boolean("is-maximized");

        // Set the size of the window
        self.set_default_size(width, height);

        // If the window was maximized when it was closed, maximize it again
        if is_maximized {
            self.maximize();
        }
    }
}

我们通过将 "application" 属性传递给 glib::Object::new 来设置该属性。 您甚至可以用这种方法设置多个属性。 在创建新的 GObject 时,这比手动调用 setter 方法要好得多。

实现的结构包含 settings. 您可以看到,我们将 settings 嵌入到 std::cell::OnceCell 中。 当你知道只需初始化一次值时,这是 RefCell<Option<T>> 的一个很好的替代方法。 我们还重写了 constructedclose_request 方法,通过它们来加载或保存窗口状态。

文件名:listings/saving_window_state/1/custom_window/imp.rs

use gio::Settings;
use gtk::subclass::prelude::*;
use gtk::{gio, glib, ApplicationWindow};
use std::cell::OnceCell;

#[derive(Default)]
pub struct Window {
    pub settings: OnceCell<Settings>,
}

#[glib::object_subclass]
impl ObjectSubclass for Window {
    const NAME: &'static str = "MyGtkAppWindow";
    type Type = super::Window;
    type ParentType = ApplicationWindow;
}
impl ObjectImpl for Window {
    fn constructed(&self) {
        self.parent_constructed();
        // Load latest window state
        let obj = self.obj();
        obj.setup_settings();
        obj.load_window_size();
    }
}
impl WidgetImpl for Window {}
impl WindowImpl for Window {
    // Save window state right before the window will be closed
    fn close_request(&self) -> glib::Propagation {
        // Save window size
        self.obj()
            .save_window_size()
            .expect("Failed to save window state");
        // Allow to invoke other event handlers
        glib::Propagation::Proceed
    }
}
impl ApplicationWindowImpl for Window {}

就是这样! 现在,我们的窗口可以在应用程序会话之间保持状态。