span.sup { vertical-align:text-top; }

Prism

Patterns For Building Composite Applications With WPF

Glenn Block

This article discusses:

  • Composite application basics
  • The bootstrapper and module initialization
  • Regions and RegionManager
  • Views, commands, and events
This article uses the following technologies:
Composite Application Guidance for WPF

Contents

The Problem: Monolithic Apps
Composite Applications
Composite Application Guidance
The Bootstrapper and Containers
Module Initialization
Using the Bootstrapper
Modules and Services
Regions and RegionManager
Locally Scoped Regions
Views
Separated Presentation
Commands
Events
Wrapping Up

Technologies such as Windows® Presentation Foundation (WPF) and Silverlight™ provide developers with a simple, declarative means of quickly and easily delivering applications with rich user experiences. But while these technologies aid in further separating the presentation layer from the logic layer, they don't solve the age-old problem of building a maintainable app.

In smaller projects, it's reasonable to expect that a developer of moderate experience can design and build an application that can be readily maintained and expanded upon. However, as the number of moving parts—and the number of people working on those parts—increases, keeping the project under control becomes exponentially more difficult.

Composite applications are a solution to this problem. In this article I will explain what a composite application is and how you can build one that takes advantage of the capabilities of WPF. Along the way I will introduce you to the new Composite Application Guidance for WPF (formerly code-named "Prism") from the patterns & practices team at Microsoft.

The Problem: Monolithic Apps

Let's consider an example to understand the need for composite applications. Contoso Financial Investments provides an application for managing your stock investment portfolio. Using the application, a user can view current investments and related news items for those investments, add items to a watch list, and perform buy/sell transactions.

If you built this as a traditional WPF application with user controls, you would start with a top-level window and add user controls for each of the different aforementioned functions. In this case, you'd have user controls such as PositionGrid, PositionSummary, TrendLine, and WatchList (see Figure 1). Each user control is laid out at design time within the main window either manually in XAML or using a designer such as Expression Blend™.

fig01.gif

Figure 1 User Controls in a Monolithic App

You would then use RoutedEvents, RoutedCommands, and data binding to wire everything up. For more on this topic, see the Brian Noyes article, "Understanding Routed Events and Commands in WPF," in this issue ( msdn.microsoft.com/magazine/cc785480 ). The Position­Grid has an associated RoutedCommand for selection. In the command's Execute handler, a TickerSymbol­Selected event occurs whenever a position is selected. The TrendLine and NewsReader are wired up in order to listen to the TickerSymbolSelected event and render their contents based on the selected ticker symbol.

In this case, the application is tightly coupled to each of the controls. There is a significant amount of logic in the UI for coordinating the different pieces. There are also interdependencies among the controls.

Due to these dependencies, there's no easy way to break up the application into a form where you can develop each of the different pieces separately. You could put all the user controls in a separate assembly to improve maintainability, but that only moves the problem from the main app to the control assembly. It's very difficult in this model to make significant changes or introduce new functionality.

Now let's complicate matters by adding two new business requirements. The first is to add a fund notes screen that displays personal notes about the selected fund when it is double-clicked. The second is to add a new screen that displays a list of hyperlinks related to the selected fund. Due to time constraints, these features must be developed in parallel by different teams.

Each team develops separate controls: FundNotes and FundLinks. To add both controls to the same control assembly, they must each be added to the control project. More important, they must be added to the main form, which means that changes to both code and XAML from each control must be merged with the main form. This type of operation can be incredibly brittle, especially for existing applications.

How do you check all of these changes back into the main application? By the time you're done, you will have potentially spent a significant amount of time doing merges and diffs in source control. If you make any mistakes applying the changes or you accidentally overwrite something, you'll be left with a broken application. The remedy lies in rethinking the application design.

Composite Applications

A composite app consists of loosely coupled modules that are dynamically discovered and composed at run time. Modules contain visual and non-visual components representing the different vertical slices of the system (see Figure 2). The visual components (views) are composed in a common shell that acts as the host for all of the app's content. Composites provide services that tie these module-level components together. Modules can offer additional services that relate to the specific functionality of the app.

fig02.gif

Figure 2 Components of a Composite App

At a high level, a composite application is an implementation of the Composite View design pattern, which describes a recursive UI structure of views containing children that are themselves views. The views are then composed by a mechanism—usually at run time, in contrast to being statically composed at design time.

To illustrate the benefits of the pattern, think of an order entry system in which you have multiple instances of an order. Each instance may involve significant complexity to display the header, details, shipping, and receipts. As the system evolves, it may need to display more information. Imagine also that parts of the order display differently depending on the type of order.

If such a screen is built statically, then you may end up with a lot of conditional logic for displaying the different parts of the order. Also, adding new functionality increases the likelihood that the existing logic will break. If we implement this as a composite view, however, then we compose the order screen dynamically of only the relevant pieces. This means that we can do away with conditional display logic and add new child screens without modifying the order view itself.

Modules contribute the views from which the main composite view (otherwise known as the shell) is created. Modules never directly reference each other, nor do they directly reference the shell. Instead, they utilize services to communicate with one another and with the shell in order to respond to user actions.

Having a system composed of modules provides several benefits. Modules can aggregate data coming from different back-end systems within the same application. Additionally, the system can more easily evolve over time. As the system requirements change, new modules can be added to the system with much less friction than in a non-modular system. Existing modules can then be evolved more independently, thus improving testability. Finally, modules can be developed, tested, and maintained by different teams.

Composite Application Guidance

The Microsoft patterns & practices team recently shipped the first version of Composite Application Guidance for WPF (available at microsoft.com/CompositeWPF). The new guidance was designed to leverage the capabilities and programming model of WPF. At the same time, the team also improved upon the design of previous composite application guidance based on feedback from internal product teams, customers, and the .NET community.

The Composite Application Guidance for WPF includes a reference implementation (the Stock Trader app discussed previously), a Composite Application Library (CAL), quickstart applications, and design and technical documentation.

The CAL provides the services and plumbing for building composite apps. It uses a composition model that allows each of its services to be used piecemeal or together as part of a CAL-designed app. Each service is also easily replaceable without recompilation of the CAL. For example, the CAL ships with an extension that uses the Unity Application Block for dependency injection but lets you replace this with your own dependency injection service.

Quickstarts provide small, focused apps that demonstrate using each of the different CAL components. They are designed to help you ramp up on concepts without having to grasp everything at once.

In the remainder of the article, I will explore several technical concepts of composites that are illustrated in the Stock Trader reference implementation. All the code for the article is available in the Composite Application Guidance for WPF download from MSDN® at msdn.microsoft.com/library/cc707819.

The Bootstrapper and Containers

When you build composite applications using the CAL, you must first initialize several core composition services. This is where the bootstrapper comes in. It performs all the necessary functions for composition to occur (illustrated in Figure 3). In many ways it is the Main method of a CAL application.

Figure 3 Bootstrapper Initialization Tasks

First, the container is initialized. By container I mean an inversion of control (IoC)/dependency injection (DI) container. If you are unfamiliar with this term, check out the MSDN Magazine article, "Tame Your Software Dependencies for More Flexible Apps," by James Kovacs ( msdn.microsoft.com/magazine/cc337885 ).

Containers play a key role in a CAL app. The container is the store of all the app services used in composition. It is responsible for injecting theses services wherever they are needed. By default, the CAL includes an abstract UnityBootstrapper that uses the Unity framework from patterns & practices as the container. However, CAL was built to work with other containers such as Windsor, Structure Map, and Sprint.NET. None of the classes in the CAL (other than the Unity extensions) depend on a specific container.

As the container is being configured, several core services used for composition are automatically registered, including a logger and an event aggregator, and the base bootstrapper lets you override any of these. For example, one service that is automatically registered is IModuleLoader. If you override the ConfigureContainer method in the bootstrapper, you can register your own module loader.

protected override void ConfigureContainer() {
  Container.RegisterType<IModuleLoader, MyModuleLoader>();
  base.ConfigureContainer();
}

If you don't want services to be registered by default, you can turn that off also. Simply call the Run method overload on the bootstrapper, passing a false value for the useDefaultConfiguration parameter.

Next, region adapters are configured. A region is a named location (usually a container, such as a panel) in the UI where modules can inject UI­Elements. Region adapters handle wiring different region types to be accessed. These adapters are mapped in a RegionAdapterMappings singleton instance in the container.

At this point the shell is created. The shell is the top-level window where regions are defined. Instead of declaring it in App.Xaml, it is created by the CreateShell method from within your application-specific bootstrapper. This is to ensure that the bootstrapper's initialization is completed before the shell is shown.

It may be surprising to know that you don't actually need to have a shell in your application. For example, you may have an existing WPF application to which you want to add some CAL functionality. Instead of having CAL control the entire screen, you may want to add a panel that will be a top-level region. In this instance, you don't need to define a shell. Your bootstrapper can simply ignore displaying the shell if it is not defined.

Module Initialization

Finally, modules are initialized. A module in a CAL application is a unit of separation within a composite that can be deployed as a separate assembly, though this is not a requirement. In a CAL app, the module is what contains most of the functionality.

Loading modules is a two-step process that involves two services: IModuleEnumerator and IModuleLoader. The enumerator is responsible for locating available modules. It returns several collections of ModuleInfo objects that contain metadata about a module. UnityBootstrapper contains a GetModuleEnumerator that should be overridden to return the correct enumerator; otherwise, an exception will be thrown at run time. The CAL includes enumerators for locating modules statically, from a directory scan, and from configuration.

For loading, the CAL includes a ModuleLoader that is used by UnityBootstrapper by default. It loads each of the module assemblies (if they have not been loaded) and then initializes them. Modules can specify dependencies on other modules. The ModuleLoader will build a dependency tree and initialize modules in the correct order based on these specifications.

Using the Bootstrapper

Because UnityBootstrapper is an abstract class, StockTraderRIBootstrapper overrides it (see Figure 4). The bootstrapper has several protected virtual methods that allow you to insert your own application-specific functionality.

Figure 4 Stock Trader Bootstrapper

public class StockTraderRIBootstrapper : UnityBootstrapper {
  private readonly EntLibLoggerAdapter _logger = new EntLibLoggerAdapter();

  protected override IModuleEnumerator GetModuleEnumerator()  {
    return new StaticModuleEnumerator()
    .AddModule(typeof(NewsModule))
    .AddModule(typeof(MarketModule))
    .AddModule(typeof(WatchModule), "MarketModule")
    .AddModule(typeof(PositionModule), "MarketModule", "NewsModule");
  }

  protected override ILoggerFacade LoggerFacade  {
    get { return _logger; }
  }

  protected override void ConfigureContainer()  {
    Container.RegisterType<IShellView, Shell>();

    base.ConfigureContainer();
  }

  protected override DependencyObject CreateShell()  {
    ShellPresenter presenter = Container.Resolve<ShellPresenter>();
    IShellView view = presenter.View;
    view.ShowView();
    return view as DependencyObject;
  }
}

The first thing you should notice is that an EntlibLoggerAdapter is defined and stored in the _logger variable. The code then overrides the LoggerFacade property to return this logger, which implements ILoggerFacade. In this case I am using the Enterprise Library's logger, but you can easily replace this to use your own adapter.

Next, the GetModuleEnumerator method is overridden to return a StaticModuleEnumerator that is pre-populated with the four reference implementation modules. The reference implementation uses static module loading, but there are several other ways to enumerate modules including directory lookup and configuration. To use a different method of enumeration, simply change this method to instantiate a different enumerator.

ConfigureContainer is then overridden to register the shell. Additional services can also be programmatically registered at this point if needed. Finally, CreateShell is overridden with the specific logic to create the shell. In this case the code is implementing the Model View Presenter pattern, so the shell has an associated presenter.

The bootstrapper shown in Figure 4 illustrates a common pattern when building a CAL app from scratch, which is to create an application-specific bootstrapper. A major benefit to this approach is that an application-specific bootstrapper enhances the testability of your application. The bootstrapper does not have any dependencies on WPF other than DependencyObject. You can, for example, create a test bootstrapper that inherits from the app-specific bootstrapper and overrides the CreateContainer method to return an AutoMocking container, thus mocking out all of your services.

Additionally, because the bootstrapper provides a single point of entry for initializing composition and because the CAL does not rely on inheritance from framework classes in your application, you can integrate CAL into your existing applications with less friction than in previous frameworks. Note that the CAL itself does not depend on the bootstrapper at all, so you can refrain from using it if a bootstrapper does not fit your needs.

Modules and Services

As I mentioned earlier, in a composite application built using the CAL, the bulk of the application logic lives in the modules. The Stock Trader reference implementation includes four modules:

  • NewsModule provides related news feeds for each fund that is selected.
  • MarketModule provides trend data, as well as real-time market data, for the selected fund.
  • WatchModule provides a Watch List that displays a list of funds you are monitoring.
  • PositionModule displays the list of funds in which you have invested and allows you to perform buy/sell transactions.

In the CAL, a module is a class that implements the IModule interface. This interface has only one method, called Initialize. If the bootstrapper is equivalent to the application's Main method, then the Initialize method is the Main for each module. For example, here you can see the Initialize method for WatchModule:

public void Initialize() {
  RegisterViewsAndServices();

  IWatchListPresentationModel watchListPresentationModel = 
    _container.Resolve<IWatchListPresentationModel>();
  _regionManager.Regions["WatchRegion"].Add(watchListPresentationModel.View);
  IAddWatchPresenter addWatchPresenter = 
    _container.Resolve<IAddWatchPresenter>();
  _regionManager.Regions["MainToolbarRegion"].Add(addWatchPresenter.View);
}

Before getting into the module specifics, two things that are worth discussing here are the references to _container and _region­Manager. If the interface doesn't have these defined, then where do they come from? Am I hardcoding logic within the module to go find these dependencies?

Fortunately, the answer to that last question is no. This is where having an IoC container comes to the rescue. When a module is loaded, it is resolved from the container, which also injects any specified dependencies into the module's constructor:

public WatchModule(IUnityContainer container, 
  IRegionManager regionManager) {
  _container = container;
  _regionManager = regionManager;
}

Here you can see that the container itself is injected into the module. This is possible because the bootstrapper registers the container in its ConfigureContainer method:

Container.RegisterInstance<IUnityContainer>(Container);

Giving modules direct access to the container allows the module to register and resolve dependencies from the container in an imperative fashion.

You don't need to have this imperative registration. Instead, you can put all services into a global configuration. Doing this would mean that all services must be registered when the container is initially created. However, most modules have module-specific services. And by keeping the registration in the module, those module-specific services are only registered if the module is loaded.

In the case of the module you were shown earlier, the first call is to Register­ViewsAndServices. In this method, each of the specific views for WatchModule is registered in the container along with an interface:

protected void RegisterViewsAndServices() {
  _container.RegisterType<IWatchListService, WatchListService>(
    new ContainerControlledLifetimeManager());
  _container.RegisterType<IWatchListView, WatchListView>();
  _container.RegisterType<IWatchListPresentationModel, 
    WatchListPresentationModel>();
  _container.RegisterType<IAddWatchView, AddWatchView>();
  _container.RegisterType<IAddWatchPresenter, AddWatchPresenter>();
}

Requiring that the interface is specified fosters separation of concerns and allows other modules in the system to interact with the view without requiring a direct reference. Putting everything in the container allows each of the dependencies for the different objects to be injected automatically. WatchListView, for example, is never directly instantiated in the code—instead it is loaded as a dependency in the WatchListPresentationModel constructor:

public WatchListPresentationModel(IWatchListView view...)

In addition to the views, WatchModule also registers the WatchListService, which contains the list data and is used for adding new items. The specific views being registered are the watch list and the watch list toolbar. After registration, the region manager is used and both views that were just registered are added to the Watch­Region and ToolbarRegion.

Regions and RegionManager

Modules by themselves are not that interesting unless they can render content to the UI. In the previous section, you saw that the Watch module uses a region to add its two views. Using a region removes the need for the module to have any specific reference to the UI or have any knowledge of how the views injected will be laid out and displayed. For an example of this, Figure 5 depicts the regions into which WatchModule injects.

fig05.gif

Figure 5 Injecting Modules into the App

CAL includes a Region class that is basically a handle that wraps these locations. The Region class contains a Views property that is a read-only collection of the views to be displayed within the region. Views are added to the region by calling the region's add method. The Views property actually contains a generic collection of objects; it is not limited to containing only UIElements. This collection implements INotifyPropertyCollectionChanged so that the UIElement associated with the region can bind to it and observe changes.

You might be wondering why the Views collection is weakly typed rather than being of type UIElement. Thanks to the rich template support in WPF, you can actually add models directly to the region. That model can then have an associated Data­Template defined for it that will define the rendering for the model. If the item added is a UIElement or a user control, then WPF will just render it as is. This means that if you have a region that is a tab of open orders, you can simply add the OrderModel or OrderPresentation­Model to the region and then define a custom DataTemplate to control the display rather than having to create a custom OrderView user control.

Regions can be registered in two ways. The first way is defined in XAML by annotating a UIElement with a RegionName attached property. For example, the XAML for defining the MainToolbarRegion is as follows:

<ItemsControl Grid.Row="1" Grid.Column="1" 
  x:Name="MainToolbar" 
  cal:RegionManager.RegionName="MainToolbarRegion">
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <WrapPanel />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
</ItemsControl>

Once a region has been defined via XAML, it will automatically be registered at run time with RegionManager, one of the composition services registered by the bootstrapper. RegionManager is essentially a Dictionary where the key is the name of the region and the value is an instance of the IRegion interface. The RegionManager attached property uses a RegionAdapter to create this instance.

Note, however, that if using attached properties doesn't work for you or you need to register additional regions dynamically, you can manually create an instance of the Region class or derived class and add it to RegionManager's Regions collection.

Notice in the XAML snippet that the MainToolbarRegion is an ItemsControl. CAL ships with three region adapters that are registered by the bootstrapper—ContentControlRegionAdapter, ItemsControlRegionAdapter, and SelectorRegionAdapter. Adapters are registered with a RegionAdapterMappings class. Adapters all inherit from RegionAdapterBase, which implements the I­RegionAdapter interface.

Figure 6 shows the implementation of ItemsControlRegionAdapter. How the adapter itself is implemented depends completely on the type of UIElement to which it is being adapted. In the case of ItemsControlRegionAdapter, the bulk of its implementation is in the Adapt method. The Adapt method accepts two parameters. The first parameter is an instance of the Region class itself that Region­Manager creates. The second parameter is the UIElement that represents the region. The Adapt method performs the relevant plumbing to ensure that the region works with the element.

Figure 6 ItemsControlRegionAdapter

public class ItemsControlRegionAdapter : RegionAdapterBase<ItemsControl> {
  protected override void Adapt(IRegion region, ItemsControl regionTarget) {
    if (regionTarget.ItemsSource != null || 
      (BindingOperations.GetBinding(regionTarget, 
      ItemsControl.ItemsSourceProperty) != null))
      throw new InvalidOperationException(
        Resources.ItemsControlHasItemsSourceException);

    if (regionTarget.Items.Count > 0) {
      foreach (object childItem in regionTarget.Items) {
        region.Add(childItem);
      }
      regionTarget.Items.Clear();
    }
    regionTarget.ItemsSource = region.Views;
  }

  protected override IRegion CreateRegion() {
    return new AllActiveRegion();
  }
}

In the case of an ItemsControl, the adapter automatically removes any child items from the ItemControl itself and then adds them to the region. The region's Views collection is then bound to the ItemsSource of the control.

The second method overridden is CreateRegion, which returns a new AllActiveRegion instance. Regions can contain views that are active or inactive. In the case of the ItemsControl, all of its items are active all the time because it does not have a notion of selection. However, in the case of other types of regions, such as Selector, only one item is selected at a time. A view can implement the IActiveAware interface so that it is notified by its region that it is selected. Whenever the view is selected, it will have its IsSelected property set to true.

Throughout the development of your composite application, you may have to create additional regions and region adapters, such as one that will adapt a control from a third-party vendor. To register your new region adapter, override the ConfigureRegionAdapter­Mappings method in the bootstrapper. Once that is done, add code similar to the following:

protected override RegionAdapterMappings 
  ConfigureRegionAdapterMappings() {
  RegionAdapterMappings regionAdapterMappings = 
    base.ConfigureRegionAdapterMappings();

  regionAdapterMappings.RegisterMapping(typeof(Selector), 
    new MyWizBangRegionAdapter());

  return regionAdapterMappings;
}

Once the region has been defined, it can be accessed from any class within the application by grabbing hold of the Region­Manager service. The common way to do this in a CAL application is to have the dependency injection container inject the RegionManager into the constructor of the class that needs it. To add a view or model to a region, simply call the region's Add method. When you add a view, you can pass an optional name:

_regionManager.Regions["MainRegion"].Add(
  somePresentationModel, "SomeView");

You can later use that name to retrieve the view from the region by using the region's GetView method.

Locally Scoped Regions

By default, there is only one RegionManager instance in your application, thereby making every region globally scoped. This addresses a wide set of scenarios, but there are situations where you may want to define a region that exists only at a particular scope. An example of where you might want to do this is when your application has a view for employee details where multiple instances of the view can be displayed at the same time. If these views are fairly complex, they behave like mini-shells or CompositeViews. In these cases, you may want each view to have its own regions, as the shell does. The CAL allows you to define a local RegionManager for a view so that any regions defined within it or its child views are automatically registered in that local region.

The UI Composition quickstart included with the guidance illustrates this employee scenario (see Figure 7). In the quickstart there is an employee list. As you click on each employee, you see the associated employee detail. For each employee selection, a new EmployeeDetailsView is created for that employee and added to the DetailsRegion (see Figure 8). This view contains a local TabRegion, into which the EmployeesController injects a ProjectListView in its OnEmployeeSelected method.

fig07.gif

Figure 7 UI Composition via RegionManager

Figure 8 Creating a New Employee View

public virtual void 
  OnEmployeeSelected(BusinessEntities.Employee employee) {
  IRegion detailsRegion = 
    regionManager.Regions[RegionNames.DetailsRegion];
  object existingView = detailsRegion.GetView(
    employee.EmployeeId.ToString(CultureInfo.InvariantCulture));

  if (existingView == null) {
    IProjectsListPresenter projectsListPresenter = 
    this.container.Resolve<IProjectsListPresenter>();
    projectsListPresenter.SetProjects(employee.EmployeeId);

    IEmployeesDetailsPresenter detailsPresenter = 
      this.container.Resolve<IEmployeesDetailsPresenter>();
    detailsPresenter.SetSelectedEmployee(employee);

    IRegionManager detailsRegionManager = 
      detailsRegion.Add(detailsPresenter.View,
      employee.EmployeeId.ToString(CultureInfo.InvariantCulture), true);

    IRegion region = detailsRegionManager.Regions[RegionNames.TabRegion];
    region.Add(projectsListPresenter.View, "CurrentProjectsView");
    detailsRegion.Activate(detailsPresenter.View);
  }
  else {
    detailsRegion.Activate(existingView);
  }
}

The region is rendered as a TabControl and contains both static and dynamic content. The General and Location tabs are statically defined within the XAML. The Current Projects tab, however, has its views injected.

You can see in the code that a new RegionManager instance is returned from the detailsRegion.Add method. Also notice that I am using the overload of Add that passes in a name for the view and sets the createRegionManagerScope parameter to true. Doing this creates a local RegionManager instance that will be used for any regions defined in the children. The TabRegion itself is defined in the XAML of the EmployeeDetailsView:

<TabControl AutomationProperties.AutomationId="DetailsTabControl" 
  cal:RegionManager.RegionName="{x:Static local:RegionNames.TabRegion}" .../>

Using local regions provides an additional benefit even if you are not using instance regions. You can use them for defining a top-level boundary so that a module does not automatically expose its regions to the rest of the world. To do this, all you need is to add the top-level view for that module into a region and specify for it to have its own scope. Once you do that, you've effectively shielded that module's regions from the rest of the world. It's not impossible to access them, but it's much more difficult.

Without views you would have no need for a composite. Views are the single most important element that you will build within your composite apps, as they are the gateway for your users to the world of functionality that your application provides.

Views are typically the screens of your application. Views can contain other views, thereby becoming composite views. Another use of views is for menus and toolbars. In the Stock Trader, for example, the OrdersToolbar is a view that contains buttons for Submit, Cancel, Submit All, and Cancel All.

WPF supports a much richer notion of a View than what was the convention in the Windows Forms world. In Windows Forms you were basically restricted to using controls as your visual representation. In WPF, this model is still supported and you can create custom user controls for representing your different screens. If you look throughout the Stock Trader application, this is the primary mechanism employed for defining views.

Another approach is to use models. WPF will allow you to bind any model to the UI and then use a DataTemplate to render it. Templates are rendered recursively, meaning that if a template renders an element that binds to a property of the model, that property will be rendered using a template if it is available.

For an example of how this works, let's take a look at the following code sample. This sample implements the same UI as the Composition quickstart but uses models and DataTemplates entirely. There is not one user control in the entire project. Figure 9 illustrates how the EmployeeDetailsView is handled. The view is now a set of three DataTemplates that have been defined in a ResourceDictionary. Everything starts with the Employee­DetailsPresentationModel. Its template declares that it should be rendered as a TabControl. As part of the template, it binds the TabControl's ItemsSource to the EmployeeDetailsPresentationModel's EmployeeDetails collection property. This collection is populated with two pieces of information when the Employee details are constructed:

public EmployeesDetailsPresentationModel() {
  EmployeeDetails = new ObservableCollection<object>();
  EmployeeDetails.Insert(0, new HeaderedEmployeeData());
  EmployeeDetails.Insert(1, new EmployeeAddressMapUrl());
  ...
}

Figure 9 Creating an EmployeeDetailsView

<ResourceDictionary 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:EmployeesDetailsView=
    "clr-namespace:ViewModelComposition.Modules.Employees.Views.EmployeesDetailsView">

  <DataTemplate 
    DataType="{x:Type EmployeesDetailsView:HeaderedEmployeeData}">
    <Grid x:Name="GeneralGrid">
      <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition Width="5"></ColumnDefinition>
        <ColumnDefinition Width="*"></ColumnDefinition>
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
      </Grid.RowDefinitions>
      <TextBlock Text="First Name:" Grid.Column="0" Grid.Row="0">
      </TextBlock>
      <TextBlock Text="Last Name:" Grid.Column="2" Grid.Row="0">
      </TextBlock>
      <TextBlock Text="Phone:" Grid.Column="0" Grid.Row="2"></TextBlock>
      <TextBlock Text="Email:" Grid.Column="2" Grid.Row="2"></TextBlock>
      <TextBox x:Name="FirstNameTextBox" 
        Text="{Binding Path=Employee.FirstName}" 
        Grid.Column="0" Grid.Row="1"></TextBox>
      <TextBox x:Name="LastNameTextBox" 
        Text="{Binding Path=Employee.LastName}" 
        Grid.Column="2" Grid.Row="1"></TextBox>
      <TextBox x:Name="PhoneTextBox" Text="{Binding Path=Employee.Phone}" 
        Grid.Column="0" Grid.Row="3"></TextBox>
      <TextBox x:Name="EmailTextBox" Text="{Binding Path=Employee.Email}" 
        Grid.Column="2" Grid.Row="3"></TextBox>
    </Grid>
  </DataTemplate>

  <DataTemplate 
    DataType="{x:Type EmployeesDetailsView:EmployeeAddressMapUrl}">
    <Frame Source="{Binding AddressMapUrl}" Height="300"></Frame>
  </DataTemplate>

  <DataTemplate DataType="{x:Type
    EmployeesDetailsView:EmployeesDetailsPresentationModel}">
    <TabControl x:Name="DetailsTabControl" 
      ItemsSource="{Binding EmployeeDetails}" >
      <TabControl.ItemContainerStyle>
        <Style TargetType="{x:Type TabItem}" 
          BasedOn="{StaticResource RoundedTabItem}">
          <Setter Property="Header" Value="{Binding HeaderInfo}" />
        </Style>
      </TabControl.ItemContainerStyle>
    </TabControl>
  </DataTemplate>
</ResourceDictionary>

A separate tab will be rendered for each item in the collection. As the first item gets rendered, WPF will use the DataTemplate specified for HeaderedEmployeeData. The HeaderedEmployeeData model contains the employee name and contact info. Its associated template renders the model as a series of labels for displaying the information. The second item will get rendered using the template specified for EmployeeAddressMapUrl, which in this case will render a frame that contains a Web page with a map of where the employee lives.

This is quite a paradigm shift in that it means that the view as you previously knew it only really exists at run time through the combination of the model and its associated template. You can also implement a hybrid of both approaches (as demonstrated in the Stock Trader), where you have user controls that have controls within them that are then bound to models that are rendered through templates.

Separated Presentation

Earlier in this article I mentioned that one of the benefits of building a composite is that it allows the code to be more maintainable and testable. There are several established presentation patterns you can apply within your views to achieve this. Throughout the Composite Application Guidance for WPF, you will see two recurring patterns that are used in the UI: Presentation Model and Supervising Controller.

The Presentation Model pattern assumes a model that contains both the behavior and the data for the UI. The view then projects the presentation model's state "onto the glass."

Behind the scenes the model interacts with business and domain models. The model also includes additional state information such as the selected item or whether an element is checked. The view then binds to the Presentation Model directly and renders it (see Figure 10). The rich support in WPF for data binding, templates, and commands makes the Presentation Model pattern an attractive option for development.

fig10.gif

Figure 10 The Presentation Model Pattern

The Stock Trader application uses Presentation Model judiciously, such as in the position summary:

public class PositionSummaryPresentationModel :
  IPositionSummaryPresentationModel, INotifyPropertyChanged {
  public PositionSummaryPresentationModel(
    IPositionSummaryView view,...) {
    ...
  }

  public IPositionSummaryView View { get; set; }
  public ObservableCollection<PositionSummaryItem> 
    PositionSummaryItems { 
    get; set; }
}

You can see that the PositionSummaryPresentationModel implements INotifyPropertyChanged to notify the view of any changes. The view itself is injected into the constructor through its IPosition­SummaryView interface as the PositionSummaryPresentationModel is resolved from the container. This interface allows the view to be mocked in unit testing. The Presentation Model exposes an observable collection of PositionSummaryItems. These items are bound to the PostionSummaryView and rendered.

Within the Supervising Controller pattern there exists the model, the view, and the presenter; this is illustrated in Figure 11. The model is the data; it is more often than not a business object. The view is a UIElement to which the model directly binds. And lastly, the presenter is a class that contains the UI logic. In this pattern, the view contains very little logic other than delegating to the presenter and responding to callbacks from the presenter to perform simple actions including either displaying or hiding a control.

fig11.gif

Figure 11 The Supervising Controller Pattern

The Supervising Controller pattern is also used in a few instances in the Stock Trader app in favor of Presentation Model. One example is the trend line (see Figure 12). Similar to the PositionSummary­PresentationModel, the TrendLinePresenter is injected with the TrendLineView through the ITrendLine­View interface. The Presenter exposes an OnTickerSymbolSelected method that the view invokes through its delegation logic. In that method, note that the presenter then calls back to the view, invoking its UpdateLineChart and SetChartTitle methods.

Figure 12 Presenting the Trend Line

public class TrendLinePresenter : ITrendLinePresenter {
  IMarketHistoryService _marketHistoryService;

  public TrendLinePresenter(ITrendLineView view, 
    IMarketHistoryService marketHistoryService) {
    this.View = view;
    this._marketHistoryService = marketHistoryService;
  }

  public ITrendLineView View { get; set; }

  public void OnTickerSymbolSelected(string tickerSymbol) {
    MarketHistoryCollection historyCollection = 
      _marketHistoryService.GetPriceHistory(tickerSymbol);
    View.UpdateLineChart(historyCollection);
    View.SetChartTitle(tickerSymbol);
  }
}

One of the challenges that arise when implementing separated presentation is the communication between the view and the presentation model or presenter. There are several approaches to handling this. One that is often implemented is having event handlers in the view that either directly call or raise events to the presentation model or presenter. The same UIElements that initiate calls to the presenter often have to be enabled or disabled in the UI based on state changes or permissions. This requires the view to have methods that can be used to call it back in order to disable those elements.

Another approach is to use WPF commands. Commands provide a clean way of addressing these situations without requiring all the back-and-forth delegation logic. Elements in WPF can bind to commands to handle both execution logic and enabling or disabling elements. When a UIElement is bound to a command, it will automatically be disabled whenever the command's Can­Execute property is false. Commands can be bound declaratively in XAML.

Out of the box, WPF provides RoutedUICommands. Using these commands requires a handler for the Execute and Can­Execute methods within the view's codebehind—which means that code modification is still required for the back-and-forth communication. RoutedUICommands also have other constraints, such as requiring the receiver to be in WPF's logical tree, a constraint that is problematic for building composites.

Fortunately, RoutedUICommands are just one implementation of commands. WPF provides the ICommand interface and will bind to any command that implements it. This means you can create custom commands to suit all your needs, and you don't need to touch the codebehind. The downside is that you have to implement custom commands, such as SaveCommand, Submit­Command, and CancelCommand, all over the place.

The CAL includes new commands such as the Delegate­Command<T>, which allows you to specify the two delegates for the Execute and CanExecute methods in the constructor. Using this command allows you to wire up views without having to delegate through methods defined in the view itself and without having to create custom commands for each action.

In the Stock Trader app, DelegateCommand is used in several places including the watch list. WatchListService uses this command for adding items to the watch list:

public WatchListService(IMarketFeedService marketFeedService) {
  this.marketFeedService = marketFeedService;
  WatchItems = new ObservableCollection<string>();
  AddWatchCommand = new DelegateCommand<string>(AddWatch);
}

In addition to routing commands between the view and a presenter or presentation model, there are other types of communication, such as event publication, that need to be handled in a composite. In these cases, the publisher is completely decoupled from the subscriber. For example, a module may expose a Web service endpoint that receives notifications from the server. Once that notification is received, it needs to fire an event to which components within the same or other modules can subscribe.

To support this functionality, the CAL has an EventAggregator service that is registered with the container. Using this service, which is an implementation of the Event Aggregator pattern, publishers and subscribers can communicate in a loosely coupled fashion. The EventAggregator service contains a repository of events that are instances of the abstract EventBase class. The service has one GetEvent<TEventType> method for retrieving event instances.

The CAL includes the CompositeWPFEvent<TPayload> class that inherits EventBase and provides specific support for WPF. This class uses delegates rather than full .NET events for publishing. Under the hood it uses a DelegateReference class that, by default, functions as a weak delegate (see msdn.microsoft.com/library/ms404247 for more on weak delegates). This allows subscribers to be garbage collected even when they do not explicitly unsubscribe.

The CompositeWPFEvent class contains Publish, Subscribe, and Unsubscribe methods. Each uses the generic type information of the event to ensure that the publisher passes the correct parameters (TPayload) and the Subscriber property receives them (Action<TPayload>). The Subscribe method allows passing in a ThreadOption that can be set to PublisherThread, UIThread, or BackgroundThread. This option determines on which thread the subscribing delegate will be invoked. Also, the Subscribe method is overloaded to allow passing in a Predicate<T> filter so that the subscriber only gets notified of the event if the filter is satisfied.

In the Stock Trader app, the EventAggregator is used for broadcasting whenever a symbol is selected in the positions screen. The News module subscribes to this event and displays news for that fund. Here's how the functionality is implemented:

public class TickerSymbolSelectedEvent : 
  CompositeWpfEvent<string> {
}

First the event is defined in the StockTraderRI.Infrastructure assembly. This is a shared assembly that all the modules reference:

public void Run() {
  this.regionManager.Regions["NewsRegion"].Add(
    articlePresentationModel.View);
  eventAggregator.GetEvent<TickerSymbolSelectedEvent>().Subscribe( 
    ShowNews, ThreadOption.UIThread);
}

public void ShowNews(string companySymbol) {
  articlePresentationModel.SetTickerSymbol(companySymbol);
}

The News module's NewsController subscribes to this event in its Run method:

private void View_TickerSymbolSelected(object sender, 
  DataEventArgs<string> e) {
  _trendLinePresenter.OnTickerSymbolSelected(e.Value);

  EventAggregator.GetEvent<TickerSymbolSelectedEvent>().Publish(
    e.Value);
}

The PositionSummaryPresentation model then fires the event whenever a symbol is selected.

Wrapping Up

Download the guidance from microsoft.com/compositewpf. To run the code, you just need the .NET Framework 3.5 installed.

The guidance contains tools to help you get going. The quickstarts offer easy-to-understand samples that focus on different aspects of building composites. The reference implementation gives you a comprehensive example that utilizes all of the different aspects. Finally, the documentation provides background information, a complete set of how-tos for specific tasks, and a hands-on lab.

As you use the guidance, post your opinion of it on the CodePlex forums or send an e-mail to cafbk@microsoft.com.

Glenn Block is a PM for the new Managed Extensibility Framework (MEF) in the .NET Framework 4.0. Before working on MEF, he was a Product Planner in patterns & practices responsible for Prism as well as other client guidance. Glenn is a geek at heart and spends a good portion of his time spreading that geekdom through conferences and groups such as ALT.NET. Read his blog at codebetter.com/glennblock/.