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

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   
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? PinmemberXaroth20-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? PinmemberStein-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? PinmemberSyed 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? PinmemberStein-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? Pinmembermayafit6-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? PinmemberAn 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 ConfigurationManager PinmemberITGFanatic14-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.

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130619.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