Silverlight and WPF - CompositeWPF/Prism supports multi-targeting (single shared codebase)

by 6. March 2009 11:00

Source Code available at http://www.Codeplex.com/SDMS 
Project Linker Blog w/Webcast HERE  

A recent Microsoft Codeplex forum interaction had me updating the SDMS application so that the Employee Module and all of it's views were Multi-targeting (worked on both Desktop and Silverlight).  The reasoning follows below: 

Developer writes "I'm not looking for multi-targeting support, I just want an SL app that uses WCF and is maintainable and testable."

My response follows:

I don't think they are mutually exclusive - I believe you may want/need multi-targeting support.   Let me explain by example - I just spent the last couple of hours updating the SDMS application so that the Modules folder is supported under both Desktop and Silverlight.  Why?   "maintainable and testable".   I created a Unit Test for you and checked everything in (been really wanting to do this for a while and this gave me good reason to).

The key point here is that the "only" code that will be different will be the XAML (Silverlight/WPF) and the actual WCF Service call.  I did however create my Desktop WCF Service using Async communications so you won't find any SILVERLIGHT conditional statements anywhere in the Business Logic Layer or Data Layer (they are one and the same code for both sides).   I should give a plug for the Project Linker (blogged about on my blog site w/webcast); all my time was spent creating empty WPF views and implementing the interface on them.

[TestMethod]

public void TestMethod1()

{

    // IModule does all of the heavy lifting - configures all interfaces

    // so we'll just use it to set things up.

    IModule module = Container.Resolve<IModule>();

    module.Initialize();

 

    // Resolve the EmployeeList Presenter

    EmployeeListPresenter MockView = Container.Resolve<EmployeeListPresenter>();

 

    // Give WCF Service a chance to complete

    Thread.Sleep(2000);

 

    // Cast so we can easily access presentation model

    EmployeePresentationModel model =
             (EmployeePresentationModel) MockView.Model;

 

    Assert.AreEqual(3, model.EmployeeList.Count,
                   "Employee list should have three records!");

}


Note we can put the Testing thread to sleep :)  I just tested everything short of the UI which is databound to the presentation model (nothing to test in the view) all the way through the WCF Service and back.  Since my PresentationModel implements INotifyPropertyChanged I can rest assured my View will work (assuming I did my Binding correctly).

Let's see what IModule was up to (showing the effectiveness of multi-targeting)

 

 

public class ModuleEmployee : IModule

{

    // For class use

    private readonly IUnityContainer container;

    private readonly IRegionViewRegistry regionViewRegistry;

 

    /// <summary>

    /// Constructor : Setup class

    /// </summary>

    /// <param name="container"></param>

    /// <param name="regionViewRegistry"></param>

    public ModuleEmployee(IUnityContainer container,

        IRegionViewRegistry regionViewRegistry)

    {

        this.container = container;

        this.regionViewRegistry = regionViewRegistry;

    }

 

    public void Initialize()

    {

        RegisterViewAndServices();

 

        // EmployeeModule - Views folder

        regionViewRegistry.RegisterViewWithRegion("MainRegion",

            () => container.Resolve<EmployeeMainPresenter>().View);

 

        regionViewRegistry.RegisterViewWithRegion("frmCaption",

            () => container.Resolve<frmCaptionPresenter>().View);

 

        regionViewRegistry.RegisterViewWithRegion("frmEmployeeList",

            () => container.Resolve<EmployeeListPresenter>().View);

 

        regionViewRegistry.RegisterViewWithRegion("TabInformation",

            () => container.Resolve<EmployeeInformationPresenter>().View);

 

        regionViewRegistry.RegisterViewWithRegion("TabAssigned",

           () => container.Resolve<EmployeeAssignedPresenter>().View);

 

        regionViewRegistry.RegisterViewWithRegion("TabInWork",

           () => container.Resolve<EmployeeInWorkPresenter>().View);

 

        regionViewRegistry.RegisterViewWithRegion("frmStatus",

            () => container.Resolve<frmStatusPresenter>().View);

 

    }

 

    private void RegisterViewAndServices()

    {

        container.RegisterType<IEmployeeMainView, EmployeeMainView>()

 

            // Layers

            .RegisterType<IEmployeeProviderBLL,EmployeeProviderBLL>()

            .RegisterType<IEmployeeProviderDAL,EmployeeProviderDAL>()

 

            // Views

            .RegisterType<IfrmStatusView, frmStatusView>()

            .RegisterType<IfrmCaptionView, frmCaptionView>()

            .RegisterType<IEmployeeListView, EmployeeListView>()

            .RegisterType<IEmployeeListView, EmployeeListView>()

            .RegisterType<IEmployeeInWorkView, EmployeeInWorkView>()

            .RegisterType<IEmployeeAssignedView, EmployeeAssignedView>()

            .RegisterType<IEmployeeInformationView, EmployeeInformationView>()

 

            // Services

            .RegisterType<IEmployeeService, EmployeeService>()

 

            // Models

            .RegisterType<IEmployeePresentationModel, EmployeePresentationModel>(

                            new ContainerControlledLifetimeManager());

    }

}

}


It did some pretty heavy lifting which tells me everything that will be executed during Silverlight runtime - works. 

The following is the Presenter, which is responsible for updating the Presentation Model (which the view is observing).  You can see that my Desktop Unit Test effectively exercises many, if not all, logic within the process.  

 

Note: Silverlight unit testing is done in a browser...  I'd rather take this approach.

Hope this helps in your quest to finding an architecture that works for you!

Bill


 

public class EmployeeListPresenter : PresenterBase<IEmployeeListView>

{

    readonly IEmployeeService employeeService;

    readonly IEventAggregator aggregator;

    readonly IEmployeePresentationModel model;

 

    /// <summary>

    /// Constructor : setup class

    /// </summary>

    /// <param name="container"></param>

    /// <param name="view"></param>

    public EmployeeListPresenter(

        IEmployeeListView view,

        IEmployeePresentationModel model,

        IUnityContainer container,

        IEventAggregator aggregator,

        IEmployeeService service) : base(view,model,container)

    {

        this.aggregator = aggregator;

        this.employeeService = service;

        this.model = model;

 

        // Subscribe to ListBoxChanged event and

        aggregator.GetEvent<ListBoxChangedEvent>()
                  .Subscribe(ListBoxChangedEventHandler, true);

        aggregator.GetEvent<EmployeeEvent>()
                  .Subscribe(EmployeeEventHandler, true);

 

        // Async call to service to populate employee list. 
        // The EmployeeListEventHandler
will be called when
        // data is received

        employeeService.GetEmployeeList();

    }

 

    /// <summary>

    /// Subscribed to in constructor - updates the model's
    /// SelectedEmployee property every
time a new employee is selected

    /// </summary>

    /// <param name="args"></param>

    private void ListBoxChangedEventHandler(SelectionChangedEventArgs args)

    {

        model.SelectedEmployee = args.AddedItems[0] as Employee_Data;

 

        StatusBarEvent sbEvent = aggregator.GetEvent<StatusBarEvent>();

        if (sbEvent != null)

            aggregator.GetEvent<StatusBarEvent>().Publish(

                new StatusBarData

                {

                    Message = string.Format("You clicked {0}",
                                    model.SelectedEmployee.DisplayValue),

                    Panel = StatusPanel.Left

                });

    }

 

    /// <summary>

    /// Handler for when Employee list is returned by service call to

    /// GetEmployeeList()

    /// </summary>

    /// <param name="args"></param>

    private void EmployeeEventHandler(EmployeeEventArgs args)

    {

        model.EmployeeList = args.EmployeeList;

    }

 }

SDMS / VisioTool - Controller completed w/unit test - MAF

by 6. September 2008 08:47

If like me, you were dismayed to learn that Visio for Architects was not supported in Visual Studio 2008  you'll appreciate this upcoming tool; particularly if you want to reverse engineer your code so that you can create your UML diagrams without having to manually input all the class information.  

Fortunately there is a workaround (I posted it HERE) which this utility performs.  Without it there was quite a bit of grunt work involved - enough for me to push architecture to the back-burner.   With a new requirement on the front burner I wasn't afforded this opportunity (to back-burner architecture).  There are to many components for me to wrap my mind around the solution, so I'll have to architect the existing WPF Calculator to prepare a gap analysis so that I can successfully create a Managed Add-in Framework (MAF) module loader for my CompositeWPF SDMS solution. 

For those that can't wait for the UI to be completed (like me) I completed the TDD of the controller - the process is fully functional.   The two test that actually perform the work are remarked out (not shown in the screenshot below) because I don't want to inadvertantly convert the solution; I have my Visio document and have to back burner this utility for now.

Note below that the logger trapped some warnings.  I created test for invalid files as well as non-existent files and the test passed as I attempted to crash the process. 

After manually running the "Controller_CanPrepareVS2008SolutionForArchitecture" test I was able to open up my solution in Visual Studio 2005 - note that there are two projects that did not open - at a glance it looks like the WPF related projects (I have some more research to do).  Since CALDemoApplication is on the "New" side of the gap analysis and the DemoApplication is mostly UI stuff I can worry about them later - what I needed was the main solution projects (which I have below).  Updated: unfortunately UI has a great deal of logic in it (more below).

With my Visio document in hand I manually ran the "Controller_CanRestoreFromArchitecture" unit test and was able to successfully reopen my solution in Visual Studio 2008, compile and run the application with no issues.

IMPORTANT NOTE:  when I was closing the Visual Studio 2005 solution (after creating the visio document) I responded "No" to save solution updates.

Note: I manually added the newly created DemoApplication.vsd document to my solution
 

Source code for above available at HERE - Change Set 23906.    App | VisioTool | VisioToolModule and VisioToolModule.Tests

This is somewhat off topic but very cool so I'll mention it - below is the LINQ code I use to get my project list out of a Visual Studio 2005 / 2008 solution:

I created the query using LINQPAD - more about this HERE

UPDATED:  I investigated the two projects that won't open.  It appears the .NET 3.5 WPF Projects have a totally different header - it follows:

<Project ToolsVersion="3.5"
             DefaultTargets="Build"
             xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

The return on investment for supporting this new format (any efforts to convert it) are not worth it; particularly since I simply have to continue good programming practices and patterns for it not to be a problem; XAML projects will contain only UI code and I'll have all the supporting logic in class libraries (an example for my SDMS application is in the following image).

This does however cause a problem for me with the WPF Calculator program that I am architecting for my gap analysis.  Most, if not all, of the wiring-up logic is sitting in the DemoApplication's CalculatorHost.xaml code behind file - not good...   This is not good for reverse engineering nor does it promise to offer any reusable components (chomping at the bit for MEF to be released).  So now I'm in refactoring mode - pulling all of the code out of code-behind and placing it in an external library.  It's a painfully slow process because I have to refactor, test everything (there are no unit test), check-in and move another component and start the cycle over again....  

The ROI for all this work is that the WPF Calculator is my POC (Proof of Concept); when I'm done refactoring to get the code out of the UI I can complete the Architecture work.  My goal is to keep refactoring the WPF Calculator until it is a CompositeWPF application with reusable components that I can use in my SDMS application.   The WPF Calculator UI will have less code than my SDMS UI does below so I have no issues with manually adding these objects to Visio: 

 

Tags: , ,

CompositeWPF - Integrating the Managed Add-in Framework (MAF) - Part 1 of ? - App.g.cs

by 4. September 2008 12:04

If you are unfamiliar with MAF I would suggest that you start by reading Kent Boogaart's Blog on the topic HERE.  Although he recommends reading an MSDN link (that he provides) before continuing his series I would recommend that you read his MAF Gymnastics: Service Provider blog first; he does a great job in taking a very complex topic and breaking it down into bite-size chunks to help us understand how everything is tied together.   I downloaded the solution he provided (the code is compact and not so overwhelming) and reviewed it while I followed along in his blog, by the time I was finished things started to make sense.  I should note the "limitations" he exposes are an eye-opener (the first I've read on the topic).

Once completed with the above mentioned blog, my curiosity had me going to the beginning - how the application is launched.   I didn't see anything unusual with the exception of the LoaderOptimizationAttribute; a wee bit of research had me realizing that I'm going to save his "Event Hub" blog for later; this attribute was going to be a "risk" for my CompositeWPF goals - WPF takes control of Main and it isn't readily apparent how we can add this attribute.

    5 class Program

    6 {

    7     [LoaderOptimization(LoaderOptimization.MultiDomainHost)]

    8     static int Main(string[] args)

    9     {

   10         Console.WriteLine("Initializing host in AppDomain '{0}'",

   11             AppDomain.CurrentDomain.FriendlyName);

   12 

   13         try

   14         {

   15             return new Host().Run();

   16         }

   17         finally

   18         {

   19             Console.WriteLine();

   20             Console.WriteLine("Host will shut down when you press a key.");

   21             Console.ReadKey();

   22         }

   23     }

   24 }

This adventure was somewhat short-lived, I figured I'd be googling for some time because of my past experiences on the topic - before I provide the "easy" solution to the problem it might help to understand what is going on under the hood.

When you compile an application the Visual Studio Compiler will automatically generate an App.g.cs file containing an App class derived from Application.  This file is automatically generated in the applicable Obj folder (Debug, Release, etc) everytime you compile so any modifications you make will be overwritten:

If you attempt to add a static Main method to your App.xaml.cs file you'll run into the following error:

 

Note: the above code is what I am now actually using, after applying the property change and above code, my SDMS application continues to run as it did before. 

The solution - we have to turn off the creation of the App.g.cs file so that ours will be used (I explain how below).  Bear in mind before you do this that the auto creation of the app.g.cs file is biased by the contents of the App.xaml file so you may have to migrate some code from the App.g.cs App class into your own.  e.g., the following XAML file:

    1 <Application x:Class="MainShell.App"

    2    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    3    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    4    >

    5     <Application.Resources>

    6         <ResourceDictionary>

    7             <ResourceDictionary.MergedDictionaries>

    8                 <ResourceDictionary Source="Styles\styles.xaml"></ResourceDictionary>

    9             </ResourceDictionary.MergedDictionaries>

   10         </ResourceDictionary>

   11     </Application.Resources>

   12 </Application>

Gnerates the following App.gs.cs file (which does more than my replacement does)- note that it has the addition of a InitializeComponent method which handles the above resource: 

   38 public partial class App : System.Windows.Application {

   39 

   40     private bool _contentLoaded;

   41 

   42     /// <summary>

   43     /// InitializeComponent

   44     /// </summary>

   45     [System.Diagnostics.DebuggerNonUserCodeAttribute()]

   46     public void InitializeComponent() {

   47         if (_contentLoaded) {

   48             return;

   49         }

   50         _contentLoaded = true;

   51         System.Uri resourceLocater =

   52             new System.Uri("/MainShell;component/app.xaml",

   53                 System.UriKind.Relative);

   54 

   55         #line 1 "..\..\App.xaml"

   56         System.Windows.Application.LoadComponent(this,

   57             resourceLocater);

   58 

   59         #line default

   60         #line hidden

   61     }

   62 

   63     /// <summary>

   64     /// Application Entry Point.

   65     /// </summary>

   66     [System.STAThreadAttribute()]

   67     [System.Diagnostics.DebuggerNonUserCodeAttribute()]

   68     public static void Main() {

   69         MainShell.App app = new MainShell.App();

   70         app.InitializeComponent();

   71         app.Run();

   72     }

   73 }

THE SOLUTION:

Select the App.xaml file and from properties change the "Build Action" from Application Definition to None and the auto generated file will no longer be created!  TIP: save a copy of the App.g.cs file first so that you can ensure your new Main application does everything it is expected to.   Note: the App.g.cs file will no longer exist in the Obj folder.

Other Links

 

SDMS - Using the Logger (TraceTool) to aid development

by 31. August 2008 00:54

Our SDMS main view takes advantage of routed events - we'll subscribe all button clicks to the UserControl_Click event handler. 

<UserControl x:Class="MainView"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   x:Name="MainViewUC"

   ButtonBase.Click="UserControl_Click"> 

 

    <DockPanel>

        <Menu DockPanel.Dock="Top">

            <MenuItem Header="File"/>

            <MenuItem Header="Tools"/>

            <MenuItem Header="Help"/>

        </Menu>

Since all routed events will be processed by a single method in our presenter we can use the Factory Pattern to process them.  For extensibility purposes we won't assume that the routed event will always be a button click. 

Typically you might place a break point on line 43 to determine what properties can be used to create the factory - we'll let the Logger (TraceTool) assist us here:

In our demo we clicked the "Toolbox" vertical button.

Below we see that we can use the e.RoutedEvent property to determine which class should be resolved to handle event processing, in this case "ButtonBase.Click".   The e.OriginalSource can be used to determine what Command should be executed within the resolved class.  

Click HERE for flash demo 
http://www.codeplex.com/SDMS | Source Code Tab | Change Set: 23138

Click HERE for flash demo

 

SDMS - Source base update attribute

by 30. August 2008 01:17

The vision statement for Solution Development Management System (http://www.CodePlex.com/SDMS) code base updates (Unity, CompositeWPF, etc) follows:

When a new release of base source is available we'll check in all source code, check out the entire project and then unpack the new release overwriting existing code.  Next we'll run all unit test (SDMS will be a TDD solution) to ensure nothing broke and if we don't have to roll-back the update we'll check-in the project updates.

In a perfect world the above would be all that is required but the problem is we may be forced to update our code base...   In preparation for this we have created the following attribute:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace Unity.Interface.Attributes

{

 

    public class SourceBaseUpdateAttribute : Attribute

    {

        private string _developer;

        private string _reason;

        private string _date;

        private int _priority;

 

        public string Date { get { return _date; } }

        public string Reason { get { return _reason; } }

        public string Developer { get { return _Developer; } }

        public int Priority { get { return _priority; } }

 

        /// <summary>

        /// Base source code update, e.g. Unity, CompositeWPF, etc.  Reason for

        /// the update and priority where Priority is on a scale of 1 to 10 with

        /// anything less than 2 being throw away (on base code updates).

        /// UPDATES SHOULD BE KEPT TO A MINIMUM!

        /// </summary>

        /// <param name="Reason"></param>

        /// <param name="Priority"></param>

        public SourceBaseUpdateAttribute(

            string reason,

            int priority,

            string developer,

            string date)

        {

            _reason = reason;

            _priority = priority;

            _developer = developer;

            _date = date;

        }

 

    }

}

An example of it's usage follows:

        [SuppressMessage("Microsoft.Design",

             "CA1062:ValidateArgumentsOfPublicMethods")]

        // FxCop suppression: Lifetime manager is passed to container to dispose.

        [SuppressMessage("Microsoft.Reliability",

             "CA2000:DisposeObjectsBeforeLosingScope")]

 ==>    [SourceBaseUpdate("Implement logging for types being resolved",

              5, "BillKrat", "2008.08.30")]

        public void Configure(IUnityContainer container)

        {

            Guard.ArgumentNotNull(container, "container");

            LifetimeManager lifetimeManager = null;

            if(Lifetime != null)

                lifetimeManager = Lifetime.CreateLifetimeManager();

This requires us to modify our upgrade plan to include running a "SourceBaseUpdate" report to show all changes made to the base as our first step; we can then rollback the specific files (if code compare reveals our changes were the only ones since last release) or copy/paste updates from a change repository (a feature we would have to write) to the affected files.

 

Tags: ,

CASK - release moving to the front burner because of SDMS

by 24. August 2008 05:34

The Community Starter Kit was a popular open source Community Site for .NET 1.1 (reference links on http://www.CodePlex.com/CASKDotNet).  There were numerous issues, particularly with Images not displaying if IIS wasn't configured correctly that plagued the community for years.   When .NET 2.0 beta was released I attempted to join a team, any team, that was involved with CSK so we could fix known issues and upgrade it to support .NET 2.0.   Maintaining the forum was a full-time job and we lost many developers through the frustrations of not being able to make it work out of the box - I wanted to see this change...

Efforts failed to create a team so I was forced to lonewolf it; I fixed known issues and upgraded it to .NET 2.0 (beta at the time).  When CodePlex opened up I created the Community Advanced Starter Kit; I added "Advanced" because "starter" can be misleading; I was lost coming out of the gate because it used many advanced features of ASP.NET 2.0 (such as HttpModule).  

Shortly after joing CodePlex CSharpEd joined the team and he did magic with the Admin styles (rewrote admin management), added RSS feed support and created an installation wizard (to name a few).    CSharpEd and I had different visions (of equal value) for the CSK so we came to a mutual agreement that we should have our own projects; CSharpEd has http://www.CodePlex.com/DotNetCommunities where I have http://www.CodePlex.com/CASKDotNet.  

With the Solution Development Management System (SDMS) on the front burner the CASK system (as of this writing) starts creeping towards the front burner because the SDMS requires a Web Interface; an interface that will be comprised of Microsoft Office SharePoint Server (MOSS) 2007.   The limitations with MOSS 2007, i.e., 2000 records per list, no support for transactions and an inability to handle complex structures, requires programming to an external database.  MOSS 2007 also comes with a price tag that is not compatible with an open source project.  It will be a "core requirement" that the SDMS takes advantage of SharePoint (for those that have it).  For those that don't I need to provide a means to use it without SharePoint - enter stage left CASK.

As I study SharePoint and learn of it's strengths and limitations I'm seeing similiarities between it and the CASK.   The goal now is to create an interface that can be shared by both CASK and MOSS 2007 to manage sites and lists.   The CASK Database will serve as a respository for those custom requirements that can't be handled by MOSS 2007 because of it's limitations.

Within this context, preparing CASK for release becomes a higher priority - it is moving towards the front burner...

Tags: ,

CompositeWPF - DirectoryLookupModuleEnumerator(path)

by 22. August 2008 22:55

The CompositeWPF's UnityBootstrapper baseclass has a virtual method called GetModules(); this method will return an IModuleEnumerator containing a list of Modules (ModuleInfo) that you want to load for your solution.  The following, from the CompositeWPF Commanding solution, is a typical implementation:

namespace Commanding
{
    class CommandingBootstrapper : UnityBootstrapper
    {
        protected override DependencyObject CreateShell()
        {
            Shell shell = Container.Resolve<Shell>();
            shell.Show();
            return shell;
        }

        protected override IModuleEnumerator GetModuleEnumerator()
        {
            return new StaticModuleEnumerator().AddModule(typeof(OrderModule));
        }
    }
}

In the case of my SDMS application I chose to use the DirectoryLookupModuleEnumerator(path) class so that I can return an IModuleEnumerator for the currently running application path as follows:

   public class LoadReferencedModules
    {
        public static IModuleEnumerator GetModules(Type type)
        {
            string path = new FileInfo(type.Assembly.Location).DirectoryName;
            return new DirectoryLookupModuleEnumerator(path);
        }
    }

This method effectively allows my MainShell to load any module it has a reference to; this means that all I have to do to load an application is set a reference to it and I'm done.  Below I show how the main shell accomplishes this.

    public App()
    {
        // Display shell with Loading... message
        ShowShell();

        // Configure the container and load modules
        BootStrapper bootStrapper = new BootStrapper(CreateShell, GetType());
        bootStrapper.Run();
       
    }

    protected DependencyObject CreateShell(IUnityContainer container)
    {
        container.Resolve<ILogger>()
            .Log("CreateShell (Buildup)", Category.Debug, Priority.Low);

        // At this point the bootstrapper has configured the
        // unity container - we can buildup our shell
        container.BuildUp(shell);
        return shell;
    }
 

Notice I send the BootStrapper above GetType(), the BootStrapper in turn will use this type to get the location of the asemblies to load:

    public BootStrapper(ShellCreationDelegate createShell, Type type)
    {
        _createShell = createShell;
        _moduleType = type;
        _getModules = GetModuleEnumeratorDirectory;
    }

    protected IModuleEnumerator GetModuleEnumeratorDirectory()
    {
        return LoadReferencedModules.GetModules(_moduleType);
    }

CompositeWPF - Displaying Loading... message while form loads

by 22. August 2008 13:34

Below we'll show how to display a loading message while we wait for our form to load - we'll display the form almost immediately.

The QuickStarts \ Commanding \ App.xaml.cs has the following code:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

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

Which has the bootstrapper loading the Shell and showing it

class CommandingBootstrapper : UnityBootstrapper
{
    protected override DependencyObject CreateShell()
    {
        Shell shell = Container.Resolve<Shell>();
        shell.Show();
        return shell;
    }
    protected override IModuleEnumerator GetModuleEnumerator()
    {
        return new StaticModuleEnumerator().AddModule(typeof(OrderModule));
    }
}

Since the SDMS solution has the bootstrapper in the CompositeWPF.Library we can't use the above code so we provide the BootStrapper a delegate for the CreateShell method that it should call.   The SDMS \ App \ CommandPrototype application has the following:

public partial class App : Application
{
    public App()
    {
        BootStrapper bootStrapper = new BootStrapper(CreateShell, GetType());
        bootStrapper.Run();
    }
    protected DependencyObject CreateShell(IUnityContainer container)
    {
        // Launch our shell
        Window1 shell = container.Resolve<Window1>();
        shell.Show();
        return shell;
    }
}

With both of the above configurations note how much time transpires from the time the Bootstrapper is started (21:22:30:102) to the time the Shell normally is displayed (21:22:31:205 - the arrow). 

We won't use the above two default methods of loading and showing the form when the BootStrapper.CreateShell() method fires; we want to show a "Loading..." message.  As you can see in the screenshot below the *first* thing we do is load the form and show it - we then start loading the application.

After the Shell is loaded (21:22:32:290) the CompositeWPF then proceeds to load and initialize modules - whether using the new process or default the form is displayed while loading continues...

The following reflects what the SDMS user will see almost immediately - until the form is completely loaded.  The loading message is in a textblock which can be replaced with pictures or progressbar (being updated at strategic intervals); for now I'm going for core functionality.

What the user sees after the form is loaded follows - we collapse the textblock so the text is no longer visible:

Click HERE for flash demo

Click HERE for demo of different app without loader modifications

The following is the new code in the App.xaml.cs file:

namespace MainShell
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private Shell shell;         /// <summary>
        /// Loads the application.
        /// Bootstrapper is in the Shell.Infrastructure project which
        /// can be shared by multiple solutions.   If you specify a type
        /// as a second parameter (as we do here) versus an IModuleEnumerator
        /// method, the BootStrapper will load all modules in the app
        /// directory (any module it has a reference to).
        /// </summary>
        public App()
        {
            // Display shell with Loading... message
            ShowShell();             // Configure the container and load modules
            BootStrapper bootStrapper = new BootStrapper(CreateShell, GetType());
            bootStrapper.Run();
           
            // Hide the loading message
            shell.tbLoading.Visibility = Visibility.Collapsed;
        }         protected DependencyObject CreateShell(IUnityContainer container)
        {
            container.Resolve<ILogger>()
                .Log("CreateShell (Buildup)", Category.Debug, Priority.Low);             // At this point the bootstrapper has configured the
            // unity container - we can buildup our shell
            container.BuildUp(shell);
            return shell;
        }         /// <summary>
        /// Show the shell immediately - the shell XAML has a Loading...
        /// textblock
        /// </summary>
        protected void ShowShell()
        {
            // We'll have to manually instantiate logger; this will be
            // the only place we tightly couple because we don't have
            // unity container yet.
            ILogger Logger = new TraceToolLogger();
            Logger.Log("Loading Shell", Category.Debug, Priority.Low);             // Instantiate the shell
            shell = new Shell();
            WF.Application.DoEvents(); // prevents black background
            shell.Show();
            WF.Application.DoEvents(); // allows form to completely paint             // This was called before the bootstrapper, the logger
            // won't be in scope so we'll log the entry here
            Logger.Log("Loading Bootstrapper", Category.Debug, Priority.Low);
        }
    }

Source code available HERE - Change Set: 22520

 

Notice

Blog videos and references to CodePlex projects are no longer valid