Click here to Skip to main content
Click here to Skip to main content

Save and restore the state of a Windows Form in .NET 2.0

By , 19 May 2006
 

Introduction

The class saves the state (location, size and windows state) of a form to registry (under HKEY_CURRENT_USER) when the form is closed and restores the form state when it is loaded. (To work with .NET 1.x one need to use some other events and a couple of methods are new to .NET 2.0).

It will also work with multi screen desktops. The key here is to use Form.DesktopBounds and not Form.Location and Form.Size. Also i case of screens not always being there I use Screen.GetBounds() to check and if needed move form to an existing screen.

To use just include the following line in the constructor of your Form:

FormState formState = new FormState(this, "SampleApp");

Also got some methods for storing and getting other settings regarding the form in registry (SaveValue(), GetValue(), GetIntValue()). Use these in the FormClosed and Load event handlers store and restore whatever... (need to keep the FormState object as a variable of your form class in this case).

The class:

using System;
using System.Drawing;
using Microsoft.Win32;
using System.Windows.Forms;

/// <summary>
/// Create an instance of this class in the constructor of a form. 
/// Will save and restore window state, size, and position.
/// Uses DesktopBounds (instead of just Form.Location/Size) 
/// to place window correctly on a multi screen desktop.
/// </summary>
class FormState
{
  private Form _parent;
  private string _registry_key;

  /// <summary>
  /// Initializes an instance of the FormState class.
  /// </summary>
  /// <param name="parent">
  /// The form to store settings for.
  /// </param>
  /// <param name="sub_key">
  /// Registry path from HKEY_CURRENT_USER to place for storing settings.
  /// Will create a subkey named "FormState".
  /// </param>
  public FormState(Form parent, string subkey)
  { 
    this._parent = parent;
    this._registry_key = subkey + "\\FormState";
    this._parent.Load += new EventHandler(On_Load);
    this._parent.FormClosed += new FormClosedEventHandler(On_FormClosed);
  }

  public void SaveValue(string name, object value)
  {
    this.RegKey.SetValue(name, value);
  }

  public object GetValue(string name, object default_value)
  {
    return this.RegKey.GetValue(name, default_value);
  }

  /// <summary>
  /// If for some reason the value stored in reg cannot be parsed to int 
  /// the default_value is returned.
  /// </summary>
  public int GetIntValue(string name, int default_value)
  {
    int val = default_value;
    if (!int.TryParse(this.RegKey.GetValue(name, default_value).ToString(), out val))
      val = default_value;
    return val;
  }

  private RegistryKey RegKey
  { 
    get 
    { 
      return Registry.CurrentUser.CreateSubKey(
        this._registry_key + "\\" + this._parent.Name); 
    } 
  }

  private void On_Load(object sender, EventArgs e)
  {
    int X, Y, width, height, window_state;

    // place to get settings from
    RegistryKey key = this.RegKey;

    if (!int.TryParse(key.GetValue("DesktopBounds.Width", 
      this._parent.DesktopBounds.Width).ToString(), 
      out width))
      width = this._parent.DesktopBounds.Width;
    if (!int.TryParse(key.GetValue("DesktopBounds.Height", 
      this._parent.DesktopBounds.Height).ToString(), 
      out height))
      height = this._parent.DesktopBounds.Height;
    if (!int.TryParse(key.GetValue("DesktopBounds.X", 
      this._parent.DesktopBounds.X).ToString(), 
      out X))
      X = this._parent.DesktopBounds.X;
    if (!int.TryParse(key.GetValue("DesktopBounds.Y", 
      this._parent.DesktopBounds.Y).ToString(), 
      out Y))
      Y = this._parent.DesktopBounds.Y;

    // In case of multi screen desktops, check if we got the
    // screen the form was when closed.
    // If not there we put it in upper left corner of nearest 
    // screen.
    // We don't bother checking size (as long as the user see
    // the form ...).
    Rectangle screen_bounds = Screen.GetBounds(new Point(X, Y));
    if (X > screen_bounds.X + screen_bounds.Width)
    {
      X = screen_bounds.X;
      Y = screen_bounds.Y;
    }

    this._parent.DesktopBounds = new Rectangle(X, Y, width, height);
    
    if (!int.TryParse(key.GetValue("WindowState", 
      (int)this._parent.WindowState).ToString(), 
      out window_state))
      window_state = (int)this._parent.WindowState;
    
    this._parent.WindowState = (FormWindowState)window_state;
  }

  private void On_FormClosed(object sender, FormClosedEventArgs e)
  {
    // There may be cases where the event is raised twice.
    // To avoid handling it twice we remove the handler.
    this._parent.FormClosed -= new FormClosedEventHandler(On_FormClosed);
      // TODO: find out why it is raised twice ...

    // place to store settings
    RegistryKey key = this.RegKey;

    // save window state
    key.SetValue("WindowState", (int)this._parent.WindowState);
    
    // save pos & size in normal window state
    if (this._parent.WindowState != FormWindowState.Normal)
      this._parent.WindowState = FormWindowState.Normal;
    key.SetValue("DesktopBounds.Y", this._parent.DesktopBounds.Y);
    key.SetValue("DesktopBounds.X", this._parent.DesktopBounds.X);
    key.SetValue("DesktopBounds.Width", this._parent.DesktopBounds.Width);
    key.SetValue("DesktopBounds.Height", this._parent.DesktopBounds.Height);
  }
}

A note on the FormClosed event. When the main form (used in Application.Run()) is closed, the application quits and all forms created after the main form was created are also closed. One would think that the FormClosing and FormClosed event would be raised for all forms. This does not seem to be the case.

I solve this by using the following in the main form:

private void On_FormClosing(object sender, FormClosingEventArgs e)
{
  this.FormClosing -= new FormClosingEventHandler(On_FormClosing);
  Application.Exit(e);
}

private void On_FormClosed(object sender, FormClosedEventArgs e)
{
  this.FormClosed -= new FormClosedEventHandler(On_FormClosed);
}
in the FormClosing event for the main form of the application. This raises the FormClosing and FormClosed event for all open forms.

Passing the FormClosingEventArgs param to Application.Exit() gives the other forms a chance to cancel quitting the application.

Removing the FormClosing and FormClosed delegates prevents the methods from being called a second time (as Application.Exit() will raise the events for the main form as well as the others).


This class was created after having used various laboursome methods over the years and finally getting tired of it.

Various articles and blogs on the internet including an article ("Saving and Restoring the Location, Size and Windows State of a .NET Form") by Joel Matthias here on the Code Project was the inspiration for this class.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Stein-Tore Erdal
Norway Norway
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralRetaining the order in which the windows are openedmemberjuhi0923 Sep '08 - 19:59 
Hi,
 
I am saving all the settings of the windows (size,posotion,loaction) in an xml file and then restoring it back.
However I ran into an issue coz teh order in which teh windows is saved is not getting saved.
Say suppose there are three windows and they overlap partially, the window opened in the last should be on the top of of it and the one
opened first should be the lowest and the second in between.
Please suggest how to implement the same.
I am using C# .Net 2005
 
Thanks & Regards,
Smita
 
"There is no substitue for Hardwork"

GeneralAn example using My.SettingsmemberHolm7630 Jul '07 - 22:38 
Hi
 
I found your example searching for a way to save WindowState. I like your example very much. In fact so much that I based my own code from your example and extented it.
 
All you have to do to use my code is to create the 5 My.Settings variables and create an instance of FormState in your main form. Everything then happens automatically heron after. This works for the main form only. If you create more instances for FormState in other subforms those forms will overwrite the location and state data for the main form. So please bear that in mind if you use it.
 
Ive updated the routine that checks if the window is at a valid location. If it isnt it is moved onto the closest screen. Instead of using GetBounds Ive used GetWorkingArea. GetWorkingArea returns a regtangle where the taskbar and other docked apps like the sidebar on vista is subtracted. I find that if a program should be moved it should be moved completly into view without cover from said taskbar or sidebar or another docked tool.
 
I also added a few other events so it can control and restore correctly after shutdown from minimized state.
 
Here is the VB.NET code:
 

Public Class FormState
 
Private parent As Form
 
Public Sub New(ByVal parent As Form)
Me.parent = parent
AddHandler parent.Load, AddressOf Load
AddHandler parent.ResizeEnd, AddressOf ResizeEnd
AddHandler parent.FormClosed, AddressOf FormClosed
End Sub
 
Private Sub Load(ByVal sender As Object, ByVal e As EventArgs)
 
Dim X As Integer = My.Settings.WindowLocationX
Dim Y As Integer = My.Settings.WindowLocationY
Dim width As Integer = My.Settings.WindowWidth
Dim height As Integer = My.Settings.WindowHeight
Dim windowState As Integer = My.Settings.WindowState
 
Dim screenBounds As Rectangle = Screen.GetWorkingArea(New Point(X, Y))
 
If X > screenBounds.X + screenBounds.Width Then
X = screenBounds.X + screenBounds.Width - width
ElseIf X + width < screenBounds.X Then
X = screenBounds.X
End If
 
If Y > screenBounds.Y + screenBounds.Height Then
Y = screenBounds.Y + screenBounds.Height - height
ElseIf Y < screenBounds.Y Then
Y = screenBounds.Y
End If
 
Me.parent.DesktopBounds = New Rectangle(X, Y, width, height)
Me.parent.WindowState = windowState
 
AddHandler parent.ClientSizeChanged, AddressOf ClientSizeChanged
End Sub
 
Private Sub ResizeEnd(ByVal sender As Object, ByVal e As EventArgs)
My.Settings.WindowLocationX = Me.parent.DesktopBounds.X
My.Settings.WindowLocationY = Me.parent.DesktopBounds.Y
My.Settings.WindowWidth = Me.parent.DesktopBounds.Width
My.Settings.WindowHeight = Me.parent.DesktopBounds.Height
End Sub
 
Private Sub ClientSizeChanged(ByVal sender As Object, ByVal e As EventArgs)
If Not Me.parent.WindowState = FormWindowState.Minimized Then
My.Settings.WindowState = Me.parent.WindowState
End If
End Sub
 
Private Sub FormClosed(ByVal sender As Object, ByVal e As FormClosedEventArgs)
If Not Me.parent.WindowState = FormWindowState.Minimized Then
My.Settings.WindowState = Me.parent.WindowState
End If
If Me.parent.WindowState = FormWindowState.Normal Then
My.Settings.WindowLocationX = Me.parent.DesktopBounds.X
My.Settings.WindowLocationY = Me.parent.DesktopBounds.Y
My.Settings.WindowWidth = Me.parent.DesktopBounds.Width
My.Settings.WindowHeight = Me.parent.DesktopBounds.Height
End If
End Sub
 
End Class

 
Again all you have to do to use this is create the five My.Settings variables and create an instance of the class in your main form in your decleration area.
 
Private formState As New FormState(Me)

QuestionReloading panel contentsmemberdeb198215 Apr '07 - 17:47 
Hi,
 
I have a panel inside a form where user can dynamically drag and drop other controls such as picturebox, button etc during runtime. I am trying to save all the contents and their relative locations, so that users can start from where they left off. Currently I am saving in a text file the type of control that was drag dropped, any change made to their name or other properties and location. So next time the application is run I reload the text file and recreate all the controls again and set their properties as mentioned in the text file. I was wondering if it is possible to save all these controls as an object (along with their properties) and when I reload these objects it automatically recreates itself (so that I dont need to create them through my code and then set their properties)?
thanks in advance,
cheers,
Debo
GeneralRestore minimized formmemberjodvova17 Aug '06 - 0:27 
Minimize the form and save its state.
 
Restore the state the form. It will appear minimized. Click "Restore Down" caption button, and you will see the form didn't restore its original size (height, width).

GeneralUnsubscribing Closing/Closed event handlersmemberDelta-Z5 Aug '06 - 4:28 
Inside FormClosing event handler you unsubscribe it so it does not get called again from Application.Exit. But if one of the child forms actually cancels the exit, don't you have to subscribe it again?
 
I can suggest two ways for dealing with that:
1) Checking for e.Cancel right after Application.Exit(e), and resubscribing if this is the case.
2) If you can commit not to use Application.Exit anywhere else - check e.CloseReason on entry.
Questionwhy using the registry?membermayafit20 May '06 - 5:35 
why do you use the registry as the storage place ? you can place it as a user setting , or in an app.config file , or just in some ini file. (the first option is the preferable).
AnswerRe: why using the registry?memberXaroth20 May '06 - 11:54 
I agree, with .NET 2.0 you have the ability to use Application Settings with the same ease as using Resources
GeneralRe: why using the registry?memberStein-Tore Erdal20 May '06 - 22:56 
Simple answer: Using Registry works and it requires one line of code to set or get a value.
 
Application Settings in .NET 2.0 is a whole other matter and requires knowing the matter and I haven't had time to play much with it yet, only enough to understand there is a lot learn before using it.
 
There is a lot of new stuff in VS2005/.NET 2.0 that is supposed to make things easier, a lot of configuration instead of programming, and some of this is brilliant, sadly a lot is "time-traps" and some plainly don't work.
 
It took me <1 minute to create the code for using the Registry, then an hour playing with Application Settings realizing I needed a lot more before possibly using it with confidence...

GeneralRe: why using the registry?memberSyed Moshiur Murshed21 May '06 - 1:40 
A good Question and Even a better Answer. I agree with the auther (Stein). Setting and config reading and writing need some exparties and time to write where as it really seems easy with registry.
Nice article. Thanks
 
-Syed Moshiur Murshed
GeneralRe: why using the registry?memberStein-Tore Erdal21 May '06 - 2:34 

To simplify even more:
 
Change the class a bit:
class FormState
{
  private Form _parent;
  private RegistryKey _reg_key;
  public FormState(Form parent)
  { 
    this._parent = parent;
    this._reg_key 
      = Application.UserAppDataRegistry.CreateSubKey(
        "FormState\\\\" + this._form.Name)
...
+ change the rest of the class so it uses _reg_key.
 
Now there is no need to concern oneself with the storage issue at all (given that user can read/write to CurrentUser\Software\CompanyName\ProductName\ProductVersion.
 

GeneralRe: why using the registry?membermayafit6 Sep '06 - 20:02 
I Strongly believe that avoiding the registry is good practice.The registry has a specific porpuse and writing form state to it is malusage. Yuo have a lot of other possibilities like the one i have mentioned before , or just writing to an XML file.things that you write to the registry should be cleaned afterwards properly and you should take extra care not to "garbage" or damage it. If you write to a file you can always delete it and no harm is done
GeneralRe: why using the registry?memberAn Englishman in Norway23 Nov '08 - 10:15 
I think you're probably right, the registry is dying. But I'm not convinced about the Settings solution, I like to control my data storage myself. Here's a solution which just XML serialises the form state object to a file in isolated storage.
 
using System;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using System.IO.IsolatedStorage;
 
/// <summary>
/// Create an instance of this class in the constructor of a form. 
/// Will save and restore window state, size, and position.
/// Uses DesktopBounds (instead of just Form.Location/Size) 
/// to place window correctly on a multi screen desktop.
/// </summary>
public class FormStateMaintainer
{
    private Form formToRemember;
    private string windowStateFileName = @"undefined.xml";
 
    /// <summary>
    /// The location of the form
    /// </summary>
    public Point Location { get; set; }
 
    /// <summary>
    /// The size of the form
    /// </summary>
    public Size Size  { get; set; }
 
    /// <summary>
    /// The FormWindowState of the form
    /// </summary>
    public FormWindowState State { get; set; }
 
    /// <summary>
    /// Initializes an instance of the FormStateMaintainer class.
    /// </summary>
    /// <param name="form">
    /// The form to store settings for.
    /// </param>
    public FormStateMaintainer(Form form)
    {
        formToRemember = form;
        windowStateFileName = string.Format("{0}.xml", form.Name);
        formToRemember.Load += new EventHandler(On_Load);
        formToRemember.FormClosed += new FormClosedEventHandler(On_FormClosed);
    }
 
    /// <summary>
    /// Default constructor must exist for serialization to work
    /// </summary>
    public FormStateMaintainer()
    {
        Location = new Point(0, 0);
        Size = new Size(800, 600);
        State = FormWindowState.Normal;
    }
 
    void formToRemember_LocationChanged(object sender, EventArgs e)
    {
        RememberParameters();
    }
 
    void formToRemember_SizeChanged(object sender, EventArgs e)
    {
        RememberParameters();
    }
 
    void RememberParameters()
    {
        if (formToRemember.WindowState == FormWindowState.Normal)
        {
            Location = formToRemember.DesktopBounds.Location;
            Size = formToRemember.DesktopBounds.Size;
        }
        if (formToRemember.WindowState != FormWindowState.Minimized)
        {
            State = formToRemember.WindowState;
        }
    }
 
    IsolatedStorageFile GetMyIsolatedStorageFile()
    {
        return IsolatedStorageFile.GetUserStoreForDomain();
    }
 
    private void On_Load(object sender, EventArgs e)
    {
        formToRemember.Load -= new EventHandler(On_Load);
 
        // create X,Y defaults from the form itself, in case the xml is missing
        int X = formToRemember.DesktopBounds.X;
        int Y = formToRemember.DesktopBounds.Y;
 
        IsolatedStorageFile isoStore = GetMyIsolatedStorageFile();
        string[] foundFilenames = isoStore.GetFileNames(windowStateFileName);
        System.Diagnostics.Debug.Assert(foundFilenames.Length <= 1);
        if (foundFilenames.Length == 1)
        {
            using (IsolatedStorageFileStream strm = new IsolatedStorageFileStream(windowStateFileName,
                FileMode.Open, FileAccess.Read, FileShare.Read, isoStore))
            {
                // Load the state from xml
                using (TextReader tr = new StreamReader(strm))
                {
                    System.Xml.Serialization.XmlSerializer x =
                        new System.Xml.Serialization.XmlSerializer(this.GetType());
                    FormStateMaintainer loadedState = (FormStateMaintainer)x.Deserialize(tr);
                    Location = loadedState.Location;
                    Size = loadedState.Size;
                    State = loadedState.State;
                    X = Location.X;
                    Y = Location.Y;
                }
            }
        }
 
        // In case of multi screen desktops, check if we got the screen the form was on when closed.
        Rectangle boundsNow = Screen.GetWorkingArea(new Point(X, Y));
 
        if (X > boundsNow.X + boundsNow.Width)
        {
            // screen to the right is missing
            X = boundsNow.X + boundsNow.Width - Size.Width;
        }
        else if (X + Size.Width < boundsNow.X)
        {
            // screen to the left is missing
            X = boundsNow.X;
        }
 
        if (Y > boundsNow.Y + boundsNow.Height)
        {
            Y = boundsNow.Y + boundsNow.Height - Size.Height;
        }
        else if (Y + Size.Height < boundsNow.Y)
        {
            Y = boundsNow.Y;
        }
 
        Location = new Point(X, Y);
        formToRemember.DesktopBounds = new Rectangle(Location,Size);
        formToRemember.WindowState = State;
 
        // now we can watch for changes (not before, since we ourselves were changing the size/state!)
        formToRemember.SizeChanged += new EventHandler(formToRemember_SizeChanged);
        formToRemember.LocationChanged += new EventHandler(formToRemember_LocationChanged);
    }
 
    private void On_FormClosed(object sender, FormClosedEventArgs e)
    {
        formToRemember.FormClosed -= new FormClosedEventHandler(On_FormClosed);
        formToRemember.SizeChanged -= new EventHandler(formToRemember_SizeChanged);
        formToRemember.LocationChanged -= new EventHandler(formToRemember_LocationChanged);
 
        IsolatedStorageFile isoStore = GetMyIsolatedStorageFile();
        using (IsolatedStorageFileStream strm = new IsolatedStorageFileStream(windowStateFileName,
            FileMode.Create, FileAccess.Write, FileShare.None, isoStore))
        {
            // Save the state as xml
            using (TextWriter tw = new StreamWriter(strm))
            {
                System.Xml.Serialization.XmlSerializer x = 
                    new System.Xml.Serialization.XmlSerializer(this.GetType());
                x.Serialize(tw,this);
            }
        }
    }
}
 

GeneralFor .NET 2.0, use ConfigurationManagermemberITGFanatic14 Dec '06 - 8:34 
In the System.Configuration namespace is a class called ConfigurationManager. It handles multiple users on a local machine, and even roaming profiles. It ends up storing things in XML files. It's actually really nifty.
 
Check out this codeproject article. http://www.codeproject.com/useritems/SystemConfiguration.asp[^]
 
It works pretty well, and it's not too difficult or cumbersome to use.
GeneralMultiple MonitorsprotectorMarc Clifton20 May '06 - 1:50 
What happens when I have the form on a second monitor, and I close it (saving state), but later, when I open the form, the second monitor is no longer present (for example, my laptop on my desk is plugged into the second monitor, but when I take my laptop on the road there's no second monitor).
 
What often happens with "smart" programs is that the form opens completely offscreen, rather than repositioning itself to the available monitor.
 
Does your code handle that scenario?
 
Marc
 
Pensieve
Some people believe what the bible says. Literally. At least [with Wikipedia] you have the chance to correct the wiki -- Jörgen Sigvardsson
GeneralRe: Multiple MonitorsmemberStein-Tore Erdal20 May '06 - 2:43 
Hi Marc,
 
Thanks for pointing that out, I'd forgotten, I have a three screen setup and sometimes run into just that problem.
 
I've changed the code a bit so it now handles that (using Screen.GetBounds()).
 
Also added some methods for storing/setting any other things related to a form.
 
Stein-Tore
GeneralRe: Multiple MonitorsprotectorMarc Clifton20 May '06 - 2:44 
Stein-Tore Erdal wrote:
I've changed the code a bit so it now handles that (using Screen.GetBounds()).

 
Awesome! Thanks for the quick response!
 
Marc
 
Pensieve
Some people believe what the bible says. Literally. At least [with Wikipedia] you have the chance to correct the wiki -- Jörgen Sigvardsson
GeneralRe: Multiple Monitors [modified]memberSimonO9 Jul '07 - 14:46 
I found your code for allowing for the presence or absence of a previously available secondary screen very helpful. But may I suggest an enhancement? As it stands, the code assumes that the disappearing secondary screen was positioned to the right of the primary screen. But what if the disappearing secondary screen had been to the left of the primary screen? Also, and I know this is not particularly likely, what if the top of the window had been positioned very near the bottom of a screen and the screen height has since decreased? To allow for these, replace
if (X > screen_bounds.X + screen_bounds.Width)
{
    X = screen_bounds.X;
    Y = screen_bounds.Y;
}
with
// If a screen or area to the right of the primary screen
// is missing
if (X > screen_bounds.X + screen_bounds.Width 
// or a screen to the left of the primary screen is missing
// (X co-ordinates to the left of the primary screen are negative)
||  X < screen_bounds.X
// or area to the bottom of the screen is missing
||  Y > screenBounds.Y + screenBounds.Height)
{
    X = screen_bounds.X;
    Y = screen_bounds.Y;
}

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 20 May 2006
Article Copyright 2006 by Stein-Tore Erdal
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid