Continuing with the theme started last week of talking about useful design patterns for WPF software development, this week I’m talking about the Window Manager pattern.
Previous Post
My previous post on WPF Design Patterns was about the Event Aggregator pattern.
Window Manager
The window manager centralises all of the messy details about window creation and destruction, allowing your actual Views and ViewModels to focus on the specific task at hand instead of spending time on the minutiae of the technology. You’ll also find this pattern described as Window Controller.
Motivation
Most of the tutorials that I’ve read about the MVVM design pattern and WPF programming in general focus on the use of data binding and the way the View and the ViewModel interact with each other.
The ViewModel exposes a set of properties that give a logical representation of the required user interface and the
View provides all the technological support to present that to the user and allow for interaction. The View knows
about the ViewModel through its DataContext
property and the ViewModel knows nothing (directly) about the View.
While extremely important, this focus on the steady state of the system ignores a couple of important questions - How do we get to the steady state? And, How do we clean up afterwards?
One clean way to handle these concerns is to introduce a Window Manager.
Show me the code
With a WindowManager
class to take care of finding the appropriate view, displaying a new window can be as simple
as one line of code:
Similarly, displaying a modal dialog is also a single line:
Here is the declaration I currently use for the IWindowManager
interface:
So, how does it work?
When you call either ShowWindow()
or ShowModalDialog()
, the WindowManager does the following things:
-
Locate and instantiate an appropriate View based on the convention of replacing the suffix
ViewModel
withWindow
. Once created, the ViewModel is set as theDataContext
of the window to activate DataBinding. -
Hook up to the appropriate events to take control of the window “Closing” process and delegate the details to the ViewModel itself.
-
Create CommandBindings to hook up any commands published by the
ViewModel
to theView
. To allow for composite ViewModels, this is a recursive operation.
Creating the Window
The simplest approach for window creation might be to implement it with a simple bit of reflection, like this:
This assumes your Views and ViewModels live next to each other, and that your Views all have simple parameterless default constructors.
In my case, I’m using the Ninject dependency injection framework - leveraging it to create the Windows works like this.
I declare a factory interface for the actual creation:
Using the Ninject Factories extension module, I can register this and get Ninject to provide the implementation:
The SelectViewInstanceProvider
class is how we inject our custom policy into the factory - based on the actual type
of the ViewModel passed, it finds the appropriate type for the View, leaving actual construction to Ninject.
The use of a loop allows for specialist ViewModels to be created that derive from existing ViewModel classes. If a matching View is created as well, it will be used - but if not, the original View will be picked up and used with the new ViewModel.
Handling Close
What do we do if the user clicks the “Close” button on the title bar of the window - or if the user triggers the close in any other way?
With a MVVM application, we want the ViewModel to be in charge of user interaction, so we need to find a way to divert that responsibility away from the Window’s chrome and back to the ViewModel where it belongs.
To achieve this, the Window Manager hooks the Closing
event of the window. The Closing
event is triggered prior to
the actual close and provides a way to decline the close event. The Window Manager uses this, preventing the close
from happening but sending a CloseWindowMessage
via the
event aggregator.
When the CloseWindowMessage
is handled, the Close()
method on the ViewModel itself is called to handle the closing.
It’s up to this method to decide what to do - up to and including closing the Window by calling WindowManager.CloseWindow()
.
Creating Command Bindings
Use of Commands is the standard way to hook user interface elements like buttons and menu items to the code that should be triggered by their use.
The Window Manager scans the ViewModel for any published commands (i.e. properties of type RoutedCommandSink) and creates a CommandBinding for each one, linking the View and ViewModel.
Here’s the requisite code from my own WindowManager implementation - note that this works recursively, scanning any nested ViewModels to allow relatively painless composition.
Comments
blog comments powered by Disqus