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:

ViewModelBase viewModel = 
mWindowManager.ShowWindow(viewModel);

Similarly, displaying a modal dialog is also a single line:

ViewModelBase viewModel = 
mWindowManager.ShowModalDialog(viewModel);

Here is the declaration I currently use for the IWindowManager interface:

/// <summary>
/// Management of window creation and lifecycles
/// </summary>
public interface IWindowManager
{
    /// <summary>
    /// Show a window associated with a viewModel
    /// </summary>
    /// <param name="viewModel">ViewModel for the window to show</param>
    void ShowWindow(ViewModelBase viewModel);

    /// <summary>
    /// Activate the window associated with a viewModel
    /// </summary>
    /// <param name="viewModel"></param>
    void ActivateWindow(ViewModelBase viewModel);

    /// <summary>
    /// Close the window associated with a viewModel
    /// </summary>
    /// <param name="viewModel">ViewModel for the window to close</param>
    void CloseWindow(ViewModelBase viewModel);

    /// <summary>
    /// Show a ViewModel as a modal dialog
    /// </summary>
    /// <param name="viewModel">ViewModel for the window to show</param>
    void ShowModalDialog(ViewModelBase viewModel);
}

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 with Window. Once created, the ViewModel is set as the DataContext 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 the View. 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:

/// <summary>
/// Create a window given the associated ViewModel
/// </summary>
/// <param name="viewModel"></param>
/// <returns></returns>
private Window CreateWindow(ViewModelBase viewModel)
{
    var modelType = viewModel.GetType();
    var windowTypeName
        = modelType.Name.Replace("ViewModel", "Window");
    var windowTypes
        = from t in modelType.Assembly.GetTypes()
            where t.IsClass
            && t.Name == windowTypeName
            select t;
    return (Window)Activator.CreateInstance(windowTypes.Single());
}

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:

/// <summary>
/// Factory interface for creating Windows
/// </summary>
public interface IWindowFactory
{
    /// <summary>
    /// Create a window given its viewmodel
    /// </summary>
    /// <param name="model">Name of the window to create</param>
    /// <returns></returns>
    Window CreateWindow(ViewModelBase model);
}

Using the Ninject Factories extension module, I can register this and get Ninject to provide the implementation:

public override void Load()
{
    Bind<IWindowFactory>().ToFactory(() => new SelectViewInstanceProvider());
}

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.

public class SelectViewInstanceProvider : StandardInstanceProvider
{
    protected override Type GetType(
        MethodInfo methodInfo, object[] arguments)
    {
        var model = arguments.Single() as ViewModelBase;
        if (model == null)
        {
            // Model is null, fallback to the default behaviour
            return base.GetType(methodInfo, arguments);
        }

        var modelType = model.GetType();
        var assembly = modelType.Assembly;
        do
        {
            if (modelType.Name.EndsWith(
                "ViewModel", StringComparison.OrdinalIgnoreCase))
            {
                var modelName = modelType.FullName;
                var viewName = modelName.Replace("ViewModel", "View");
                var viewType = assembly.GetType(viewName, false);
                if (viewType != null)
                {
                    return viewType;
                }
            }

            modelType = modelType.BaseType;
        } while (modelType != typeof(object));

        // Didn't find a ViewModel, fallback to default behaviour
        return base.GetType(methodInfo, arguments);
    }
}

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.

private static IList<CommandBinding> CreateBindings<T>(T subject)
    where T : ViewModelBase
{
    var bindings = new List<CommandBinding>();

    var processingQueue = new Queue<ViewModelBase>();
    processingQueue.Enqueue(subject);

    var commandType = typeof(RoutedCommandSinkBase);
    var modelType = typeof(ViewModelBase);

    while (processingQueue.Any())
    {
        var instance = processingQueue.Dequeue();
        foreach (PropertyDescriptor property 
            in TypeDescriptor.GetProperties(instance))
        {
            if (commandType.IsAssignableFrom(
                property.PropertyType))
            {
                var command 
                    = property.GetValue(instance) 
                        as RoutedCommandSinkBase;
                if (command != null)
                {
                    bindings.Add(command.CreateBinding());
                }
            }
            else if (modelType.IsAssignableFrom(
                property.PropertyType))
            {
                var model 
                    = property.GetValue(instance) 
                        as ViewModelBase;
                processingQueue.Enqueue(model);
            }
        }
    }

    return bindings;
}

Comments

blog comments powered by Disqus