Tuesday, February 5, 2013

Prism, WPF and Unity based modular application step by step



Starting a modular application development using Prism, WPF and Unity container.

Step 1

·         In Visual Studio 2010, start a new WPF project.
·         Remove MainWindow.xaml file that was automatically created.
·         Remove StartupUri="MainWindow.xaml" from App.xaml file.
·         Add the following references
o    Microsoft.Practices.Prism
o   Microsoft.Practices.Prism.Unity
o   Microsoft.Practices.Prism.UnityExtensions
o   Microsoft.Practices.Prism.ServiceLocation
o   Microsoft.Practices.Prism.Interactivity

·         Create a class called Bootstrapper.cs and derive this from UnityBootstrapper class.
·         Create  the main shell window by adding a new wpf window called PrismAppShell.xaml
·         Implement the abstract method CreateShell in Bootstrapper class.
·         Override two methods in Bootstrapper class
o    InitializeModules
o    ConfigureModuleCatalog
·        
      Inside App.xaml.cs within OnStartup method instantiate Bootstratper object and call its Run()

Therefore the files are going to look like this

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();
        }
       }



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); // TODO - placeholder
        }
    }




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>



Now one with run this application from visual studio the empty shell window will come up like below.



Step 2

Now let us divide the main shell xaml  into regions and mark them with regional names.



<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>







Step 3

In step 3. let us create few individual modules such and load them on the regions on the shell as we defined before. Also in the shell xaml on the top we used a ribbon control and defined 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 kept as ContentControl still.









<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>



Cash blotter module registers view like this.

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));
        }
    }


And bootstrapper loads module like this. (IN next section this will be worked on further to load using container and decouple.

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)); // Seems like as soon as you add a new module, internally seletor module adopter (since blotter region is on tab control), automatically adds a tab item)
            moduleCatalog.AddModule(typeof(PrismApp.Module.Deriv.Blotter.DerivBlotterModule));
            //moduleCatalog.AddModule("CashBlotterModule", "PrismApp.Module.Cash.Blotter.CashBlotterModule");
        }
    }

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 4

In step 4, we have

  • Replaced the wpf datagrid with devexpress grid control, wpf toolbox ribbon with devexpress ribbon control.
  • View Injection - done 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.
  • ViewModel - created a number of view model classes in order for the views to bind to trades data.


After all the above steps in step 4, our skeleton app looks like the following.