You are on page 1of 9

Composite Application Library in WPF Application

Introduction
Starting development of an application with a complex user interface, we always face the same problems: how to organize
data presentation, change views, route events, share resources and so on. Badly planned project structure leads to a
headache and extensive rework. That's why before starting a big project, I'd like to make a prototype of a WPF-based
solution and share my small experience with you.
Developing an application, we confront increasing complexity - the more controls, views, menus we add, the more
tangled application architecture becomes. And one fine day we understand that it is easy to throw away all we've done
before, than add yet another module. But thanks to design patterns, the problem can be solved. All we need is Composite
Application Library. With its help, we can split user interface into regions and dynamically load modules into them. We can
organize event and command routing between different modules. And what is more important - a loosely coupled design
of an application built with the Composite Application Library allows different teams to create and test modules
independently.
Sounds good, but if you have just decided to use the Composite Application Library, the next question you ask yourself is:
"well, samples work fine, but how can I build something more realistic?"
I decided to create a small application emulating work with a few servers. Toolbar depends on a server context. Menu bar
contains menu items, depending on currently selected module (Documents, Users or Security). Central area contains a
view presenting current module data:

It is just a prototype. So, only one module was written more or less in more detail - Documents. My main goals were:
to study how to load modules and change views dynamically
to separate presentation from logic using Model-View-Presentation Model (MVP) pattern
to find a way to display and process general menu items (like "Help") and module-specific menu items
to share resources in such a way, that modules can be developed independently and styling with skins would be
easy

Project Structure
The main project is CompositeWpfApp. It contains the main application Window - Shell. That's the first and the last
1

UI element in the project - all other UI views, controls and menus will be created in different projects.
Another set of projects is located in the Common folder:

CWA.ResourceLibrary - Contains shared resources: images, resource dictionaries, skins


CWA.UIControls - Contains custom UI controls and general menu items
CWA.Infrastructure - Contains classes and interfaces that should be easily accessible to all projects
These projects could be linked statically. But the projects in Modules folder will be loaded dynamically when they are
needed.

In order to load these modules, they should be copied to the Modules directory in the CompositeWpfApp project.
Open Properties of a "Module" project (e.g. CWA.Module.Documents) and enter the following Post-build event
command line:
xcopy "$(TargetDir)*.*" "$(SolutionDir)CompositeWpfApp\bin\
$(ConfigurationName)\Modules\" /Y
Besides, we should enumerate the modules in the App.config file in order that the ConfigurationModuleCatalog
could be able to locate and load them:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="modules"
type="Microsoft.Practices.Composite.Modularity.ModulesConfigurationSection,
Microsoft.Practices.Composite"/>
</configSections>
<modules>
<module assemblyFile="Modules/CWA.Module.DefaultModule.dll"
moduleType="CWA.Module.DefaultModule.DefaultModule,
CWA.Module.DefaultModule" moduleName="DefaultModule"/>
2

<module assemblyFile="Modules/CWA.Module.ServerSelector.dll"
moduleType="CWA.Module.ServerSelector.ServerSelector,
CWA.Module.ServerSelector" moduleName="ServerSelectorModule"/>
<module assemblyFile="Modules/CWA.Module.ModuleSelector.dll"
moduleType="CWA.Module.ModuleSelector.ModuleSelector,
CWA.Module.ModuleSelector" moduleName="ModuleSelectorModule"/>
<module assemblyFile="Modules/CWA.Module.StatusArea.dll"
moduleType="CWA.Module.StatusArea.StatusArea,
CWA.Module.StatusArea" moduleName="StatusAreaModule"/>
<module assemblyFile="Modules/CWA.Module.Documents.dll"
moduleType="CWA.Module.Documents.DocumentsModule,
CWA.Module.Documents" moduleName="Documents" startupLoaded="false"/>
<module assemblyFile="Modules/CWA.Module.Users.dll"
moduleType="CWA.Module.Users.UsersModule,
CWA.Module.Users" moduleName="Users" startupLoaded="false"/>
<module assemblyFile="Modules/CWA.Module.Security.dll"
moduleType="CWA.Module.Security.SecurityModule,
CWA.Module.Security" moduleName="Security" startupLoaded="false"/>
</modules>
</configuration>
Pay attention to module names - they are defined in the CWA.Infrastructure project.
The ConfigurationModuleCatalog is defined as our module enumerator in the Bootstrapper class. It will be
used by the Composite Application Library to get information about the modules and their location:
protected override IModuleCatalog GetModuleCatalog()
{
// ConfigurationModuleCatalog class builds a catalog of modules from
// a configuration file
return new ConfigurationModuleCatalog();
}
Instead of the ConfigurationModuleCatalog you can use DirectoryModuleCatalog to discover modules
in assemblies stored in a particular folder, or specify modules in your code or in a XAML file.
DirectoryModuleCatalog could be particularly useful for applications with plug-ins.
Now our spade-work is completed and we can proceed with UI.

Regions and Views


Regions are used to define a layout for a view. If you look at Shell.xaml, you will see region names in its markup:
<ItemsControl Name="MainMenuRegion"
cal:RegionManager.RegionName="{x:Static inf:RegionNames.MainMenuRegion}"
DockPanel.Dock="Top" Focusable="False" />
<ItemsControl Name="ServerSelectorRegion"
cal:RegionManager.RegionName="{x:Static inf:RegionNames.ServerSelectorRegion}"
DockPanel.Dock="Top" Focusable="False" />
<ItemsControl Name="ModuleSelectorRegion"
cal:RegionManager.RegionName="{x:Static inf:RegionNames.ModuleSelectorRegion}"
DockPanel.Dock="Top" Focusable="False"/>
<ItemsControl Name="StatusRegion"
cal:RegionManager.RegionName="{x:Static inf:RegionNames.StatusRegion}"
DockPanel.Dock="Bottom" Focusable="False" />
<ItemsControl Name="MainRegion"
cal:RegionManager.RegionName="{x:Static inf:RegionNames.MainRegion}"
Focusable="False">
These regions will be used to load modules into them:

Let's see how to load modules into the Main Region. ModuleController is responsible for changing views in this
region. First of all, we should register this class in the Bootstraper class of the Shell project:
Container.RegisterType<IGeneralController, ModuleController>
(ControllerNames.ModuleController, new ContainerControlledLifetimeManager());
The ModuleController class implements IGeneralController interface with the single method Run().
CreateShell() method of the Bootstraper finds classes implementing IGeneralController interface and
invokes their Run() methods. As a result, the ModuleController subscribes to the ModuleChangeEvent:
public void Run()
{
eventAggregator.GetEvent<ModuleChangeEvent>().Subscribe
(DisplayModule, ThreadOption.UIThread, true);
}
When we click on the "Documents", "Users" or "Security" button, ModuleSelectorPresententaionModel
publishes ModuleChangeEvent with the corresponding module name. ModuleController catches the event
and displays the module in the following method (shortened version):
private void DisplayModule(string moduleName)
{
try
{
moduleManager.LoadModule(moduleName);
IModulePresentation module = TryResolve<IModulePresentation>(moduleName);
if (module != null)
{
IRegion region = regionManager.Regions[RegionNames.MainRegion];
currentView = region.GetView(RegionNames.MainRegion);
if (currentView != null)
region.Remove(currentView);
currentView = module.View;
region.Add(currentView, RegionNames.MainRegion);
region.Activate(currentView);
}
}
}
The RegionManager is responsible for creating and managing regions - a kind of container for controls implementing
IRegion interface. Our duty is removing previously loaded content from the region and addition of new module view to

it. The only requirement to the module view is implementation of the IModulePresentation interface exposing a
View property.

Model-View-Presenter
Model-View-Presentation Model pattern is intended to separate data model from its presentation and business logic. On
practice, that means that Presentation Model provides content for visual display (View) and tracks changes in visual
content and data model.

Modules, loaded into the Main Region, should implement this pattern. Really, in this demo solution, only
CWA.Module.Documents follows this pattern. This module contains DocumentsPresentationModel and
DocumentsView. Separate data model class is not implemented due to simplicity of the sample.

DocumentsView code-behind does not contain any business logic. Instead, all processing is performed in the
DocumentsPresentationModel. To bind a View to the Presentation Model, we initialize the view in the
DocumentsPresentationModel constructor and make it publicly available as a View property:
public object View
{
get { return view; }
}
public DocumentsPresentationModel(IUnityContainer container,
IServerContextService serverContextService)
{
this.container = container;
this.serverContextService = serverContextService;
view = container.Resolve<DocumentsView>();
}
This View will be passed from the DocumentsPresentationModel to the DocumentsModule and later loaded
into a region (see DocumentsModule.cs):
/// <span class="code-SummaryComment"><summary></span>
/// A View associated with the module.
/// <span class="code-SummaryComment"></summary></span>
public object View
{
get
{
// Each ServerContext shall have a PresentationModel
DocumentsPresentationModel presentationModel =
(DocumentsPresentationModel)TryResolve<IPresentationModel>
(serverContextService.CurrentServerContext.Uid);
5

if (presentationModel == null)
{
// If there is no a PresentationModel associated with the ServerContext
// (i.e. the module was not called/displayed
// for the currently selected Server), create it.
container.RegisterType<IPresentationModel, DocumentsPresentationModel>
(serverContextService.CurrentServerContext.Uid,
new ContainerControlledLifetimeManager());
// Create a PresentationModel
presentationModel = (DocumentsPresentationModel)container.Resolve
<IPresentationModel>(serverContextService.CurrentServerContext.Uid);
}
return presentationModel.View;
}
}
Now we can operate with the view in the DocumentsPresentationModel - for example, to bind some data to
visual controls or, if necessary, display a view of another type. In the last case, Supervising Controller pattern would be
more suitable.

Menus
Often an application can have menus containing some constant set of general items like "Help", "Exit", "About program",
and context-dependent menu items. It makes sense to process general commands in the Shell project, while
view-dependent commands should be processed in the corresponding module.
So, we have a few requirements:
we have to be able to change menus dynamically
general commands should be processed in the Shell project
view-dependent commands should be processed in the corresponding Presentation Model class
we should not duplicate general menu items in each module
First requirement can be easily met. There is a MenuController in the Shell project, which is very similar to
ModuleController described above. Its purpose is to display menu views in the Main Menu Region in response to
the MainMenuChangeEvent. A view generates this event when it is loaded and displayed, i.e., activated. Each
module view is derived from the ModuleViewBase class, that defines virtual event handler ViewActivated().
Overridden method looks like this one:
protected override void ViewActivated(object sender, EventArgs e)
{
base.ViewActivated(sender, e);
if (Menu != null)
eventAggregator.GetEvent<MainMenuChangeEvent>().Publish(Menu);
}
Where the Menu is a UserControl initiated in the view's constructor:
Menu = container.Resolve<DocumentsMainMenuView>();
If you look at DocumentsMainMenuView.xaml, you will see the following XAML code:
<UserControl x:Class="CWA.Module.Documents.DocumentsMainMenuView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctl="clr-namespace:CWA.UIControls.Menus;assembly=CWA.UIControls"
Height="Auto" Width="Auto" Name="DocumentsMainMenu">

<Menu>
<ctl:MainMenuControl />
<MenuItem Header="Documents">
<MenuItem Header="New Document" Command="{Binding NewDocumentCommand}" />
<MenuItem Header="Cut" Command="{Binding CutCommand}" />
<MenuItem Header="Copy" Command="{Binding CopyCommand}" />
<MenuItem Header="Delete" Command="{Binding DeleteCommand}" />
<MenuItem Header="Rename" Command="{Binding RenameCommand}" />
<Separator />
<MenuItem Header="Properties" Command="{Binding PropertiesCommand}" />
</MenuItem>
<ctl:HelpMenuControl />
</Menu>
</UserControl>
It is our markup for menus loaded when a user clicks on "Documents". Do you remember we promised not to duplicate
general menu items? We keep our promises - MainMenuControl and HelpMenuControl are defined in the
CWA.UIControls project. We'll return to them a bit later.
Now we have to provide a way to process menu commands in the Presentation Model, not in the Menu View class. To do
that, we have to bind menu's DataContext to the Presentation Model. Let's return to the
DocumentsPresentationModel constructor:
public DocumentsPresentationModel(IUnityContainer container,
IServerContextService serverContextService)
{
this.container = container;
this.serverContextService = serverContextService;
view = container.Resolve<DocumentsView>();
view.Menu.DataContext = this;
view.Text = serverContextService.CurrentServerContext.Name;
NewDocumentCommand = new DelegateCommand<object>(NewDocument, CanExecuteCommand);
CutCommand = new DelegateCommand<object>(Cut, CanExecuteCommand);
CopyCommand = new DelegateCommand<object>(Copy, CanExecuteCommand);
DeleteCommand = new DelegateCommand<object>(Delete, CanExecuteCommand);
RenameCommand = new DelegateCommand<object>(Rename, CanExecuteCommand);
PropertiesCommand = new DelegateCommand<object>(Properties, CanExecuteCommand);
}
As to general menu items defined in the MainMenuControl and HelpMenuControl - they are sources of
commands of RoutedUICommand type. The commands are bubbling up to a window, containing command binding
for those type of commands. Our responsibility is to create such a binding somewhere in the Shell project. For that
purpose, I created a few command controllers, registered and started them in the Bootstrapper:
private void RegisterCommandControllers()
{
Container.RegisterType<IGeneralController, ExitCommandController>
(ControllerNames.ExitCommandController, new ContainerControlledLifetimeManager());
Container.RegisterType<IGeneralController, SkinCommandController>
(ControllerNames.SkinCommandController, new ContainerControlledLifetimeManager());
Container.RegisterType<IGeneralController, AboutCommandController>
(ControllerNames.AboutCommandController, new ContainerControlledLifetimeManager());
Container.RegisterType<IGeneralController, HelpCommandController>
(ControllerNames.HelpCommandController, new ContainerControlledLifetimeManager());
Container.RegisterType<IGeneralController, SettingsCommandController>
(ControllerNames.SettingsCommandController,
new ContainerControlledLifetimeManager());
}
These controllers add command binding to the main window and now we are able to process these command events like
in the HelpCommandController:

public void Run()


{
// Bind "Help" command to the MainWindow
CommandBinding binding = new CommandBinding
(GlobalCommands.HelpCommand, Command_Executed, Command_CanExecute);
Application.Current.MainWindow.CommandBindings.Add(binding);
}
private void Command_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
private void Command_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("HELP!!!");
}
That's it then.

Skins
It is good practice to keep application resources (brushes, styles, control templates) in one place. For that purpose, I
created CWA.ResourceLibrary project. The main application makes reference to it in the
ResourceDictionary element of the App.xaml file:
<Application x:Class="CompositeWpfApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/CWA.ResourceLibrary;
component/Skins/DefaultSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
DefaultSkin.xaml contains some brushes and a reference to another, style-independent resource file - Resources.xaml.
If we wish to change the appearance of the controls dynamically, we have to fulfill two conditions. First, we should use
DynamicResource references for skin-depending properties like in the sample below:
<Border Background="{DynamicResource ServerSelectorBackgroundBrush}"
BorderThickness="0,1,0,0"
BorderBrush="{DynamicResource ServerSelectorBorderBrush}">
The second condition is a bit tricky. If you look at DefaultSkin, you will notice that it is derived from the
ResourceDictionary class. To do that, I recommend you first create a UserControl and then change its base
class to ResourceDictionary. And don't forget about UserControl element in the XAML!
Now we can change the skin. SkinCommandController is responsible for that:
private void ChangeSkin(string skinName)
{
if (string.IsNullOrEmpty(skinName))
throw new ArgumentException("Skin Name is empty.", "skinName");
if (string.Compare(skinName, currentSkinName, true) != 0)
{

// Change the skin if it differs from the current one


Application.Current.Resources.MergedDictionaries[0] =
SkinFactory.GetResourceDictionary(skinName);
currentSkinName = skinName;
}
}
It replaces the application resource dictionary with the new one, returned by the SkinFactory. That's the case when
inheritance of DefaultSkin from ResourceDictionary comes in handy:
public static ResourceDictionary GetResourceDictionary(string skinName)
{
if (string.IsNullOrEmpty(skinName))
throw new ArgumentException("Skin Name is empty.", "skinName");
if (skinTable.ContainsKey(skinName))
return (ResourceDictionary)skinTable[skinName];
ResourceDictionary resourceDictionary = null;
switch (skinName)
{
case SkinNames.DefaultSkin:
resourceDictionary = (ResourceDictionary)new DefaultSkin();
break;
case SkinNames.BlueSkin:
resourceDictionary = (ResourceDictionary)new BlueSkin();
break;
default:
throw new ArgumentException("Invalid Skin Name.");
}
if (resourceDictionary != null)
{
skinTable.Add(skinName, resourceDictionary);
}
return resourceDictionary;
}
Now, when a user selects "Blue" style, he or she will see new colors:

My sample application is very simple. In the "real" world, skinning is very non-trivial task - just glance at Menu.xaml file.

You might also like