Introduction
Every time I create a new desktop application I find myself having to add some
code that will restore the 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";
}
[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";
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)
{
m_data = (int)key.GetValue("m_data", m_data);
}
private void SaveState(object sender, RegistryKey key)
{
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 property functions).
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
{
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;
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);
m_normalWidth = m_parent.Width;
m_normalHeight = m_parent.Height;
}
get
{
return m_parent;
}
}
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)
{
if(m_parent.WindowState == FormWindowState.Normal)
{
m_normalWidth = m_parent.Width;
m_normalHeight = m_parent.Height;
}
}
private void OnMove(object sender, System.EventArgs e)
{
if(m_parent.WindowState == FormWindowState.Normal)
{
m_normalLeft = m_parent.Left;
m_normalTop = m_parent.Top;
}
m_windowState = m_parent.WindowState;
}
private void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
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);
if(!m_allowSaveMinimized)
{
if(m_windowState == FormWindowState.Minimized)
m_windowState = FormWindowState.Normal;
}
key.SetValue("WindowState", (int)m_windowState);
if(SaveStateEvent != null)
SaveStateEvent(this, key);
}
private void OnLoad(object sender, System.EventArgs e)
{
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;
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 behaviour 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.