Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Composite WPF Display-on-Demand

0.00/5 (No votes)
30 Aug 2009 1  
Composite WPF Display-on-Demand.

Introduction

Note: This article has been superceded by Getting Started with Prism 2.1 for WPF, which covers the same ground in more detail. This article remains for those still using Prism 1.0.

In June 2008, Microsoft released version 1.0 of Composite Client Application Guidance, which provides a framework for developing composite applications in WPF. In February 2009, Microsoft released version 2.0, which extends the framework to Silverlight. Both frameworks are commonly referred to as Prism, their code name during development.

Most of the Quickstarts and sample applications that ship with Prism implicitly assume that all modules are loaded at startup and are displayed immediately and continuously. But that’s often not the case. For example, consider Microsoft Outlook, which swaps workspace panels in and out of the UI as the user changes tasks from email, to scheduling, to task management.

Prism, built as it is on the idea of loosely-coupled modules, would seem to be ideal for this sort of UI. But oddly, there isn't a lot of documentation on how to create this type of UI using Prism. This article explains one way of implementing such an interface. It includes a very simple demo project that shows how to do it.

This article assumes that you have a basic understanding of Prism and ‘inversion of control containers’, on which Prism depends. If you are not familiar with these items, MSDN provides a good starting point. The demo project uses Prism 1.0, and it includes the necessary assemblies. The demo will run, with minor modifications, equally as well on Prism 2.0. The demo app is written in C#, but it can be easily translated to VB.NET.

The Problem

The problem that this article will address is: How do you show and hide module views in C# code? This is a bit different from a different, but related problem: How do I load a module on demand? MSDN has a good How-To for that problem. Basically, on-demand loading defers module loading until the module is requested in code. What we are going to do is load all modules at startup, but defer showing the modules until requested in code. That’s why we refer to the problem as Display-on-Demand, rather than Load-on-Demand. The solution presented in this article can be adapted to load and unload modules on demand, by changing calls to IRegion.Activate() and IRegion.Deactivate() to IRegion.Add() and IRegion.Remove() methods, and making other minor changes. Matias Bonaventura’s blog has an entry and a demo app that may be helpful in doing this.

The Demo App

The demo app included with this article demonstrates Display-on-Demand in a Composite WPF application. The app is extremely simple; in fact, it is little more than a Hello World application. However, it contains complete code for implementing display-on-demand.

One item is particularly worthy of note regarding the demo app. The app uses a relaxed form of the Model-View-ViewModel pattern. I say ‘relaxed’ because the Shell view model serves both the Shell and the two modules in the app. In a production app, each module should probably have its own view model, to minimize coupling to the shell.

Here is how the app works: The ContentControl in the Shell window is set in XAML as the sole Composite WPF region. The buttons in the shell window are bound to command properties in ShellViewModel.cs. That approach keeps code out of the code-behind, which improves testability.

View model command properties are implemented as ICommands. My ICommands tend to be different from what you may see elsewhere. I create each ICommand as a separate object, and I put the code to execute the command in its Execute() method. Common functions, such as the ClearRegion() method, go into static service classes. In a production app, I would structure things a bit differently to loosen coupling. In the demo app, we keep things relatively simple.

Walkthrough

When the app starts, the Bootstrapper initializes two modules, Module A and Module B, in the usual Composite WPF manner. The Initialize() method of each module loads the module, but it does not activate it:

public void Initialize()
{
    // Get main region
    var mainRegion = m_RegionManager.Regions["MainRegion"];
 
    // Load Module A
    var view = new ModuleAView();
    mainRegion.Add(view, "ModuleA.ModuleAView");
}

Note the name we give the view in the second parameter to mainRegion.Add(). We give the module a name equal to its fully qualified class name. This is the value that is returned when we call ToString() on an instance of the view. As we will see below, that approach is key to being able to retrieve the view without knowing anything about it.

Since the views are loaded but not initialized, when the demo app loads, it displays an empty shell:

When you click a button, its binding invokes a command property on ShellViewModel.cs, which in turn invokes the appropriate ICommand. For example, clicking the Load Module A button ultimately invokes the LoadModuleACommand.Execute() method:

public void Execute(object parameter)
{
    // Get main region
    var mainRegion = m_ViewModel.RegionManager.Regions["MainRegion"];
 
    // Clear region
    ModuleServices.ClearRegion(mainRegion);
 
    // Activate Module A view
    var moduleAView = mainRegion.GetView("ModuleA.ModuleAView");
    mainRegion.Activate(moduleAView);
}

The method first gets the main region from the Prism region manager, which it gets from the shell view model. Then the command calls the ModuleServices.ClearRegion() method to deactivate any currently-active view. As I mentioned above, this method is contained in a static service class:

public static void ClearRegion(IRegion region)
{
    // Get existing view names
    var oldViewNames = new List<string>();
    foreach (var v in region.Views)
    {
        var s = v.ToString();
        oldViewNames.Add(s);
    }
 
    // Remove existing views
    foreach (var oldViewName in oldViewNames)
    {
        var oldView = region.GetView(oldViewName);
        region.Deactivate(oldView);
    }
}

The method first compiles a list of view names. We can't get a Name property on each view, because the views are cast to type object in the IRegion.Views collection. But we can call ToString() on the views, which will return the fully qualified name of the view. Since we earlier set the name of the view equal to its fully-qualified name, what we get back from ToString() is, in fact, the name of the view.

Why do we use a foreach iterator, when all we really want to do is deactivate one view? The approach that I took in the demo app was to deactivate all views, so that the calling code would not need to know which view was active. But other approaches should work as well, such as storing the name of the current view in the view model when that view is activated, then recalling it for deactivation when a different view is loaded. Since my ICommands contain a reference to the view model, that’s pretty easy to do.

The result of the button click is as simple as it gets. The view for the requested module is loaded and displays as follows:

The Unload buttons in the Shell work pretty much the same way. Since we know which view we want to deactivate, we can simply pass the name to the main region.

Conclusion

There is really not much more to say. This is my first pass at the problem, and I am sure the solution will evolve over time. One of the reasons for publishing this article is to submit it for the review of my peers on CodeProject. If you find any errors, or if you can suggest improvements to the solution, your comments are most welcome.

History

  • 29th August, 2009: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here