Download pdf version of this document

Regenerating the WPF/SC-SF Quickstart with the Composite Application Library

The Composite Application Guidance for WPF (Prism) is a set of assets for developing Composite WPF applications that addresses the challenges around building complex enterprise WPF applications. It is not a new version of Composite UI Application Block (CAB) but the concepts of Modularity (composition), Services, Dependency Injection, and Event Brokering are also present in this new guidance because they are essential for building composite applications. However, their manifestations are very different than what you see in CAB.

Contents

Purpose

In this article, you will see how the WPF Quickstart shipped with the Smart Client Software Factory was regenerated using the Composite Application Guidance for WPF. It includes guidance about how to create the initial solution from scratch, identify the regions, use the Event Aggregator service and load modules.

Prerequisites

This topic requires you to have the following Composite Application Library and Unity Application Block assemblies:
  • Microsoft.Practices.Composite.dll
  • Microsoft.Practices.Composite.Wpf.dll
  • Microsoft.Practices.Composite.UnityExtensions.dll
  • Microsoft.Practices.Unity.dll
  • Microsoft.Practices.ObjectBuilder2.dll
The Composite Application Library ships as source code, which means you must compile it to get the Composite Application Library assemblies.

Application Structure

Both the Composite Application Library and Composite UI Application Block applications have similar solution structure with the following components:
  • An startup project that contains the shell view that defines the layout
  • Modules that add views, services, and other functionalities
  • [Optionally] Infrastructure projects to share common artifacts and constants definitions.
The following figure shows the equivalent elements between the original WPF Quickstart shipped with the SCSF source code and the WPF Quickstart rewritten to use the Composite Application Library.
CABvsCAPPG.png
Figure 1
Solution structure

Creating the initial solution

To be able to use the features of the Composite Application Library, you will need to create a pure WPF Application. To do this, follow these steps:
  • In Visual Studio, create a new WPF application named WPFQuickstartwithCAL.
  • Add the Common and Modules solution folders to the WPFQuickstartwithCAL solution.
  • Create a new Class Library project in the Common solution folder called WPFQuickstartwithCAL.Infrastructure.
Note: In this project you will place common components like constants and events definitions. This is just a good practice but it is not required.
  • Add the Constants and Events folders to the WPFQuickstartwithCAL.Infrastructure project.
  • Add the RegionNames class to the Constants folder and put there the definition of the following constants:
public const string RightRegion = "RightRegion";
public const string LeftRegion = "LeftRegion"; 

These constants are used to identify regions throughout the application. A region is a mechanism that allows you to expose to the application Windows Presentation Foundation controls as components that encapsulate a particular visual way of displaying views. Regions can be accessed in a decoupled way by their name and they support adding or removing views dynamically at run time
  • Add the ShowEmployeeEvent class to the Events folder with the following event definition:
public class ShowEmployeeEvent : CompositeWpfEvent<int>
{
} 

Note: All the modules of the final solution have a reference to the WPFQuickstartwithCAL.Infrastructure project.

Preparing the Shell

The application starting point is a WPF Application project which contains a Shell, a Bootstrapper class and a App.xaml file. The following is a description of these artifacts:
  • Shell: A Composite WPF application should contain a place to host different UI components that exposes a way for itself to be populated by others. The following is the Shell used in this solution:
Shell.png
Figure 2
Application’s Shell

The cal:RegionManager.RegionName attribute added to the control is an attached property which indicates that a region has to be created and associated with the control when the view is being built. The value of this attribute is set with the name of the region defined in the RegionNames class.
  • Bootstrapper class: This class inherits from UnityBootstrapper and is in charge of initializing the application. The Bootstrapper is responsible for:
    1. Initialize the container (UnityBootstrapper base class uses the Unity container).
    2. Register core services, like the RegionManager (it is used to get regions) and the EventAggregator (it is used to implement event brokering).
    3. Register adapters for the region that the application uses.
    4. Create and shows the shell.
    5. Load the modules.
Note: The Composite Application Library allows you to choose the way that your modules are loaded (dynamically or statically). By using static module loading, like this sample, the code is simplified and improves the application startup performance.


internal class Bootstrapper : UnityBootstrapper
{
    protected override IModuleEnumerator GetModuleEnumerator()
    {
        return new StaticModuleEnumerator()
            .AddModule(typeof(EmployeeDataModule))
            .AddModule(typeof(OrganizationChartModule));
    }

    protected override DependencyObject CreateShell()
    {
        Shell shell = new Shell();
        shell.Show();

        return shell;
    }
} 
  • App.xaml file: The OnStartup method of the App class is overridden to create and initialize the Bootstrapper class.
protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    Bootstrapper bootstrapper = new Bootstrapper();
    bootstrapper.Run();
} 

Creating Modules

A module is a class library project that must have a class witch implements the IModule interface. This interface has a single method: the Initialize method. In this method you should register services, views and presenters in the container. It is also where you should create views and add them to the Shell’s regions.
The WPF Quickstart has the following modules:
  • WPFQuickstartwithCAL.EmployeeData: In this module are placed the view that shows information about an employee (which is shown in the Right region of the Shell) and a service that retrieves data about employees. The following code shows the implementation of the EmployeeDataModule class.
public class EmployeeDataModule : IModule
{
    private IUnityContainer container;
    private IRegionManager regionManager;

    public EmployeeDataModule(IUnityContainer container, IRegionManager regionManager)
    {
        this.container = container;
        this.regionManager = regionManager;
    }

    public void Initialize()
    {
        this.RegisterViewsAndServices();

        EmployeePresenter presenter = this.container.Resolve<EmployeePresenter>();

        IRegion mainRegion = this.regionManager.Regions[RegionNames.RightRegion];
        mainRegion.Add(presenter.View);
    }

    protected void RegisterViewsAndServices()
    {
        this.container.RegisterType<IEmployeeView, EmployeeView>();
    }
} 
  • WPFQuickstartwithCAL.OrganizationChart: In this module are placed the view that shows the organization chart. The view is showed in the Left region of the Shell. The following code shows the implementation of the OrganizationChartModule class.
public class OrganizationChartModule : IModule
{
    private IUnityContainer container;
    private IRegionManager regionManager;

    public OrganizationChartModule(IUnityContainer container, IRegionManager regionManager)
    {
        this.container = container;
        this.regionManager = regionManager;
    }

    public void Initialize()
    {
        this.RegisterViewsAndServices();

        OrgChartPresenter presenter = this.container.Resolve<OrgChartPresenter>();

        IRegion mainRegion = this.regionManager.Regions[RegionNames.LeftRegion];
        mainRegion.Add(presenter.View);
    }

    protected void RegisterViewsAndServices()
    {
        this.container.RegisterType<IOrgChartView, OrgChartView>();
    }
} 

The Figure 3 shows the structure of the two modules in the solution explorer.

modules.png
Figure 3
Modules structure

Creating Views

All the views are WPF User Controls. To implement the view logic this sample implements the Model-View-Presenter pattern.
In the MVP implementation of CAB/SC-SF, the presenter has a reference to the view interface and the view also has a reference to its presenter. But in the MVP implementation of a Composite WPF application the view usually does not have a reference to its presenter. The communication between the view and its presenter is achieved by using .NET events.
Another difference is that in CAB/SC-SF you created the view and the presenter was injected on it. In a Composite WPF application, the presenter usually drives all the interaction, so the presenters are created using the container (by the Resolve method) and receives a view passed to its constructor as a parameter. The Dependency Injection container handles the injection.
These are the views used in the WPF Quickstart:
  • EmployeeView (EmployeeData module): The following classes are required to complete the view logic:
    • Employee class. This is the view model, it is similar to the one used in the original WPF QuickStart but takes advantage of the C# 3.5 features.
public class Employee
{
    public int Id { set; get; }
    public string Name { set; get; }
    public string Position { set; get; }
    public int Age { set; get; }
    public DateTime EmployedOn { set; get; }
} 
  • IEmployeeView interface
public interface IEmployeeView
{
    Employee Model { get; set; }
} 
  • EmployeePresenter class
public class EmployeePresenter
{
    private EmployeeService _employeeService;

    public EmployeePresenter(IEmployeeView view, EmployeeService employeeService, IEventAggregator eventAggregator)
    {
        this.View = view;
        this._employeeService = employeeService;

        eventAggregator.GetEvent<ShowEmployeeEvent>()
           .Subscribe(SetSelectedEmployee, ThreadOption.UIThread, true);
    }

    public void SetSelectedEmployee(int index)
    {
        Employee employee = _employeeService.GetEmployee(index);
        View.Model = employee;
    }

    public IEmployeeView View { get; set; }
} 

When the presenter is created, the container resolves its dependencies (the employee view, the employee service and the event aggregator). The presenter subscribes its SetSelectedEmployee method as a callback for the ShowEmployeeEvent event.
  • EmployeeView WPF User Control. The following code shows the code behind and the XAML file.
public partial class EmployeeView : UserControl, IEmployeeView
{
    public EmployeeView()
    {
        InitializeComponent();
    }

    public Employee Model
    {
        get { return this.DataContext as Employee; }
        set { this.DataContext = value; }
    }
} 

<UserControl x:Class="EmployeeData.Views.EmployeeView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:EmployeeData">
    <GroupBox Header="Employee Information">
        <Grid DataContext="{Binding}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition></ColumnDefinition>
                <ColumnDefinition Width="5"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
            <TextBlock Text="Name:" Grid.Column="0" Grid.Row="0"></TextBlock>
            <TextBlock Text="Position:" Grid.Column="0" Grid.Row="2"></TextBlock>
            <TextBlock Text="Age:" Grid.Column="0" Grid.Row="4"></TextBlock>
            <TextBlock Text="Employed On:" Grid.Column="0" Grid.Row="6"></TextBlock>

            <TextBox Text="{Binding Path=Name}" Grid.Column="0" Grid.Row="1"></TextBox>
            <TextBox Text="{Binding Path=Position}" Grid.Column="0" Grid.Row="3"></TextBox>
            <TextBox Text="{Binding Path=Age}" Grid.Column="0" Grid.Row="5"></TextBox>
            <TextBox Text="{Binding Path=EmployedOn}" Grid.Column="0" Grid.Row="7"></TextBox>
        </Grid>
    </GroupBox>
</UserControl> 

The Figure 4 shows the final design of the view.

EmployeeView.png
Figure 4
OrgCharView view
  • OrgChartView (OrganizationChart module): The following classes are required to complete the view logic:
    • IOrgChartView interface: Exposes the PositionSelected event to communicate the view with the presenter (since the view does not have a reference to its presenter).
public interface IOrgChartView
{
    event EventHandler<DataEventArgs<XmlElement>> PositionSelected;
    XmlDocument Model { get; set; }
} 
  • OrgChartPresenter class: The presenter raises the ShowEmployeeEvent event when a position is selected in the view.
public class OrgChartPresenter
{
    private readonly IEventAggregator _eventAggregator;

    public OrgChartPresenter(IOrgChartView view, IEventAggregator eventAggregator)
    {
        this.View = view;
        this._eventAggregator = eventAggregator;

        this.View.PositionSelected += delegate(object sender, DataEventArgs<XmlElement> args)
          {
              if (_eventAggregator != null)
              {
                  int selectedEmployeeId = Int32.Parse(args.Value.Attributes["ID"].Value, CultureInfo.InvariantCulture);

                  _eventAggregator.GetEvent<ShowEmployeeEvent>()
                                           .Publish(selectedEmployeeId);
              }
          };

        XmlDocument chart = new XmlDocument();
        chart.Load(@"OrganizationChart.xml");
        View.Model = chart;
    }

    public IOrgChartView View { get; set; }
} 
  • OrgChartView WPF User Control. When a user selects a position in the chart, the view raises the PositionSelected event. The following code shows the view´s code behind.
Note: The XAML code is not shown here for simplicity purposes. The code is the same as the one used in the original WPF Quickstart.


public partial class OrgChartView : UserControl, IOrgChartView
{
    public OrgChartView()
    {
        InitializeComponent();
    }

    public static readonly DependencyProperty OrgChartDataSourceProperty =
        DependencyProperty.Register("OrgChartDataSource", typeof(XmlDocument), typeof(OrgChartView), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnValueChanged)));

    private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        OrgChartView control = (OrgChartView)obj;
        XmlDataProvider provider = (XmlDataProvider)control.Resources["xdpOrgChart"];
        provider.Document = (XmlDocument)args.NewValue;
    }

    void SelectionChanged(object sender, RoutedEventArgs e)
    {
        XmlElement element = ((XmlElement)((TreeView)e.Source).SelectedItem);
        PositionSelected(this, new DataEventArgs<XmlElement>(element));
    }

    public XmlDocument Model
    {
        get { return (XmlDocument)GetValue(OrgChartDataSourceProperty); }
        set { SetValue(OrgChartDataSourceProperty, value); }
    }

    public event EventHandler<DataEventArgs<XmlElement>> PositionSelected = delegate { };
} 

The Figure 5 shows the final design of the view (copied from the WPF Quickstart source code).

OrgCharView.png
Figure 5
OrgCharView view

Checking the Results

Build and run the final solution by pressing F5. You will see both views from the EmployeeView and OrgChartView views loaded in the Shell:

WPFQuickstart.png
Figure 6
Regenerated Quickstart running

If you click in a different block in the organization chart of the left view, you will see the corresponding employee information populated in the right view. This is accomplished with the EventAggregator service and the ShowEmployeeEvent event.

Last edited Sep 8, 2008 at 2:53 PM by mconverti, version 7

Comments

No comments yet.