.NET WPF Remember window size between sessions

48,858

Solution 1

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.

Solution 2

Actually you don't need to use code-behind to do that (except for saving the settings). You can use a custom markup extension to bind the window size and position to the settings like this :

<Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:WpfApplication1"
        Title="Window1"
        Height="{my:SettingBinding Height}"
        Width="{my:SettingBinding Width}"
        Left="{my:SettingBinding Left}"
        Top="{my:SettingBinding Top}">

You can find the code for this markup extension here : http://www.thomaslevesque.com/2008/11/18/wpf-binding-to-application-settings-using-a-markup-extension/

Solution 3

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.

Solution 4

The "long form" binding that Thomas posted above requires almost no coding, just make sure you have the namespace binding:

<Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:p="clr-namespace:WpfApplication1.Properties"
        Title="Window1"
        Height="{Binding Source={x:Static p:Settings.Default}, Path=Height, Mode=TwoWay}"
        Width="{Binding Source={x:Static p:Settings.Default}, Path=Width, Mode=TwoWay}"
        Left="{Binding Source={x:Static p:Settings.Default}, Path=Left, Mode=TwoWay}"
        Top="{Binding Source={x:Static p:Settings.Default}, Path=Top, Mode=TwoWay}">

Then to save on the code-behind:

private void frmMain_Closed(object sender, EventArgs e)
{
    Properties.Settings.Default.Save();
}

Solution 5

Alternatively, you might like the following approach too (see source). Add the WindowSettings class to your project and insert WindowSettings.Save="True" in your main window's header:

<Window x:Class="YOURPROJECT.Views.ShellView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Services="clr-namespace:YOURNAMESPACE.Services" 
    Services:WindowSettings.Save="True">

Where WindowSettings is defined as follows:

using System;
using System.ComponentModel;
using System.Configuration;
using System.Windows;

namespace YOURNAMESPACE.Services
{
/// <summary>
///   Persists a Window's Size, Location and WindowState to UserScopeSettings
/// </summary>
public class WindowSettings
{
    #region Fields

    /// <summary>
    ///   Register the "Save" attached property and the "OnSaveInvalidated" callback
    /// </summary>
    public static readonly DependencyProperty SaveProperty = DependencyProperty.RegisterAttached("Save", typeof (bool), typeof (WindowSettings), new FrameworkPropertyMetadata(OnSaveInvalidated));

    private readonly Window mWindow;

    private WindowApplicationSettings mWindowApplicationSettings;

    #endregion Fields

    #region Constructors

    public WindowSettings(Window pWindow) { mWindow = pWindow; }

    #endregion Constructors

    #region Properties

    [Browsable(false)] public WindowApplicationSettings Settings {
        get {
            if (mWindowApplicationSettings == null) mWindowApplicationSettings = CreateWindowApplicationSettingsInstance();
            return mWindowApplicationSettings;
        }
    }

    #endregion Properties

    #region Methods

    public static void SetSave(DependencyObject pDependencyObject, bool pEnabled) { pDependencyObject.SetValue(SaveProperty, pEnabled); }

    protected virtual WindowApplicationSettings CreateWindowApplicationSettingsInstance() { return new WindowApplicationSettings(this); }

    /// <summary>
    ///   Load the Window Size Location and State from the settings object
    /// </summary>
    protected virtual void LoadWindowState() {
        Settings.Reload();
        if (Settings.Location != Rect.Empty) {
            mWindow.Left = Settings.Location.Left;
            mWindow.Top = Settings.Location.Top;
            mWindow.Width = Settings.Location.Width;
            mWindow.Height = Settings.Location.Height;
        }
        if (Settings.WindowState != WindowState.Maximized) mWindow.WindowState = Settings.WindowState;
    }

    /// <summary>
    ///   Save the Window Size, Location and State to the settings object
    /// </summary>
    protected virtual void SaveWindowState() {
        Settings.WindowState = mWindow.WindowState;
        Settings.Location = mWindow.RestoreBounds;
        Settings.Save();
    }

    /// <summary>
    ///   Called when Save is changed on an object.
    /// </summary>
    private static void OnSaveInvalidated(DependencyObject pDependencyObject, DependencyPropertyChangedEventArgs pDependencyPropertyChangedEventArgs) {
        var window = pDependencyObject as Window;
        if (window != null)
            if ((bool) pDependencyPropertyChangedEventArgs.NewValue) {
                var settings = new WindowSettings(window);
                settings.Attach();
            }
    }

    private void Attach() {
        if (mWindow != null) {
            mWindow.Closing += WindowClosing;
            mWindow.Initialized += WindowInitialized;
            mWindow.Loaded += WindowLoaded;
        }
    }

    private void WindowClosing(object pSender, CancelEventArgs pCancelEventArgs) { SaveWindowState(); }

    private void WindowInitialized(object pSender, EventArgs pEventArgs) { LoadWindowState(); }

    private void WindowLoaded(object pSender, RoutedEventArgs pRoutedEventArgs) { if (Settings.WindowState == WindowState.Maximized) mWindow.WindowState = Settings.WindowState; }

    #endregion Methods

    #region Nested Types

    public class WindowApplicationSettings : ApplicationSettingsBase
    {
        #region Constructors

        public WindowApplicationSettings(WindowSettings pWindowSettings) { }

        #endregion Constructors

        #region Properties

        [UserScopedSetting] public Rect Location {
            get {
                if (this["Location"] != null) return ((Rect) this["Location"]);
                return Rect.Empty;
            }
            set { this["Location"] = value; }
        }

        [UserScopedSetting] public WindowState WindowState {
            get {
                if (this["WindowState"] != null) return (WindowState) this["WindowState"];
                return WindowState.Normal;
            }
            set { this["WindowState"] = value; }
        }

        #endregion Properties
    }

    #endregion Nested Types
}
}
Share:
48,858

Related videos on Youtube

Daniil Harik
Author by

Daniil Harik

Hei,

Updated on May 09, 2020

Comments

  • Daniil Harik
    Daniil Harik about 4 years

    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 SizeChanged event and save Height and Width, but I think there must be easier solution.

    Pretty simple problem, but I can not find easy solution to it.

    • Omer Raviv
      Omer Raviv almost 12 years
      Please note that if you're resoring both the size and the position (as most code samples below do), you'll want to handle the edge-case where someone unplugged the monitor that the window was last presented on, to avoid presenting your window off-screen.
    • Andrew Truckle
      Andrew Truckle almost 8 years
      @OmerRaviv Have you found an example taking the edge case in to account?
    • AelanY
      AelanY about 7 years
      I have too less repution to add a comment, hence I created this new awnser. I use the same solution as Lance Cleveland including the setting of RobJohnson, but it doesn't work if you use it for sub windows and want to open more of them at the same time...
  • aroon65
    aroon65 about 15 years
    What if the application is installed under Program Files and the user is running as non-admin?
  • ChrisF
    ChrisF about 15 years
    Sorry - I meant user.config which is stored under C:\Documents and Settings\[user]\Local Settings\Application Data where the [User] should have access rights. I'll update the answer.
  • Thomas Levesque
    Thomas Levesque about 15 years
    Actually, settings with scope "User" are not saved in the app.config file in Program Files, but in a user.config file in the user's application data directory. So it's not a problem...
  • ChrisF
    ChrisF about 15 years
    I realised the mistake in the original answer when I saw Kent's comment and corrected it - probably at the same time you were adding your comment!
  • longaster
    longaster over 14 years
    I like this answer more than the chosen accepted answer. Well done.
  • MartyIX
    MartyIX almost 14 years
    Actually you can add "WindowState" to settings. Select type -> browse -> PresentationFramework -> System.Windows -> WindowState :)
  • Thomas
    Thomas over 13 years
    FWIW, I do this from the size changed handler as well, in case of application crashes. They're rare with an unhandled exception processing, but why punish the user with lost size/location when they do mysteriously occur.
  • Matt DeKrey
    Matt DeKrey over 13 years
    +1 - I love the use of binding and extensions! If you add the WindowState to your bound settings, it provides the full capabilities. Alternatively, if you have the user settings available in the DataContext, you can use something like {Binding Settings.Height}, etc.
  • David Sykes
    David Sykes over 12 years
    I chose this solution, but only saved the settings if the window state was normal, otherwise it can be fiddly getting it out of maximised mode
  • Menefee
    Menefee over 12 years
    @MartyIX WindowState only manages maximize, minimized, or restored and does not address the size of window at all so I don't see how it is at all relavent.
  • Omer Raviv
    Omer Raviv almost 12 years
    There's a bug in this code in that, if the user opens the window on his/her second screen, then disconnects that screen from the computer, the next time they open the window, it will be presented off screen. If the window is modal, the user won't be able to interact with the app at all, and won't understand what's going on. You need to add a bounds check using Window.GetScreen(), after converting the screen coordinates to DPI dependant values.
  • ChrisF
    ChrisF almost 12 years
    @OmerRaviv - it's not a bug, but a limitation :) Seriously - I didn't address that aspect of the problem.
  • RobJohnson
    RobJohnson over 11 years
    +1 I used this too, @DavidSykes - Adding another setting for the window state seems to work well enough, e.g. WindowState="{Binding Source={x:Static properties:Settings.Default}, Path=WindowState, Mode=TwoWay}"
  • David Sykes
    David Sykes about 11 years
    @RobJohnson I tried your suggestion and it worked very well, thanks.
  • Vinicius Rocha
    Vinicius Rocha over 10 years
    This approach have an issue when the user close the application when the Window is Maximized.
  • Thomas Levesque
    Thomas Levesque over 10 years
    @Vinicius, can you elaborate? What is the issue exactly?
  • Vinicius Rocha
    Vinicius Rocha over 10 years
    Hi @ThomasLevesque. When you maximize the window the values of width, height, left and top changes. So, if you close the application and then reopen it, the application wont remember the width/height/top/left from when it was in normal mode.
  • Rafael Rivera
    Rafael Rivera about 10 years
    Let the OS do the heavy lifting... (see Andy's answer)
  • Gleb Sevruk
    Gleb Sevruk almost 10 years
    window.Intitialized should be window.Loaded see mostlytech.blogspot.com/2008/01/…
  • tster
    tster almost 10 years
    @Gleb, both work I think. Are you having problems with it on Initialized?
  • Gleb Sevruk
    Gleb Sevruk almost 10 years
    Yes, since maximized window will be on incorrect screen if you use only initialized event. What I've done and this seems to work: Now I'm subscribing to Loaded event also. I moved _window.WindowState = s.Maximized ? WindowState.Maximized : WindowState.Normal; line inside "Loaded" event handler. window.Initialized += InitializedHandler; window.Loaded += LoadedHandler; btw: I like this approach
  • PIntag
    PIntag over 9 years
    @Vinicius: I don't seem to have a problem with the app properly remembering width/height/top/left setting when closing the app while maximized.
  • Alan Baljeu
    Alan Baljeu about 9 years
    Not working. If maximized on screen 2 when closed, it opens maximized on screen 1 when started.
  • ChrisF
    ChrisF about 9 years
    @AlanBaljeu - you'll have to extend this to save which screen the application was closed on.
  • Piyush Soni
    Piyush Soni about 9 years
    But doesn't it still require us to write code behind (perhaps even more?) by writing a whole new extension/class?
  • Mark Bell
    Mark Bell over 8 years
    Nice solution. However I just discovered that GetWindowPlacement/SetWindowPlacement are not Aero Snap aware
  • Andrew Truckle
    Andrew Truckle almost 8 years
    What about when people have two monitors and thus it might have negative coordinates and then they change monitor configurations and the values are no longer valid?
  • CptObvious
    CptObvious almost 7 years
    Nice, thanks for this - I've used your code snippet in a new class to set up the state tracker with a path based on the program's name. From now on I only have to write one line and all the window properties are handled
  • KnorxThieus
    KnorxThieus over 6 years
    By the way, you must indeed not subscribe to Window_Closed instead of Window_Closing, although this might lead to a better user experience. For if you query RestoreBounds before the window has been shown or after it has been closed, Empty is returned., like MSDN says.
  • Kyle Delaney
    Kyle Delaney over 6 years
    To be clear, this works with both application and user settings, right? The link only says application settings.
  • Kyle Delaney
    Kyle Delaney over 6 years
    Since File Explorer displays this behavior, I was wondering if there's some automatic way of getting a window to do this, but I guess not.
  • Stéphane Gourichon
    Stéphane Gourichon about 6 years
    @RandomEngy has posted an improved answer based on this.
  • Apfelkuacha
    Apfelkuacha over 5 years
    RestoreBounds could also be used when the Window isn't maximized, so the if-else is not necessary
  • zar
    zar over 3 years
    I downloaded your sample from blog and that works and I am doing identical in a new application and somehow that doesn't work. Any idea if we need to do something more?
  • 曾其威
    曾其威 almost 3 years
    I save on Settings.Default.PropertyChanged += (s, e) => Settings.Default.Save(), and add some delay WindowState="{Binding MainWindow_WindowState, Source={x:Static properties:Settings.Default}, Delay=250, Mode=TwoWay}" so it saves some SSD.
  • Urs Meili
    Urs Meili over 2 years
    this package works great, and it's also handling minimized windows and disconnected monitors correctly. IMHO This should be the accepted answer.
  • Matt Varblow
    Matt Varblow about 2 years
    Definitely check out the github page. This solution includes support for multiple monitors: github.com/anakic/Jot#real-world-formwindow-tracking
  • Duncan Groenewald
    Duncan Groenewald about 2 years
    Best one yet - just need to call RestoreSizeAndLocation() from MainWindow() and after InitialiseComponent() otherwise there is a visible jump.