Table of contents
Right after I had published the Profile article, I went back to the small Windows app I had been working on to add the code that would save the form's position and size upon closing and then show it the same way next time. I added handlers for the
Closed events, and added the proper code to save and restore the position and size values using my
Registry profile class. The code just used the form's
WindowState properties, and it worked fine... except for one small problem. If the window was minimized or maximized and I then closed it, it would reappear minimized or maximized but it would not remember what its "normal" size had been. So its normal size would be the same as its maximized or minimized size. Not good!
I went looking for a .NET solution to this problem and to my dismay could not find one. I then remembered a C++ class I had worked on and published here called,
PlacementHook. It used a simple hooking mechanism (via
SetWindowLong) to transparently handle the
WM_DESTROY messages and take care of saving and restoring the window's placement. The window's placement, of course! -- that's how I had been able to save the window's "normal" size. The Win32
WINDOWPLACEMENT structure contains a
rcNormalPosition member which keeps track of that. Now, why didn't the .NET classes provide this? Hmmm...
Anyway, my new mission was clearly defined: to write a class that would encapsulate saving and restoring the form's placement. It would use my new Profile classes to store and retrieve the placement data. It would call the Win32 API
GetWindowPlacement to get the form's "normal" position and size. And like
PlacementHook, it would contain handlers for the
Closed events that would do all the work transparently.
So once again, I had to set my small Windows app aside and get to work on my new class. I called it
Placement and decided that it would be great to publish it here. I hope you find it useful in your Windows-based apps.
I initially created the class stand-alone -- not derived from anything. It just had a couple of constructors where the
Form object would be passed. The class would keep the
Form object inside a field and save/restore its placement inside the
Closed event handlers.
I then went back to try to convert it to a
Component class so that it could be added to the Toolbox and dropped inside a form inside the designer. This was problematic because the designer would not be smart enough to know that it needed to pass the form's object (
this) to the class's constructor. So I had to add a default constructor to the class. But I still didn't know how to make the designer generate code to set the
Form property to
this, the container. Well, after doing a bit of research I found the
ComponentDesigner class. Inside its
OnSetComponentDefaults method is where I set the
Form property, which the designer cleverly translates to
It all turned out great! The
Placement class can be used just like any component and added to the designer's Toolbox. Once there, you can drag-n-drop the component on a form and pretty much forget about it. After you compile and run your program, you'll notice that each time the form comes up, it will be at the same location and with the same dimensions as it was when it was last closed. All this without having to write a single line of code!
Of course, I added several properties to the class to allow you to adjust the component if necessary. There's one for getting/setting the
Profile object that takes care of saving/restoring the profile data. You may (manually) set that to any
IProfile implementation, such as XML or Config -- by default it's Registry. There's also a
Section property that holds the name of the section under which the placement data will be stored inside the profile. And finally, I added three distinct boolean properties that allow you to individually set which aspects of the placement to restore (location, size, and state). These are all set to
true by default and can be easily adjusted inside the Properties window.
I placed the class inside the
AMS.Form namespace, and built it into a new DLL called AMS.Form.dll. I plan to add other
Form-related classes to this DLL as the need arises, but for now it only contains the
Placement class. To add it to the Visual Studio .NET Toolbox, follow these steps:
- Open your own project inside Visual Studio .NET.
- Open the file containing the form you wish to add the control(s) to.
- Right click on the Toolbox and select "Customize Toolbox".
- Click on the .NET Components tab.
- Click the Browse button and select the AMS.Form.dll file.
Placement component will be added to the Customize toolbox and will be check-marked.
- Click OK. The control will then appear on the toolbox.
- Drag and drop it to your form and modify any of its properties, if desired.
You may alternately add the
Placement class to your code manually, which is also very simple. First you need to add the DLL your project's References, which is done as follows inside Visual Studio .NET:
- Open your own project inside Visual Studio .NET.
- Inside the Solution Explorer toolbox, right click on the References folder and select "Add Reference".
- Click on the Browse button and select the AMS.Form.dll file.
- Click OK. You may now use the
AMS.Form namespace inside your project.
Then you can add this line to your form's constructor, right after the call to
AMS.Form.Placement placement = new AMS.Form.Placement(this);
That's pretty much it! If you want to change the default settings, it's simple enough. Here's an example:
placement.Profile = new Xml("Settings.xml");
placement.Section = "Some Section";
placement.RestoreSize = false;
I thoroughly documented the class and created a help file for it, called AMS.Form.chm. I doubt you'll need it, but it's there just in case.
One thing to keep in mind is that AMS.Form.dll depends on AMS.Profile.dll (which I've included in the downloadables -- the source code is here). Just remember to keep these two DLLs together. Enjoy!
- Nov. 7, 2003
- Nov. 12, 2003
- Re-release due to this article being published under the wrong user account.
- Changed the
Restore method to not restore the location if doing so would cause the form to fall outside the bounds of the available screen(s). Also, if the form is initially minimized or maximized, its state will not be restored. Thanks to Maximilian Hänel and Igor Gribanov for their suggestions.