.NET WPF Remember window size between sessions

后端 未结 12 905
悲哀的现实
悲哀的现实 2020-11-30 20:33

Basically when user resizes my application\'s window I want application to be same size when application is re-opened again.

At first I though of handling SizeChange

相关标签:
12条回答
  • 2020-11-30 20:34

    I wrote a quick class which does this. Here is how it's called:

        public MainWindow()
        {
            FormSizeSaver.RegisterForm(this, () => Settings.Default.MainWindowSettings,
                                       s =>
                                       {
                                           Settings.Default.MainWindowSettings = s;
                                           Settings.Default.Save();
                                       });
            InitializeComponent();
            ...
    

    And here is the code:

    public class FormSizeSaver
    {
        private readonly Window window;
        private readonly Func<FormSizeSaverSettings> getSetting;
        private readonly Action<FormSizeSaverSettings> saveSetting;
        private FormSizeSaver(Window window, Func<string> getSetting, Action<string> saveSetting)
        {
            this.window = window;
            this.getSetting = () => FormSizeSaverSettings.FromString(getSetting());
            this.saveSetting = s => saveSetting(s.ToString());
    
            window.Initialized += InitializedHandler;
            window.StateChanged += StateChangedHandler;
            window.SizeChanged += SizeChangedHandler;
            window.LocationChanged += LocationChangedHandler;
        }
    
        public static FormSizeSaver RegisterForm(Window window, Func<string> getSetting, Action<string> saveSetting)
        {
            return new FormSizeSaver(window, getSetting, saveSetting);
        }
    
    
        private void SizeChangedHandler(object sender, SizeChangedEventArgs e)
        {
            var s = getSetting();
            s.Height = e.NewSize.Height;
            s.Width = e.NewSize.Width;
            saveSetting(s);
        }
    
        private void StateChangedHandler(object sender, EventArgs e)
        {
            var s = getSetting();
            if (window.WindowState == WindowState.Maximized)
            {
                if (!s.Maximized)
                {
                    s.Maximized = true;
                    saveSetting(s);
                }
            }
            else if (window.WindowState == WindowState.Normal)
            {
                if (s.Maximized)
                {
                    s.Maximized = false;
                    saveSetting(s);
                }
            }
        }
    
        private void InitializedHandler(object sender, EventArgs e)
        {
            var s = getSetting();
            window.WindowState = s.Maximized ? WindowState.Maximized : WindowState.Normal;
    
            if (s.Height != 0 && s.Width != 0)
            {
                window.Height = s.Height;
                window.Width = s.Width;
                window.WindowStartupLocation = WindowStartupLocation.Manual;
                window.Left = s.XLoc;
                window.Top = s.YLoc;
            }
        }
    
        private void LocationChangedHandler(object sender, EventArgs e)
        {
            var s = getSetting();
            s.XLoc = window.Left;
            s.YLoc = window.Top;
            saveSetting(s);
        }
    }
    
    [Serializable]
    internal class FormSizeSaverSettings
    {
        public double Height, Width, YLoc, XLoc;
        public bool Maximized;
    
        public override string ToString()
        {
            using (var ms = new MemoryStream())
            {
                var bf = new BinaryFormatter();
                bf.Serialize(ms, this);
                ms.Position = 0;
                byte[] buffer = new byte[(int)ms.Length];
                ms.Read(buffer, 0, buffer.Length);
                return Convert.ToBase64String(buffer);
            }
        }
    
        internal static FormSizeSaverSettings FromString(string value)
        {
            try
            {
                using (var ms = new MemoryStream(Convert.FromBase64String(value)))
                {
                    var bf = new BinaryFormatter();
                    return (FormSizeSaverSettings) bf.Deserialize(ms);
                }
            }
            catch (Exception)
            {
                return new FormSizeSaverSettings();
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-30 20:37

    The default way of solving it is to use settings files. The problem with settings files is that you have to define all the settings and write the code that copies data back and forth yourself. Quite tedious if you have a lot of properties to keep track of.

    I made a pretty flexible and very easy to use library for this, you just tell it which properties of which object to track and it does the rest. You can configure the crap out of it too if you like.

    The library is called Jot (github), here is an old CodeProject article I wrote about it.

    Here's how you'd use it to keep track of a window's size and location:

    public MainWindow()
    {
        InitializeComponent();
    
        _stateTracker.Configure(this)
            .IdentifyAs("MyMainWindow")
            .AddProperties(nameof(Height), nameof(Width), nameof(Left), nameof(Top), nameof(WindowState))
            .RegisterPersistTrigger(nameof(Closed))
            .Apply();
    }
    

    Jot vs. settings files: With Jot there's considerably less code, and it's a lot less error prone since you only need to mention each property once. With settings files you need to mention each property 5 times: once when you explicitly create the property and an additional four times in the code that copies the values back and forth.

    Storage, serialization etc are completely configurable. Also, when using IOC, you can even hook it up so that it applies tracking automatically to all objects it resolves so that all you need to do to make a property persistent is slap a [Trackable] attribute on it.

    I'm writing all this because I think the library is top notch and I want to mouth off about it.

    0 讨论(0)
  • 2020-11-30 20:40

    There's a NuGet Project RestoreWindowPlace see on github that does all this for you, saving the information in an XML file.

    To get it to work on a window, it's as simple as calling:

    ((App)Application.Current).WindowPlace.Register(this);

    In App you create the class that manages your windows. See the github link above for more information.

    0 讨论(0)
  • 2020-11-30 20:40

    You might like this:

    public class WindowStateHelper
    {
        public static string ToXml(System.Windows.Window win)
        {
            XElement bounds = new XElement("Bounds");
            if (win.WindowState == System.Windows.WindowState.Maximized)
            {
                bounds.Add(new XElement("Top", win.RestoreBounds.Top));
                bounds.Add(new XElement("Left", win.RestoreBounds.Left));
                bounds.Add(new XElement("Height", win.RestoreBounds.Height));
                bounds.Add(new XElement("Width", win.RestoreBounds.Width));
            }
            else
            {
                bounds.Add(new XElement("Top", win.Top));
                bounds.Add(new XElement("Left", win.Left));
                bounds.Add(new XElement("Height", win.Height));
                bounds.Add(new XElement("Width", win.Width));
            }
            XElement root = new XElement("WindowState",
                new XElement("State", win.WindowState.ToString()),
                new XElement("Visibility", win.Visibility.ToString()),
                bounds);
    
            return root.ToString();
        }
    
        public static void FromXml(string xml, System.Windows.Window win)
        {
            try
            {
                XElement root = XElement.Parse(xml);
                string state = root.Descendants("State").FirstOrDefault().Value;
                win.WindowState = (System.Windows.WindowState)Enum.Parse(typeof(System.Windows.WindowState), state);
    
                state = root.Descendants("Visibility").FirstOrDefault().Value;
                win.Visibility = (System.Windows.Visibility)Enum.Parse(typeof(System.Windows.Visibility), state);
    
                XElement bounds = root.Descendants("Bounds").FirstOrDefault();
                win.Top = Convert.ToDouble(bounds.Element("Top").Value);
                win.Left = Convert.ToDouble(bounds.Element("Left").Value);
                win.Height = Convert.ToDouble(bounds.Element("Height").Value);
                win.Width = Convert.ToDouble(bounds.Element("Width").Value);
            }
            catch (Exception x)
            {
                System.Console.WriteLine(x.ToString());
            }
        }
    }
    

    When the app closes:

            Properties.Settings.Default.Win1Placement = WindowStateHelper.ToXml(win1);
            Properties.Settings.Default.Win2Placement = WindowStateHelper.ToXml(win2);
            ...
    

    When the app starts:

            WindowStateHelper.FromXml(Properties.Settings.Default.Win1Placement, win1);
            WindowStateHelper.FromXml(Properties.Settings.Default.Win2Placement, win2);
            ...
    
    0 讨论(0)
  • 2020-11-30 20:43

    Save the values in the user.config file.

    You'll need to create the value in the settings file - it should be in the Properties folder. Create five values:

    • Top of type double
    • Left of type double
    • Height of type double
    • Width of type double
    • Maximized of type bool - to hold whether the window is maximized or not. If you want to store more information then a different type or structure will be needed.

    Initialise the first two to 0 and the second two to the default size of your application, and the last one to false.

    Create a Window_OnSourceInitialized event handler and add the following:

    this.Top = Properties.Settings.Default.Top;
    this.Left = Properties.Settings.Default.Left;
    this.Height = Properties.Settings.Default.Height;
    this.Width = Properties.Settings.Default.Width;
    // Very quick and dirty - but it does the job
    if (Properties.Settings.Default.Maximized)
    {
        WindowState = WindowState.Maximized;
    }
    

    NOTE: The set window placement needs to go in the on source initialised event of the window not the constructor, otherwise if you have the window maximised on a second monitor, it will always restart maximised on the primary monitor and you won't be able to access it.

    Create a Window_Closing event handler and add the following:

    if (WindowState == WindowState.Maximized)
    {
        // Use the RestoreBounds as the current values will be 0, 0 and the size of the screen
        Properties.Settings.Default.Top = RestoreBounds.Top;
        Properties.Settings.Default.Left = RestoreBounds.Left;
        Properties.Settings.Default.Height = RestoreBounds.Height;
        Properties.Settings.Default.Width = RestoreBounds.Width;
        Properties.Settings.Default.Maximized = true;
    }
    else
    {
        Properties.Settings.Default.Top = this.Top;
        Properties.Settings.Default.Left = this.Left;
        Properties.Settings.Default.Height = this.Height;
        Properties.Settings.Default.Width = this.Width;
        Properties.Settings.Default.Maximized = false;
    }
    
    Properties.Settings.Default.Save();
    

    This will fail if the user makes the display area smaller - either by disconnecting a screen or changing the screen resolution - while the application is closed so you should add a check that the desired location and size is still valid before applying the values.

    0 讨论(0)
  • 2020-11-30 20:44

    While you can "roll your own" and manually save the settings somewhere, and in general it will work, it is very easy to not handle all of the cases correctly. It is much better to let the OS do the work for you, by calling GetWindowPlacement() at exit and SetWindowPlacement() at startup. It handles all of the crazy edge cases that can occur (multiple monitors, save the normal size of the window if it is closed while maximized, etc.) so that you don't have to.

    This MSDN Sample shows how to use these with a WPF app. The sample isn't perfect (the window will start in the upper left corner as small as possible on first run, and there is some odd behavior with the Settings designer saving a value of type WINDOWPLACEMENT), but it should at least get you started.

    0 讨论(0)
提交回复
热议问题