A Configurable Window for WPF

Intro

Any application that people use often should be as convenient as possible. One of the most expected conveniences of a desktop application is that the size, location, and state of the application’s various windows persist across runs. If I resize a window, move it to the left side of my screen, and then close it, the next time I open that window it should be the same size and be at the same place on the left side of the screen. It would be great if WPF provided that functionality for us, but it does not. This post introduces a base window class that automates this for us, so that every window in our applications gets this functionality free.

What is involved?

The recipe for this dish involves:

1 lbs of an abstract window subclass called ConfigurableWindow

1 cup of a ConfigurableWindowSettings class that knows how to load and save the window settings

1 dash of a ConfigurableWindow subclass which overrides the CreateSettings method and returns a ConfigurableWindowSettings object

4 delicious properties in the Settings.settings file that are used to persist the window settings

This recipe serves one window. For every additional window that needs to have its settings persisted, add three more properties to the Settings.settings file, and create a new ConfigurableWindowSettings object that knows how to get and set those values.

How to use it

It is easy to use the ConfigurableWindow class. Here is a step-by-step explanation of how the demo application (available for download at the end of this post) was put together. This walkthrough assumes that you already have a WPF project set up and you’ve included the source files for the ConfigurableWindow and ConfigurableWindowSettings classes.

First, create a new Window in your WPF project. In this case, I named it DemoWindow.

Change DemoWindow so that it derives from ConfigurableWindow. Note: you need to do this both in the code and XAML file. Here is how you do it in the XAML:

Now that DemoWindow derives from ConfigurableWindow you need to override the CreateSettings method, and return an IConfigurableWindowSettings object. Here is that method:

At this point, if you compile the project you will receive an error telling you that the DemoWindowConfigSettings class does not exist. To remedy this situation, add a nested class in DemoWindow as seen below:

There are several things to notice about that class. First, it derives from ConfigurableWindowSettings, which is a class that knows how to load and save window settings across runs of an application. We pass into the base constructor five pieces of information: the backing store for all persisted property settings, and four property names. Those property names correspond to settings that we will soon add to our application’s Settings.settings file. Also, the DemoWindowConfigSettings class hooks the DemoWindow’s Closed event and sets the IsFirstRun setting to false. This is necessary because it enables ConfigurableWindow to know if the persisted window location value is a value saved from a previous run or not.

Lastly, we need to add some settings into the project’s Settings.settings file. We need to add four items, as seen below:

If you add more windows that derive from ConfigurableWindow, you only need to add the last three properties again with new names. The IsFirstRun setting only needs to exist once since it applies to the entire application, not a specific window in the application.

Word of warning

If you run the application in debug mode (F5) you will be using a different config file than if you run without a debugger attached (Ctrl + F5). This is the way things work in Visual Studio, so don’t be surprised if you stumble across this.

Get the code

Download the demo project here: Configurable Window Demo (The source code was updated on 12/28/07). Be sure to change the file extension from .DOC to .ZIP and then decompress the file. Note, this project was built in Visual Studio 2008, so you will have to copy the source files into a new project if you are using VS2005.

Advertisements

Share this:

Like this:

LikeLoading...

Related

This entry was posted on Thursday, December 27th, 2007 at 5:45 pm and is filed under Praxis. You can follow any responses to this entry through the RSS 2.0 feed.
Both comments and pings are currently closed.

Wouldnt it be easier to just have those properties in the settings file handle multiple windows. Couldn’t you just csv the data? I could see having to add properties for each new window a little tedious. Otherwise nice job.

Using CSV to store data for multiple windows is an interesting idea. I’ll think about that!

MarcoB,

Thanks for the bug report. Unfortunately I don’t have a second monitor, so testing a fix is impossible for me right now. 😦 If anyone comes up with a fix for the bug MarcoB reported, please drop a comment with the code. Thanks!

I could be wrong but it looks like the WPF framework is only adjusting the location of the window when its window state is normal so the os is using the primary monitor for determining the monitor in which to maximize the window. I think you’re going to have to wait for the SourceInitialized event to fire (or override OnSourceInitialized) to initialize the window state.

For settings you could consider just serializing a class that holds a collection of window positions and possibly other settings as well. We are doing this in Mole. Then you can add or remove settings without having to do anything except edit the class.

I did test this solution on my two and three monitor systems and it worked fine. However, I didn’t maximize and close them like Marlon did. I’ll update the code and test for you too.

The backing store used is arbitrary from ConfigurableWindow’s perspective. Notice how its CreateSettings method returns an IConfigurableWindowSettings instance, which can be any type of object. I specifically set it up so that the using the Settings infrastructure is merely an option, not a requirement.

If you decide that your application should not use the Settings support, but should save user settings in a database, or pass them to a Web service, or use a serialized object graph, then you can create your own class which implements IConfigurableWindowSettings and return it from CreateSettings.

Have you seen the GetWindowPlacement() and SetWindowPlacement() functions (in user32.dll). I know it’s P/Invoke, but they handle all the problems with multiple screens/resolutions, and if these change between runs (including making sure the window is still visible — many applications don’t do this). It has worked great in our WinForms apps.

Q: I can run your demo program just fine, but when I create a new one that is structured identically (except for the name) and try to run it, I get an InvalidCastException in class ConfigurableWindowSettings.GetValue, where propName is “MainWindowSize” (I named my main window that and renamed your properties accordingly). The stack trace indicates it’s happening in the first line of ConfigurableWindow.ApplySettings: “Size sz = _settings.WindowSize;”

BTW, is it not possible to put ConfigurableWindow into a separate library project (I’m trying to build several WPF apps and share as much as possible betweenst them)? When I move it into a separate class-library project, I can’t get it to compile. VS2008 says “‘Window’ does not exist in the namespace ‘Sysem.Windows’ (are you missing..” This is somewhat confusing: I have a “using Sysem.Windows” at the top. And ConfigurableWindow derives from System.Windows.Window. Are we not able to reference the Window class within a class library?

It sounds like you need to need to add references in your class library to the PresentationCore, PresentationFramework, and WindowsBase assemblies. The Window class is in PresentationFramework, but it can’t hurt to add references to the other common WPF DLLs too.

Great classes, but I don’t follow what you said to Terry – do you mean replace the protected ConfigurationWindow() constructor by making it public? If so then I get an error (when attempting to make the designer reload the window, but not when building) saying

Thanks Josh. I might give that a go, but it would involve a fair amount of reworking your code as it all hinges around the abstract ConfigurableWindow class calling CreateSettings() which is overriden by the child window. Making it non abstract would involve changing the method by which the settings were created.

1. Nit: it is slightly odd to have ConfigurableWindow call a virtual (abstract) function from its ctor (and it will generate an FxCop warning, I believe). The problem with the current pattern is that the overridden implementation of CreateSettings runs in a half-baked state (during the base class ctor, not after). Better might be to trade away the abstract CreateSettings() class and instead pass the IConfigurableWindowSettings instance to the ctor as a parameter, to wit:

2. Nit: the ConfigurableWindowSettings class you provide (which implements IConfigurableWindowSettings) might be less brittle if instead of passing string resource keys from the main window and doing type conversion in the generic Get/Set functions, you simply had an implementation which referenced the Settings properities using the .Net 2.0 wrappers. For example: