Saving and Restoring Location, Size, and Window State of a Form

Every time I create a new desktop application I find myself having to add some code that will restorethe main application window to the position, size and window state at the time of closing. This article presents a simple C# class that may be added to a form to automatically do this. When I designed this class I wanted to be able to add it to a form using the least amount of code and also be able to add it to a form from the toolbox.

The class is named PersitWindowState and using it in a form is very simple. The simplest usage of the class is shown below:

public class AppForm : System.Windows.Forms.Form
{
private PersistWindowState m_windowState;
public AppForm()
{
m_windowState =
new PersistWindowState();
m_windowState.Parent =
this;
m_windowState.RegistryPath = @"Software\YourCompany\YourApp";
// set registry path in HKEY_CURRENT_USER
}
[STAThread]
static void Main()
{
Application.Run(
new AppForm());
}
}

This is all the code that is required to automatically save and load the window state correctly. I have also added an additional feature to PersistWindowState that facilitates loading and saving any additional form state information. The form can subscribe to two events PersistWindowState.LoadStateEvent and PersistWindowState.SaveWindowState. These events are fired with a RegistryKey instance allowing the form to save and load additional values. The code below illustrates the use of this feature:

public class AppForm : System.Windows.Forms.Form
{
private PersistWindowState m_windowState;
public AppForm()
{
this.Text = "RestoreFormState";
m_windowState =
new PersistWindowState();
m_windowState.Parent =
this;
m_windowState.RegistryPath = @"Software\YourCompany\YourApp";
// set registry path in HKEY_CURRENT_USER
// subscribe to the load and save events
m_windowState.LoadStateEvent += new PersistWindowState.WindowStateDelegate(LoadState);
m_windowState.SaveStateEvent +=
new PersistWindowState.WindowStateDelegate(SaveState);
}
private int m_data = 34;
private void LoadState(object sender, RegistryKey key)
{
// get additional state information from registry
m_data = (int)key.GetValue("m_data", m_data);
}
private void SaveState(object sender, RegistryKey key)
{
// save additional state information to registry
key.SetValue("m_data", m_data);
}
[STAThread]
static void Main()
{
Application.Run(
new AppForm());
}
}

Let's now take a look at PersistWindowState itself. The key to this class is the ability of an instance of any class to subscribe to the events of any other class. PersistWindowState subscribes to 4 events of it's parent's class namely Form.Closing, Control.Resize, Control.Move and Form.Load. These events are subscribed to when the Parent property is set (a good example of the usefulness of propertyfunctions). The current state of the form is recorded in the two events Control.Resize and Control.Move. Control.Resize allows us to record the current form width and height. Note that we only do this if the current window state is normal. If we don't then we end up saving the size of the maximized or minimized window which is not what we want. The Control.Move event is used to record the form's position (agin only if the window state is normal) and the current window state.

The saving and loading of registry data is handled in response to Form.Closing and Form.Load respectively. When I first developed this class in .NET Beta 1, I found that restoring the form state in response to Form.Load caused the form to visibly move. This does not appear to happen in the released version of .NET.

Here is the complete code for PersistWindowState:

public class PersistWindowState : System.ComponentModel.Component
{
// event info that allows form to persist extra window state data
public delegate void WindowStateDelegate(object sender, RegistryKey key);
public event WindowStateDelegate LoadStateEvent;
public event WindowStateDelegate SaveStateEvent;
private Form m_parent;
private string m_regPath;
private int m_normalLeft;
private int m_normalTop;
private int m_normalWidth;
private int m_normalHeight;
private FormWindowState m_windowState;
private bool m_allowSaveMinimized = false;
public PersistWindowState()
{
}
public Form Parent
{
set
{
m_parent =
value;
// subscribe to parent form's events
m_parent.Closing += new System.ComponentModel.CancelEventHandler(OnClosing);
m_parent.Resize +=
new System.EventHandler(OnResize);
m_parent.Move +=
new System.EventHandler(OnMove);
m_parent.Load +=
new System.EventHandler(OnLoad);
// get initial width and height in case form is never resized
m_normalWidth = m_parent.Width;
m_normalHeight = m_parent.Height;
}
get
{
return m_parent;
}
}
// registry key should be set in parent form's constructor
public string RegistryPath
{
set
{
m_regPath =
value;
}
get
{
return m_regPath;
}
}
public bool AllowSaveMinimized
{
set
{
m_allowSaveMinimized =
value;
}
}
private void OnResize(object sender, System.EventArgs e)
{
// save width and height
if(m_parent.WindowState == FormWindowState.Normal)
{
m_normalWidth = m_parent.Width;
m_normalHeight = m_parent.Height;
}
}
private void OnMove(object sender, System.EventArgs e)
{
// save position
if(m_parent.WindowState == FormWindowState.Normal)
{
m_normalLeft = m_parent.Left;
m_normalTop = m_parent.Top;
}
// save state
m_windowState = m_parent.WindowState;
}
private void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
// save position, size and state
RegistryKey key = Registry.CurrentUser.CreateSubKey(m_regPath);
key.SetValue("Left", m_normalLeft);
key.SetValue("Top", m_normalTop);
key.SetValue("Width", m_normalWidth);
key.SetValue("Height", m_normalHeight);
// check if we are allowed to save the state as minimized (not normally)
if(!m_allowSaveMinimized)
{
if(m_windowState == FormWindowState.Minimized)
m_windowState = FormWindowState.Normal;
}
key.SetValue("WindowState", (
int)m_windowState);
// fire SaveState event
if(SaveStateEvent != null)
SaveStateEvent(
this, key);
}
private void OnLoad(object sender, System.EventArgs e)
{
// attempt to read state from registry
RegistryKey key = Registry.CurrentUser.OpenSubKey(m_regPath);
if(key != null)
{
int left = (int)key.GetValue("Left", m_parent.Left);
int top = (int)key.GetValue("Top", m_parent.Top);
int width = (int)key.GetValue("Width", m_parent.Width);
int height = (int)key.GetValue("Height", m_parent.Height);
FormWindowState windowState = (FormWindowState)key.GetValue("WindowState",
(int)m_parent.WindowState);
m_parent.Location =
new Point(left, top);
m_parent.Size =
new Size(width, height);
m_parent.WindowState = windowState;
// fire LoadState event
if(LoadStateEvent != null)
LoadStateEvent(
this, key);
}
}
}

You may have noticed a property AllowSaveMinimized, if this is set to true then if the form is minimized when it is closed it will be minimized when it is reloaded. This behavior is probably not what you want to happen so it is set to false by default.

Well I hope a few people find this class useful and that some have learned a little from it.