Click here to Skip to main content
6,295,667 members and growing! (16,636 online)
Email Password   helpLost your password?
Desktop Development » Miscellaneous » Windows Forms     Beginner License: The Code Project Open License (CPOL)

Painless Persistence of Windows Forms positions

By Phil Martin...

A no-pain way of retaining your Windows Form positions between application runs
C# (C# 1.0, C# 2.0, C# 3.0), .NET (.NET 2.0, .NET 3.5), WinForms, Dev
Posted:21 May 2008
Views:8,018
Bookmarked:19 times
Unedited contribution
Announcements
Loading...
 
Search    
Advanced Search
printPrint   Broken Article?Report       add Share
  Discuss Discuss   Recommend Article Email
7 votes for this article.
Popularity: 3.19 Rating: 3.78 out of 5

1
1 vote, 14.3%
2
1 vote, 14.3%
3
4 votes, 57.1%
4
1 vote, 14.3%
5

Introduction

This is a simple drop-in component that will retain the bounds and state of a window between sessions of an application.

Background

A common task in developing desktop applications is remembering where the user put their forms in the last application run. Some users spend a great deal of time setting up their workspace exactly right, and it is quite an unpleasant experience to realise the application doesn't remember their hard work.

Using the Code

To use, follow these steps:

  1. simply place add a reference to the RememberFormPosition assembly, or include the source in your own project
  2. Open the designer for your windows form of chouse
  3. From the toolbox, add a RememberFormPosition component

And that is all there is to it.

Points of Interest

Where data is stored

The position and state information for each form is stored in the registry in the Application.UserAppDataRegistry key. The following registry entries will be created:

  • FormName_Left
  • FormName_Top
  • FormName_Right
  • FormName_Bottom
  • FormName_State

Where FormName is the name of the form, or the custom name that is provided to the control.

When is the data read in

The RememberFormPosition component implements the ISupportInitialize interface, and we do the reading of the form state in the EndInit() implementation.

Doing this in EndEdit() ensures that

  • The form is not yet visible, so no update flickering can take place. For example if we did this on and FormLoad event instead, we would see the window move around as it has already been made visible.
  • The Form property of the RememberFormPosition component is set up.
    public void EndInit()
    {
      _form.StartPosition = FormStartPosition.Manual;
      RememberFormPositionUtils.RestoreFormPlacement(Application.UserAppDataRegistry,
                                                    Form,
                                                    UseFormName ? _form.Name : StorageName);
    }

When is information written out

During the initialization of the RememberFormPosition component, we attach an event handler to our form's FormClosing event. During this, we write out the form's position and state. This is done because it ensures the form has not been disposed yet and all the information we need is in a valid state.

How are minimized and maximized windows handled

The special cases of a form being maximized or minimized needs to be handled carefully, because using the ordinary Control.Bounds will not achieve the desired result. In the case of being maximized it will be the extends of the current Screen.WorkingArea, and in the case of minimized it may be very small and will not be useful.

So when our Form.WindowState is not FormWindowState.Normal, we do the following:

  • Look at the Form.RestoreBounds property
  • This Form.RestoreBounds property is relative to the Screen.PrimaryScreen.WorkingArea, so we need to compute the desktop bounds for this.
  • We then write out the current WindowState

The function that handles this is RemeberFormPositionUtils.SaveFormPlacement()

    public static void SaveFormPlacement(RegistryKey key, Form form, string name)
    {
      if (form.WindowState == FormWindowState.Normal)
      {
        key.SetRectangleValue(name, form.DesktopBounds);
      }
      else
      {
        Rectangle workingArea = Screen.PrimaryScreen.WorkingArea;
        Rectangle restoreBounds = form.RestoreBounds;
        restoreBounds.X -= workingArea.X;
        restoreBounds.Y -= workingArea.Y;
        key.SetRectangleValue(name, restoreBounds);
      }
      key.SetWindowStateValue(name, form.WindowState);

    }

When reading these back in, we

  • Set Form.StartPosition to FormStartPosition.Manual
  • Set the WindowState to FormWindowState.Normal.
  • We then set form bounds by using Form.SetDesktopBounds().
  • After this we change the Form.WindowState to the state stored in the registry.

The code that handles this is primarily in RemeberFormPositionUtils.RestoreFormPlacement()

    public static void RestoreFormPlacement(RegistryKey key, Form form, string name)
    {
      Rectangle? rect = key.GetRectangleValue(name);

      if (rect.HasValue)
      {
        Rectangle formBounds = rect.Value;

        if (!form.IsMdiChild)
        {
          formBounds = EnsureFitsInDesktop(formBounds);
        }

        form.WindowState = FormWindowState.Normal;
        form.SetDesktopBounds(formBounds.X, formBounds.Y, formBounds.Width, formBounds.Height);
      }

      FormWindowState? state = key.GetWindowStateValue(name);

      if (state.HasValue)
      {
        form.WindowState = state.Value;
      }
    }

Extension Methods

A small portion of the code makes use of extension methods to read and write values from the registry. It is hardly a necessary thing, but it was just something to experiment with.

If you wish to use the code in a C# 2 compiler, it should be a trivial task to remove the extra this keywords and just refer to the static classes explicitly.

Multiple Monitors

Multiple monitors has not been tested, but preliminary code has been put in place to handle it. If all four corners of the form are out of an available Screen, then we place the form in the middle of the screen. Also, if the form is too big to fit on the screen, we resize it so it is just under the size of the working area.

Things learned about Components and WinForms in general

Designer Serialized Properties

When exposing properties that [Browsable(true)], ensure that if you have a [DefaultValue] attribute, that you also set up this default value in your constructor! If you do not then if these conidtions are true:

  • Your property value is the same as your DefaultValue
  • Your DefaultValue is different to that of the default compiler value.

Then your property value will not get serialized and it will very confusingly lose your value. This happens because in designer serialized code, if a property value equals a DefaultValue, then the value is not written out as part of serialization. And hence next time the deisgner appears and your object is instantiated, then it will revert to the compiler generated value.

Discovering the owner of a component

This was not as straight-forward a task as I would have imagined. Components to not have direct knowledge of the Component or Control they are a part of.

In order to achieve this a simple ComponentDesigner needed to be created in order to initialise our RememberFormPosition.Form property to the current Form. Inside ComponentDesigner.Initialize() we have the ability to enquire what component we are designing via the ComponentDesigner.ParentComponent property.

History

21/05/2008 - First article version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Phil Martin...


Member
A professional developer in the Mining sector since 2001, Phil has a passion for creating robust, quality scientific applications.

He will never forget his roots though, those early nostalgia filled days of writing Quake 1 and 2 mods, and writing 3D modelling tools for the mod makers of the day.

Phil is passionate about Jesus, his family, mathematics, art, tennis, squash, cycling and talking about himself in the 5th person. A friend of his wife said that Phil doesn't mind the odd bit of programming either.
Occupation: Software Developer (Senior)
Location: Australia Australia

Other popular Miscellaneous articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 18 of 18 (Total in Forum: 18) (Refresh)FirstPrevNext
GeneralYet another congratulation message PinmemberTiago Freitas Leal6:07 13 May '09  
GeneralRe: Yet another congratulation message PinmemberPhil Martin...13:24 13 May '09  
GeneralRe: Yet another congratulation message PinmemberTiago Freitas Leal14:33 13 May '09  
GeneralRe: Yet another congratulation message PinmemberTiago Freitas Leal1:39 23 May '09  
GeneralChange of storage Pinmemberdactivo1:40 11 Nov '08  
GeneralRe: Change of storage PinmemberPhil Martin...10:55 11 Nov '08  
GeneralSuggestion PinmemberChantiPDM14:06 18 Jul '08  
GeneralInteresting subject PinmemberGrommel6:09 21 May '08  
GeneralRe: Interesting subject PinmemberPhil Martin...13:31 21 May '08  
RantRegistry in Vista PinmemberItay Sagui6:00 21 May '08  
GeneralRe: Registry in Vista PinmemberPhil Martin...13:30 21 May '08  
GeneralI have seen this done before PinmvpSacha Barber5:15 21 May '08  
GeneralRe: I have seen this done before PinmemberPhil Martin...13:29 21 May '08  
GeneralRe: I have seen this done before PinmvpSacha Barber22:11 21 May '08  
GeneralMissing download PinsupporterMarc Clifton3:47 21 May '08  
GeneralRe: Missing download PinmemberPhil Martin...4:55 21 May '08  
GeneralNo source code Pinmember leppie 3:47 21 May '08  
GeneralRe: No source code PinmemberPhil Martin...4:53 21 May '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 21 May 2008
Editor:
Copyright 2008 by Phil Martin...
Everything else Copyright © CodeProject, 1999-2009
Web15 | Advertise on the Code Project