Click here to Skip to main content
15,888,527 members
Articles / Programming Languages / C#
Article

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

Rate me:
Please Sign up or sign in to vote.
3.61/5 (9 votes)
19 May 20062 min read 69.6K   29   17
A class that will save and restore window state, size, and position for a Form in .NET 2.0.

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


Written By
Norway Norway
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRetaining the order in which the windows are opened Pin
juhi0923-Sep-08 19:59
juhi0923-Sep-08 19:59 
GeneralAn example using My.Settings Pin
Holm7630-Jul-07 22:38
Holm7630-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:

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


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 contents Pin
deb198215-Apr-07 17:47
deb198215-Apr-07 17:47 
GeneralRestore minimized form Pin
jodvova17-Aug-06 0:27
jodvova17-Aug-06 0:27 
GeneralUnsubscribing Closing/Closed event handlers Pin
Delta-Z5-Aug-06 4:28
Delta-Z5-Aug-06 4:28 
Questionwhy using the registry? Pin
mayafit20-May-06 5:35
mayafit20-May-06 5:35 
AnswerRe: why using the registry? Pin
Xaroth20-May-06 11:54
Xaroth20-May-06 11:54 
GeneralRe: why using the registry? Pin
Stein-Tore Erdal20-May-06 22:56
Stein-Tore Erdal20-May-06 22:56 
GeneralRe: why using the registry? Pin
Syed Moshiur Murshed21-May-06 1:40
professionalSyed Moshiur Murshed21-May-06 1:40 
GeneralRe: why using the registry? Pin
Stein-Tore Erdal21-May-06 2:34
Stein-Tore Erdal21-May-06 2:34 
GeneralRe: why using the registry? Pin
mayafit6-Sep-06 20:02
mayafit6-Sep-06 20:02 
GeneralRe: why using the registry? Pin
An Englishman in Norway23-Nov-08 10:15
An Englishman in Norway23-Nov-08 10:15 
GeneralFor .NET 2.0, use ConfigurationManager Pin
ITGFanatic14-Dec-06 8:34
ITGFanatic14-Dec-06 8:34 
GeneralMultiple Monitors Pin
Marc Clifton20-May-06 1:50
mvaMarc Clifton20-May-06 1:50 
GeneralRe: Multiple Monitors Pin
Stein-Tore Erdal20-May-06 2:43
Stein-Tore Erdal20-May-06 2:43 
GeneralRe: Multiple Monitors Pin
Marc Clifton20-May-06 2:44
mvaMarc Clifton20-May-06 2:44 
GeneralRe: Multiple Monitors [modified] Pin
SimonO9-Jul-07 14:46
SimonO9-Jul-07 14:46 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.