Introduction

It’s like the punch line of an old joke: “What, you again?” Yes, it’s Prism update time again, and this time we’re up to Version 4, which brings Prism in line with the current numbering of the .NET Framework. The good news is that this upgrade is pretty worthwhile, with improved navigation, MVVM guidance, and a Service Locator that allows us to use either Unity or the Managed Extensibility Framework incorporated into .NET 4.0.

This article is an update to my earlier article, Getting Started with Prism 2.1 for WPF. Prism 4 includes pretty good documentation and a number of QuickStarts, so I won’t spend a lot of time explaining Prism’s background and theory. The article will focus on how to get a Prism 4 line-of-business application up and running, using WPF and the Unity dependency injection (DI) container. If you need a tutorial before diving into this article, try the Hands-On Lab included with Prism 4.

This article goes a little further than my previous Prism article. The earlier article presented a strictly bare-bones UI—you couldn’t really use it in a production app. This article demonstrates a more sophisticated UI, complete with:

A Ribbon at the top of the application; and

Outlook-style TaskButtons in the lower left corner.

You might ask “What’s the big deal?” After all, it’s fairly easy to add a Ribbon and a couple of buttons to the Shell. If the controls are being added by the developer at design-time, that’s true. However, this first-pass approach will result in tight coupling between the Shell and its modules.

Consider the following: If we wanted to add modules to that first-pass application down the road, here is what we would have to do:

Open the Shell;

Add a new TaskButton control to the Shell;

Change the Ribbon in the Shell;

Recompile the Shell; and

Retest the Shell.

And that really defeats the purpose of using Prism, which is designed to keep modules as loosely coupled as possible. Ideally, to add a new module later, we should be able to simply drop it into a designated folder, where Prism will discover it and load it into the Shell, complete with its own TaskButton and RibbonTab, along with its views.

That’s what the demo app does—it provides commercial-grade user interaction without coupling the Shell to its modules. That keeps the modules as independent and isolated as possible, so that we can develop each one separately from all of the others. The trick is to have each module load its own TaskButton and its own RibbonTab.

This article is a companion to another CodeProject article that I have written, A Prism 4 Application Checklist, which presents a fairly detailed checklist of steps to follow in building a Prism 4 application. I used the checklist to structure the demo app, so the checklist should provide a good walkthrough for the demo, in addition to general guidance on setting up a Prism 4 application. But, before turning to the checklist, let’s take a look at the general structure of the demo app.

The Parts of the Demo App

The UI of the demo app is modeled on Outlook 2010. It uses a custom TaskButton control, which is documented in my article Create a WPF Custom Control. This control mimics the behavior of the Mail, Calendar, Contacts, and Tasks buttons in the lower-left corner of Outlook’s main window.

An Outlook-style interface offers a great deal of flexibility, and is adaptable to a wide variety of applications. It is particularly useful for applications that need to switch between several modules, working with only one at a time, the way Outlook switches between mail, a calendar, contacts, and tasks. Most business users are familiar with the Outlook UI and should find an app built on it fairly easy to learn.

The Shell: The Shell has four named regions:

The regions behave similarly to their counterparts in Outlook 2010:

Ribbon Region: This region contains the application Ribbon. The Ribbon itself and its Home tab are hard-coded into the Shell.

TaskButton Region: This region is used to switch modules. The buttons in this region behave similarly to Outlook’s Mail, Calendar, Contacts, and Tasks buttons.

Navigator Region: This region is used to navigate among views within the active module. It behaves similarly to Outlook’s Navigator Region. For example, in Outlook’s Mail module, the Navigator Region contains a folder list for various email folders, such as Inbox, Sent Items, and Deleted Items. For simplicity, the demo app loads a view with a TextBlock that merely identifies itself.

Workspace Region: This region contains the views where the actual work is done. For simplicity, the module’s views for this region simply identify themselves, the same as Navigator views.

Modules: The demo app has two modules, Module A and Module B. Each module loads its TaskButton, its RibbonTab, and simple views for its Navigator and Workspace regions. Modules are loaded using Module Discovery, which minimizes coupling to the Shell. If you look at the references for the Shell project, you will see that it does not contain references to the module projects. In other words, the Shell has no knowledge of the modules that it hosts.

The TaskButton controls are loaded and activated at application startup, when modules are discovered and loaded by Prism. Each module’s RibbonTab and its views are registered with the Unity container, but are not loaded until the user navigates to the module. As controls are loaded for one module (which I refer to as ‘activating’ the module), the controls for the other module are unloaded (the module is ‘deactivated’). All TaskButton controls remain loaded and active at all times.

Bootstrapper: The third piece to the puzzle is the Bootstrapper, which controls the process of configuring the application at initial startup. The demo app’s Bootstrapper is conventional, and should be self-explanatory.

DI Container: The final element in a Prism app is a Dependency Injection (DI) container, also referred to simply as a Container. A DI container is essentially a factory that can create objects for any type that is registered with the container. If you aren’t familiar with containers, spend a little time learning how they work before continuing on. If you have never used a container before, you will be amazed at the extent to which they simplify creating complex objects.

Prism 4 natively supports two DI containers: Unity 2.0, and the Managed Extensibility Framework that ships with .NET 4. However, Prism is container-agnostic—it can support other containers (such as Windsor Castle), but you will need to find or write an adapter class so that Prism can communicate with the container.

There is a lot of debate over which container is best—I think they are all pretty good, and the choice is largely one of personal preference. I have used Unity for a while now, so the demo app uses Unity 2.0. The demo app should be adaptable to another container without too much difficulty.

MVVM pattern: The demo app follows the MVVM pattern. If you aren’t familiar with the pattern, see Chapter 5 of the Developer’s Guide to Microsoft Prism. The demo app uses the view-first approach to MVVM. The nomenclature is a bit confusing, because in this approach, the View is created first, and either it instantiates its View Model, or another component injects the View Model into the View. In either event, the View must have knowledge of its View M, but the View Model is totally ignorant of the View that uses it. The result is that the View has a dependency on the View Model.

Here is why I prefer the View-first approach: The View Model creates an API that defines a contract between the View Model and any View that uses it. So long as the View complies with this contract, one can change the View without re-opening the View Model. Designers (and clients) are notorious for playing around with Views, and as a result, Views are highly volatile. If we follow the principle that the more volatile component should depend on the less volatile component, then the View should depend on the View Model.

Stated another way, once the API is settled, the client and designer can play with the UI as much as they want without disrupting the rest of the application, so long as they adhere to the contract.

Note that the demo app does not implement View Models for every module view. The Workspace and Navigator views don’t do anything that requires a View Module, and the Ribbon isn’t wired up. So, the only View Models in the demo app are very simple ones for the modules’ TaskButton controls.

The Application Checklist

If you would like a step-by-step description of how to set up a Prism application, you can turn to the companion article, A Prism 4 Application Checklist. As was noted above, I used the checklist to set up the demo app, so it will give you a good walkthrough of how it was developed.

If you don’t need the walkthrough, then you can continue here, where we will turn our consideration to the specific issues presented in the demo app.

The TaskButtons

The TaskButton controls are actually fairly straightforward. Each module loads its TaskButton into the Task Button Region at startup, when the Bootstrapper populates the module catalog and loads the application’s modules. The TaskButton controls for all modules are available immediately, and they remain activated regardless of which module is active.

TaskButton XAML: Each module’s TaskButton is defined as a View. The TaskButton is wrapped in a UserControl, which facilitates adding margins between buttons in the Shell. The UserControl markup is fairly simple:

The UserControl contains a TaskButton control, a custom control derived from a RadioButton. The TaskButton is bound to a couple of View Model command properties:

The Command property manages the navigation that the TaskButton invokes when clicked. It is bound to an ICommand object and is discussed in detail later in this article.

The IsChecked property controls whether the button is selected.

It is worth noting at this point that I prefer to use fully-articulated ICommand objects, rather than the DelegateCommand feature provided by Prism. That means that each of the commands in my Prism application is contained in a separate ICommand class, which I store in a separate Commands folder in each project. I like the way that ICommand classes segregate my command code, and I find that the approach keeps my View Models relatively uncluttered.

Task Button View Models: You can see an example of this approach in the commands for the two modules of the demo project. Each View Model’s constructor calls an Initialize() method for the View Model. The Initialize() method instantiates all command properties in the View Model to corresponding ICommand objects, like this:

And as we saw above, the TaskButton that uses the View Model is bound to the ShowModuleAView command property.

Registering the Task Buttons with Prism: Now let’s switch to the module initializer classes. The Initialize() method for each initializer class registers its module’s Views with Prism. The Task Button View is registered with the Prism Region Manager, so that it will load and become available immediately and remain available throughout the application’s lifetime:

The Shell’s task button region: In the Shell, TaskButtonRegion is simply a StackPanel with a BorderControl to provide the horizontal divider at the top of the region, and an ItemsControl to hold the TaskButton controls.

The Ribbon Control

The Ribbon control presents some interesting problems. The Quick Access Toolbar and the Application and Home tabs of a Ribbon control typically provide functionality that is available across the entire application. To avoid duplication, this functionality should be housed in the Shell. However, modules frequently need to add their own functionality to the Ribbon, and to avoid tight coupling, this functionality should be located in each module. The solution is to have each module add its own RibbonTab control to the Ribbon.

The RibbonRegionAdapter: Unfortunately, the Ribbon cannot natively host a Prism region. Prism only defines a few region controls, most notably the ContentControl (for individual views) and the ItemsControl (for multiple controls). The good news is that Prism can be extended to allow other controls to host regions through the use of Region Adapters. The RibbonRegionAdapter performs this function for the Ribbon. Region Adapters are documented in Appendix E of the Developer’s Guide to Microsoft Prism. The code for the RibbonRegionAdapter is included as Appendix A to this article.

The Bootstrapper registers the Region Adapter in a ConfigureRegionAdapterMappings() override:

Setting up the Ribbon: Note that the Ribbon’s Application Menu, Quick Access Toolbar, and Home tab are defined in the Shell. In a production application, the Ribbon items would be wired up to ICommand properties in the ShellWindow View Model, in the same manner as the TaskButton objects. To keep things simple, the demo app’s Ribbon items aren’t wired to anything.

Each module defines a View for the RibbonTab that it loads:

<ribbon:RibbonTabx:Class="Prism4Demo.ModuleA.Views.ModuleARibbonTab"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:ribbon="clr-namespace:Microsoft.Windows.
Controls.Ribbon;assembly=RibbonControlsLibrary"mc:Ignorable="d"Header="Module A"><!-- See code-behind for implementation
of IRegionMemberLifetime interface. This interface
causes the RibbonTab to be unloaded from the Ribbon when we switch views. --><ribbon:RibbonGroupHeader="Group A1"><ribbon:RibbonButtonLargeImageSource="Images\LargeIcon.png"Label="Button A1"/><ribbon:RibbonButtonSmallImageSource="Images\SmallIcon.png"Label="Button A2"/><ribbon:RibbonButtonSmallImageSource="Images\SmallIcon.png"Label="Button A3"/><ribbon:RibbonButtonSmallImageSource="Images\SmallIcon.png"Label="Button A4"/></ribbon:RibbonGroup></ribbon:RibbonTab>

Note that we don’t wrap the RibbonTab in a UserControl. The View class derives from RibbonTab, instead of UserControl, as shown below:

This approach is not optional. If we were to wrap the RibbonTab in a UserControl, it wouldn’t appear in the Ribbon when Prism loads it.

The IRegionMemberLifetime interface: Note also that the ModuleARibbonTab class implements the IRegionMemberLifetime interface. This interface is provided by Prism, and it controls whether a View is removed from a region when the user navigates away from the View. For example, the RibbonRegionAdapter acts like an ItemsControl, in that it can display multiple RibbonTab controls at one time. Without the IRegionMemberLifetime interface, it would load the RibbonTab for Module B, without unloading the tab for Module A, when the user navigates from Module A to Module B. As a result, the user would see both modules’ RibbonTab controls.

But the behavior we want is for Module A’s RibbonTab to be unloaded when we navigate to Module B so that only the active module’s RibbonTab is shown at any time. That’s the task that the IRegionMemberLifetime interface performs. The interface consists of a single property, KeepAlive. If we set this property to false, then the implementing View is unloaded when the user navigates away from it.

As a result, when we click on the Module A Task Button, Module A’s Ribbon Tab appears, and when we click on the Task Button for Module B, the Ribbon Tab is replaced with that of Module B.

The IRegionMemberLifetime interface can be implemented on a View or on a View Model. In the demo app, I implemented the interface on the View, because the KeepAlive property value is hard-coded, and we don’t have to interact with the property in code. If we did have code interaction (if we needed to change the value of the KeepAlive property at run-time), then the interface would be implemented on the View Model.

Registering the RibbonTab controls: The RibbonTab controls are registered with Prism differently than the TaskButton controls. We don’t want the RibbonTab controls to be available until the user navigates to the host module, so we register the RibbonTab controls with the Unity container, rather than with the Region Manager:

Note that if you are using the Unity 2.0 container, it has a quirk that you need to deal with. By default, Unity resolves all requests as System.Object types. To get the correct type when a View is requested, you have to register the type using a type-mapping overload, with System.Object as the TFrom type parameter, and the actual type of the View as the TTo type parameter:

container.RegisterType<Object, ModuleARibbonTab>("ModuleARibbonTab");

Otherwise, your module will load, but at most it will only show the string “System.Object”.

The Workspace and Navigator Views

As I mentioned above, the Workspace and Navigator Views for both modules are simple placeholders. The Workspace and Navigator regions in the Shell are declared as ContentControl objects, so we could easily skip implementing the IRegionMemberLifetime interface on these Views—only one View can be shown at a time in a ContentControl. To make it clear that these Views should be removed when they are not active, I went ahead and implemented the interface on the Workspace and Navigator Views.

Navigation

Prism 4 added a new Navigation API that by itself would justify the hassle of an upgrade:

Navigation has been simplified by the addition of the RequestNavigate() method.

Parameters can now be passed during navigation.

The RequestNavigate() method can specify a callback method to be invoked when navigation has completed.

Prism can now inform a View that the user is navigating to it or away from it.

It is now much easier to re-use an existing View to display new information.

The demo app uses Prism 4 navigation to load and unload the Views of its modules. The navigation code is triggered when the user clicks a TaskButton, which is bound to a command property in its own View Model. This View Model is contained in the same module as the TaskButton, and it can be found in the module’s ViewModels folder.

Here is the XAML that binds the TaskButton:

<fsc:TaskButtonCommand="{Binding ShowModuleAView}".../>

As we noted earlier, the Command property is initialized in the View Model as an instance of an ICommand class contained in the module’s Commands folder. The ICommand class contains the actual navigation code, in its Execute() method:

The navigation requests are relatively straightforward. We simply call IRegionManager.RequestNavigate() and pass it a region name and a URI object containing the name of the View we want to load.

Communication Between Modules

The app’s TaskButtons should be single-select. That is, when one in selected, the others should be de-selected. Normally, this would be handled automatically by the TaskButton object (since it is derived from RadioButton), but unfortunately, that feature doesn’t work when buttons are inserted into a Prism region. So, we will have to code the task.

The navigation callback method: Note that in the last navigation request, we pass an additional parameter, NavigationCompleted. This parameter is the name of a callback method that Prism should invoke when navigation is completed. The demo app uses this callback method, together with a Composite Presentation Event (CPE), to implement single-select behavior on the TaskButtons.

Composite Presentation Events: CPEs are the key to loosely-coupled communication between Prism modules. They enable any Prism component (the Shell, modules) to communicate with any other component, without having direct knowledge of it. CPEs use a Publish/Subscribe model based on event objects that are derived from the CompositePresentationEvent<T> class. Here is the declaration for the NavigationCompletedEvent, which the demo app uses to trigger single-select behavior:

CPEs are designed to cross assembly boundaries, which normal .NET events can’t do. Prism’s Event Aggregator provides an event registry to make that possible:

When a CPE is declared, it is added to the Event Aggregator.

Prism components that wish to be notified of an event subscribe to its CPE in the Event Aggregator.

A Prism component raises a CPE by publishing it to the Event Aggregator, which notifies all subscribers to the event.

Since a CPE is used across the entire application, its class is located in the demo app’s Common project, in the Events folder. Each module has a reference to this project, but the Common project knows nothing of the modules that subscribe to it. Accordingly, we can add and remove modules without reopening the Common project, maintaining loose coupling in the application.

A CPE class declaration simply assigns a type to the CompositePresentationEvent<T> class. The type indicates the ‘payload’ that the event will carry when it is published. In the NavigationCompletedEvent, the type is a string—we only need to communicate the name of the event’s publisher. But in a more complex app, we could pass a custom type that contained whatever data that needs to be passed with the event.

Implementing single-select behavior: Let’s return to the task at hand. We need to implement single-select behavior on the demo app’s Task Buttons. Here is how we accomplish that task:

First, we declare the CPE, as shown above. Note that we do not have to explicitly register the CPE with the Event Aggregator.

Next, components that wish to be notified when the event is raised subscribe to the event in the Event Aggregator. In the demo app, it is the View Model associated with each Task Button that needs to be notified. So, the View Model subscribes to the CPE in its Initialize() method:

As we saw above, clicking a Task Button triggers a series of navigation requests in an associated ICommand object. The last of these requests passes the name of a callback method, NavigationCompleted():

The CPE event handler: When the CPE is published, the Event Aggregator notifies all subscribers to the event—in this case, the Task Button View Models—and those subscribers handle the event. Here is the event handler in Module A:

#region Event Handlers
///<summary>/// Sets the IsChecked state of the Task Button when navigation is completed.
///</summary>///<paramname="publisher">The publisher of the event.</param>privatevoid OnNavigationCompleted(string publisher)
{
// Exit if this module published the event
if (publisher == "ModuleA") return;
// Otherwise, uncheck this button
this.IsChecked = false;
}

The event handler checks first to see which module published the event. If the event was published from its host module, then the handler does nothing. Its Task Button was clicked, and it needs no changes. But if the event was published by another module, then its Task Button should have its IsChecked state set to false. The event handler sets this property in the View Model. The IsChecked property of the module’s Task Button is bound to this property, so the button deselects. The net result is that all Task Buttons, except the one that was clicked, are deselected.

Is it worth it?: We use the CPE communication model to perform a relatively simple task. But the model is highly scalable, and it can be used to perform tasks of nearly any complexity by developing the appropriate data class to act as the CPE’s payload. If it seems like an overly-complex series of steps to solve a simple problem, consider the benefits that the approach provides:

First, events can be passed across assembly boundaries.

Second, and most importantly, Prism components can communicate with each other with minimal knowledge of the other component required. In most cases, project references can be reduced to a single reference to a Common project, which has no knowledge on the modules that depend upon it.

Close attention to the direction of dependencies will preserve loose coupling of all of the components of a Prism application, allowing them to be developed and tested independently of each other. And it is well-known that it is far easier to develop a set of small projects than a single large one.

Conclusion

Hopefully, this article, along with its companion piece, will prove helpful in creating the essential plumbing for Prism View-switching applications. As always, I welcome the peer review of other CodeProject users. Please let me know of any errors that you may find, or any suggestions you would like to make, by posting in the Comments section at the end of this article.

Appendix A: The RibbonRegionAdapter

The code for the RibbonRegionAdapter used in the demo app is shown below. The class can be found in the Utility folder of the Shell project.

I want to thank Scott, from La Crosse, Wisconsin, who posted his code for a Ribbon Region Adapter on the Code Review web site. This RibbonRegionAdapter in the demo app is derived from his work.

Share

About the Author

David Veeneman is a financial planner and software developer. He is the author of "The Fortune in Your Future" (McGraw-Hill 1998). His company, Foresight Systems, develops planning and financial software.

Comments and Discussions

Bug in FsTaskButton.dll causes "XamlParseException was unhandled - Input string was not in a correct format."

The metod FsTools.Converters.LuminanceConverter.Convert(...) in FsTaskButton.dll does not pass the culture in to the System.Double.Parse(String s) method. So if the current culture have something other than dot as decimal character (such as comma) the conversion will fail with the above exception.

If the the developer of FsTaskButton.dll have time to fix it: Change the call System.Double.Parse(parameter) to System.Double.Parse(parameter, culture)

Workaround to get the example going now: Force the current culture to be en-US at the start of the application: Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");