Introduction
This article explains some simple steps to create a modular application using WPF and Prism.
Background
Third party DexExpress WPF control is used in this sample UI application. However, even without using any third party UI control,
the steps below can be used to develop a modular
UI application with WPF and Prism and by using regular WPF controls. The purpose of this example is to show
the basic steps to start a modular application using Prism/Unity and not the usage
of third party controls.
This article was posted back in February 2013 in my personal blog:
Using the code
Here we will explore a few basic steps for how we can start a modular application from scratch using Prism, WPF, and Unity container.
There are often needs for modular applications, especially for large scale application development projects
where each module performing its own specific tasks
can be developed independently and later seamlessly incorporated within the modular application shell through
a container. This also provides the capability
to switch modules of similar functionality seamless for the application to bring up the desired one as configured. This article will not go into
the details of demonstrating
the various aspects and benefits of a modular application. Rather, this will just show
a few basic steps of how we can take advantage of Prism and define regions in our UI where each
module can provide service to independent regions within an application.
Step 1
In Visual Studio 2010, start a new WPF project. Remove the MainWindow.xaml file that was automatically created. Remove
StartupUri="MainWindow.xaml"
from the App.xaml file.
Add the following references:
Microsoft.Practices.Prism
Microsoft.Practices.Prism.Unity
Microsoft.Practices.Prism.UnityExtensions
Microsoft.Practices.Prism.ServiceLocation
Microsoft.Practices.Prism.Interactivity
Create a class called Bootstrapper.cs and derive this from the UnityBootstrapper
class. Create the main shell window by adding a new
WPF window called PrismAppShell.xaml. Implement the abstract method CreateShell
in
the BootStrapper
class.
Override these two methods in the Bootstrapper
class:
InitializeModules
ConfigureModuleCatalog
Inside App.xaml.cs within the OnStartup
method, instantiate
the BootStrapper
object and call its Run()
.
App.xaml
<Application x:Class="Prism.ModuleExmaple.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Application.Resources>
</Application.Resources>
</Application>
App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
BootStrapper bootstrapper = new BootStrapper();
bootstrapper.Run();
}
}
Implement a bootstrapper class which inherits from the UnityBootStrapper
class and override the following three protected methods:
Bootstrapper.cs
public class BootStrapper : UnityBootstrapper
{
protected override System.Windows.DependencyObject CreateShell()
{
return this.Container.Resolve<PrismAppShell>();
}
protected override void InitializeModules()
{
base.InitializeModules();
App.Current.MainWindow = (PrismAppShell)this.Shell;
App.Current.MainWindow.Show();
}
protected override void ConfigureModuleCatalog()
{
base.ConfigureModuleCatalog();
this.ModuleCatalog.AddModule(null); }
}
Create a shell window as show in the XAML below.
PrismAppShell.xaml
<Window x:Class="Prism.ModuleExmaple.PrismAppShell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="PrismAppShell" Height="300" Width="300">
<Grid>
</Grid>
</Window>
After this much, if you run the application from Visual Studio, an empty shell window will come up.
Step 2
Now let us divide the UI into regions and mark them with region names by updating the main shell PrismAppShell.xaml file as follows:
<Window x:Class="Prism.ModuleExmaple.PrismAppShell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://www.codeplex.com/prism"
Title="PrismAppShell" Height="900" Width="1200">
<Grid x:Name="LayoutRoot">
<DockPanel LastChildFill="True" HorizontalAlignment="Stretch"
Name="dockPanel1" VerticalAlignment="Stretch">
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top"
Background="#FFCCD4F8" Height="100">
<ContentControl prism:RegionManager.RegionName="RibbonRegion"></ContentControl>
</StackPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" Background="#FFD9E1EF" Height="100">
<ContentControl prism:RegionManager.RegionName="StatusRegion"></ContentControl>
</StackPanel>
<ScrollViewer>
<StackPanel Orientation="Vertical" DockPanel.Dock="Left" Background="#FF50576F" Width="200">
<ContentControl prism:RegionManager.RegionName="TreeRegion"></ContentControl>
</StackPanel>
</ScrollViewer>
<StackPanel Orientation="Vertical" DockPanel.Dock="Right" Background="#FF677BA7" Width="100">
<ContentControl prism:RegionManager.RegionName="AlertRegion"></ContentControl>
</StackPanel>
<ScrollViewer HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#FFC0DBF2">
<ContentControl prism:RegionManager.RegionName="BlotterRegion"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></ContentControl>
</StackPanel>
</ScrollViewer>
</DockPanel>
</Grid>
</Window>
If you run this application at this point, it is will display the application like below. Notice the regions are now defined in the UI (so that when we create a few modules, different modules can hook up to different UI regions here for corresponding service).
Step 3
Let us place some actual UI controls to replace the place holder stack panels (which we named for different UI regions).
In the shell XAML, we will use a ribbon control and define the RibbonRegion
there.
A TreeView
control on the left for TreeRegion
, a TabControl
in the middle
for BlotterRegion
. Bottom and right (StatusRegion
and
AlertRegion
). We will keep the region defined still within the content control of each of these new UI controls we will add.
<Window x:Class="PrismApp.Shell.PrismAppShell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://www.codeplex.com/prism"
Title="PrismAppShell" Height="900" Width="1200"
xmlns:my="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon"
>
<Window.Resources>
<DataTemplate x:Key="crap">
<StackPanel Width="100" Height="18"></StackPanel>
</DataTemplate>
<Style x:Key="TabItemStyleKey" TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Path=DataContext.ViewTile}"></Setter>
<Setter Property="HeaderTemplate" Value="{Binding Source= {StaticResource crap}}"></Setter>
</Style>
<Style x:Key="TreeItemStyleKey" TargetType="{x:Type TreeViewItem}">
<Setter Property="Height" Value="Auto"></Setter>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="BorderThickness" Value="0" />
</Style>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<DockPanel LastChildFill="True" HorizontalAlignment="Stretch"
Name="dockPanel1" VerticalAlignment="Stretch">
<my:Ribbon prism:RegionManager.RegionName="RibbonRegion"
DockPanel.Dock="Top" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Name="ribbon1" Height="150"/>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom"
Background="#FFD9E1EF" Height="100">
<ContentControl prism:RegionManager.RegionName="StatusRegion"></ContentControl>
</StackPanel>
<ScrollViewer>
<StackPanel Orientation="Vertical" DockPanel.Dock="Left"
MinWidth="150" MaxWidth="200">
<TreeView ItemContainerStyle="{StaticResource TreeItemStyleKey}"
prism:RegionManager.RegionName="TreeRegion"
Name="treeView1" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" BorderThickness="0"
/>
</StackPanel>
</ScrollViewer>
<StackPanel Orientation="Vertical" DockPanel.Dock="Right"
Background="#FF677BA7" Width="100">
<ContentControl prism:RegionManager.RegionName="AlertRegion"></ContentControl>
</StackPanel>
<ScrollViewer HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Background="#FFC0DBF2">
<TabControl ItemContainerStyle="{StaticResource TabItemStyleKey}"
prism:RegionManager.RegionName="BlotterRegion" Name="tabControl1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Width="Auto">
</TabControl>
</StackPanel>
</ScrollViewer>
</DockPanel>
</Grid>
</Window>
Step 4
Now let us create a few modules by implementing the IModule
interface and register with regions. For example, we created a module called cash blotter module and register
it in the following way:
public class CashBlotterModule : IModule
{
private readonly IRegionViewRegistry regionViewRegistry = null;
public CashBlotterModule(IRegionViewRegistry regionViewRegistry)
{
this.regionViewRegistry = regionViewRegistry;
}
public void Initialize()
{
this.regionViewRegistry.RegisterViewWithRegion("BlotterRegion", typeof(CashBlotterView));
this.regionViewRegistry.RegisterViewWithRegion("TreeRegion", typeof (CashItemsTreeView));
}
}
We will just add new XAML files and the corresponding view model files for modules we are adding. Here is a snapshot of
Solution Explorer with the skeleton module files that we are adding.
We added a cash blotter module, a deriv blotter module, cash items tree module, deriv items tree module, common module, ticketing module, etc. Names of the modules may not seem realistic,
however let us just call them so for the sake of this example.
Step 5
We will now update the BootStrapper
class to load modules as shown below. (Below, modules are just added in
the module catalog for brevity of this example.
However you can further load modules using the container and decouple them from
the bootstrapper altogether for loading modules independently and as needed.)
public class BootStrapper : UnityBootstrapper
{
protected override System.Windows.DependencyObject CreateShell()
{
return this.Container.Resolve<PrismAppShell>();
}
protected override void InitializeModules()
{
base.InitializeModules();
App.Current.MainWindow = (PrismAppShell)this.Shell;
App.Current.MainWindow.Show();
}
protected override void ConfigureModuleCatalog()
{
base.ConfigureModuleCatalog();
ModuleCatalog moduleCatalog = (ModuleCatalog)this.ModuleCatalog;
moduleCatalog.AddModule(typeof(PrismApp.Module.Cash.Blotter.CashBlotterModule));
moduleCatalog.AddModule(typeof(PrismApp.Module.Deriv.Blotter.DerivBlotterModule));
}
}
In above code, inside ConfigureModuleCatalog
the module loading will be upgraded in the following section.
When we run now, the skeleton application comes up like this:
Step 6
- Replaced the WPF
DataGrid
with DevExpress grid control, WPF
Toolbox ribbon with DevExpress ribbon control. (Third-party DevExpress controls are not needed to accomplish this,
you can use any UI control, including regular .NET provided WPF controls). - View Injection -
did view injection from cash blotter module and
ticket module and as well from derivatives blotter and ticket module.
- Injected views in desired
Prism regions on the shell, i.e., on
BlotterRegion
,
RibbonRegion
, TreeRegions
. - Model - connected to database for fake trade data. (Code sample for data is not shown here, you can set up your own data
provider and retrieve data and hook up to blotter module to display.)
- ViewModel - created a number of view model classes in order for the views to bind to fake data.
After all the above steps in step 6, our skeleton app looks like the following. In the UI below, when "Cash Trading" tab is chosen,
the cash blotter module will provide
the necessary data and service, and same for the Derivative Trading tab will be serviced by
the deriv blotter module. Same goes for tickets. Also, alert region on the UI (now shown empty)
will be filled by the alert module, same way status region in the UI (now showing empty) will be filled with status messages from
the status module. All these modules
can be developed independent of the main application, can be independently built, tested, and configured.