Click here to Skip to main content
6,629,885 members and growing! (24,207 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » Windows Presentation Foundation » General     Intermediate License: The Code Project Open License (CPOL)

WPF : If Heineken did MVVM Frameworks Part 3 of n

By Sacha Barber

It would probably be like Cinch a MVVM framework for WPF
C# (C# 3.0, C# 4.0), .NET (.NET 3.5, .NET 4.0), WPF, Dev, Design
Version:20 (See All)
Posted:25 Jul 2009
Updated:17 Oct 2009
Views:19,475
Bookmarked:39 times
Unedited contribution
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
60 votes for this article.
Popularity: 8.58 Rating: 4.82 out of 5

1

2
6 votes, 10.0%
3

4
54 votes, 90.0%
5

Contents

Cinch Article Series Links

  • Cinch primer article
  • A walkthrough of Cinch, and it's internals I
  • A walkthrough of Cinch, and it's internals II (This article)
  • How to develop ViewModels using Cinch
  • How to Unit test ViewModels using Cinch, including how to test Background worker threads which may run within Cinch ViewModels
  • A Demo app using Cinch
  • Introduction

    Last time we started looking at some of the Cinch internals, and this time we are going to finish up looking at the Cinch internals.

    In this article I will be looking at the following:

     

    PreRequisites

    The demo app makes use of :

    • VS2008 SP1
    • .NET 3.5 SP1
    • SQL server (see the README.txt in the MVVM.DataAccess project to learn what you have to setup for the demo app database)

    Special Thanks

    So I guess the only way to do this is to just start, so lets get going shall we, but before we do that I just need to repeat the special thanks section, with one addition, Paul Stovell who I forgot to include last time

    Before I start I would specifically like to say a massive thanks to the following people, without whom this article and the subsequent series of articles would never have been possible. Basically what I have done with Cinch is studied most of these guys, seen what's hot, what's not, and come up with Cinch. Which I hope adresses some new ground not covered in other frameworks.

    Mark Smith (Julmar Technology), for his excellent MVVM Helper Library, which has helped me enormously. Mark I know I asked your persmission to use some of your code, which you most kindly gave, but I just wanted to say a massive thanks for your cool ideas, some of which I genuinely had not thought of. I take my hat off to you mate.

    Josh Smith / Marlon Grech (as an atomic pair) for their excellent Mediator Implementation. You boys rock, always a pleasure

    Karl Shifflett / Jaime Rodriguez (Microsoft boys) for their excellent MVVM Lob tour, which I attended, well done lads

    Bill Kempf, for just being Bill and being a crazy wizard like programmer, who also has a great MVVM framework called Onyx, which I wrote an article about some time ago. Bill always has the answers, to tough questions, cheers Bill.

    Paul Stovell for his excellent delegate validation idea, which Cinch uses for validation of business objects

    ALL of the WPF Disciples, for being the best online group to belong to IMHO

    Thanks guys/girl, you know who you are

     

    Cinch Internals II

    This section will finish the dive into the internals of Cinch, which should hopefully not bore you lot too much, and should allow you to fully understand the rest of the articles which deal with building a demo set of ViewModels/Unit Tests and showcase the actual attached demo app.

     

    DI/IOC

    The Cinch MVVM framework makes use of an IOC container, namely the Microsoft Unity IOC container, which is freely available as a standalone application block pr as part of Enterprise Library.

    Cinch uses the Unity IOC container to allow different service implementations to be dynamically injected at runtime. There are some defaults assumed, but these can be overridden by specifying an entry in the App.Config, which will override the default service implementation that made otherwise have been used.

    As Cinch is aimed at being a WPF framework the defaults for most services are WPF implementations, as such when you come to do a Unit Test project you MUST supply test service implementations (Cinch has these available) via the App.Config of the Unit Test project.

    This is achieved using the custom UnityConfigurationSection which is filled in, in both the real UI project and also a Unit Test project. The Unity container simply examines the active projects App.Config and reads the Types from the UnityConfigurationSection, and will then create and hold an instance of the configuration specified type.

    Cinch wraps the unity container in a singleton, to ensure that there is only ever one Unity container available within a Cinch application.

    The following diagram illustrates how the Unity IOC container works.

    Optional App.Config Unity Items Required

    The following table illustrates what you could supply in the App.Config when using Cinch.

    Service Item WPF App Test Project
    IMessageBoxService Not required, default is used You can use the Cinch test service version default, but you MUST provide an entry in the Unity config section to ensure the default WPF implementation is override in Cinch to use the Test version.
    IOpenFileService Not required, default is used You can use the Cinch test service version default, but you MUST provide an entry in the Unity config section to ensure the default WPF implementation is override in Cinch to use the Test version.
    ISaveFileService Not required, default is used You can use the Cinch test service version default, but you MUST provide an entry in the Unity config section to ensure the default WPF implementation is override in Cinch to use the Test version.
    IUIVisualizerService

    Not required, default is used, but you should provide the popups this service manages in the constructor of the WPF apps main window, or some other suitable place

    You can use the Cinch test service version default, but you MUST provide an entry in the Unity config section to ensure the default WPF implementation is override in Cinch to use the Test version.

    As shown above if you are planning on using all the default Cinch services, you do not need to provide an App.Config for the main WPF app, though you MUST for any test projects to ensure the default services (the WPF implementations) are overrided inside the Cinch service resolution code.

    Actual Application

    Any application based on Cinch does not really need to provide any service implementations as default WPF ones are added and used internally. If however you wish to change one of the default WPF services you MUST supply a new Unity container App.Config section which overrides the default implementation of the service type.

    Here is an example App.Config for a Cinch app. Remember this MUST include any services that you may have changed, shown below is a specialization of the Cinch.IUIVisualizerService service that may provide extra functionality.

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      
      <configSections>
    
        <section name="unity"
            type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
                  Microsoft.Practices.Unity.Configuration" />  
      </configSections>
    
      <!-- Unity Config Section -->
      <unity>
        <containers>
          <container>
    
            <types>
    
              <type
                  type="Cinch.IUIVisualizerService, Cinch"
                  mapTo="MVVM.Demo.MyFunkyWPFUIVisualizerService, MVVM.Demo"/>
    
            </types>
          </container>
        </containers>
    
      </unity>
    
    </configuration>

    You can see that the apps implementation of the Cinch.IUIVisualizerService service is injected using Unity into Cinch, and will override the default implementation of Cinch.IUIVisualizerService within Cinch. Where previously Cinch would have previously tried to have used the default WPF implementation.

    Units Tests

    Cinch is designed to Unit testable, as such you can supply alternative services to Cinch using injection/Unity. But to be honest, it is more than likely the default implementations supplied with Cinch will be more than adequate, but you can decide that after you have read about them below.

    Anyway you either create your own test implementations of all the Cinch required services or use the defaults supplied, and then you must inject them in using using Unity into Cinch.

    Here is what your Unit Test project App.Config should look like:

    <?xml version="1.0"?>
    <configuration>
    
      <configSections>
        <section name="unity"
            type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
                  Microsoft.Practices.Unity.Configuration" />
      </configSections>
    
      <!-- Unity Config Section -->
      <unity>
        <containers>
          <container>
            <types>
    
              <type
                  type="Cinch.IUIVisualizerService, Cinch"
                  mapTo="Cinch.TestUIVisualizerService, Cinch"/>
    
              <type
                  type="Cinch.IMessageBoxService, Cinch"
                  mapTo="Cinch.TestMessageBoxService, Cinch"/>
    
              <type
                  type="Cinch.IOpenFileService, Cinch"
                  mapTo="Cinch.TestOpenFileService, Cinch"/>
    
              <type
                  type="Cinch.ISaveFileService, Cinch"
                  mapTo="Cinch.TestSaveFileService, Cinch"/>
    
            </types>
    
          </container>
        </containers>
      </unity>
    
    </configuration>
    

    This ensures that the default WPF service implementations are overriden by Unit Test service implementations.

    Exposed Services

    So you have now seen that there is Unity IOC container that is responsible for locating and loading the right services as dictated by the current App.Config file. Well in Cinch the story doesn't end there. You see the thing is within Cinch, the general idea is that there is a kind of all powerful ViewModel base class (Cinch.ViewModelBase) and that providing you inherit from that you will get some good stuff for free. Exposed services is one such thing.

    You may be asking, why do we need to do more stuff with the services, I thought that is what the Unity IOC container was doing for us. Well that's 1/2 right, what the Unity IOC container does is get the current services (as dictated by the App.Config) into the Cinch.ViewModelBase. Which is all cool. The problem with the Unity IOC container is that if you try and request a service from it, it appears that it gives you a different instance each time. Which may be cool if your service implementations do not require any state, but in Cinch some of the test services DO require state. So this just didn't cut it. So what happens is that the Unity read in services are added to a static available property on the Cinch.ViewModelBase. This property is a ServiceProvider, which is nothing more than a wrapper around a Dictionary really. So when you request a service from the Cinch.ViewModelBase. using the Resolve<T> method you will see in a minute, the ServiceProvider will examine its internal Dictionary and return the single instance of the service. This ensures we are always working with the same instance of a service.

    This diagram made help explain this a bit better.

    And here is the relevant code from the Cinch.ViewModelBase class.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Configuration;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    
    using Microsoft.Practices.Unity;
    using Microsoft.Practices.Unity.Configuration;
    
    namespace Cinch
    {
        /// <summary>
        /// Provides a base class for ViewModels to inherit from. This 
        /// base class provides the following
        /// <list type="Bullet">
        /// <item>Mediator pattern implementation</item>
    
        /// <item>Service resolution</item>
        /// <item>Window lifetime virtual method hooks</item>
        /// <item>INotifyPropertyChanged</item>
    
        /// </list>
        /// </summary>
        public abstract class ViewModelBase : 
            INotifyPropertyChanged, IDisposable
        {
           
    
            /// <summary>
            /// Service resolver for view models.  Allows derived types to add/remove
            /// services from mapping.
            /// </summary>
            public static readonly ServiceProvider ServiceProvider = new ServiceProvider();
    
                  
    
            /// <summary>
    
            /// Registers the default service implemenations with the Unity container, and
            /// then configures Unity container (which allows for changes to be made to pick
            /// up overriden services within Unity configuration).
            /// And finally add all services found to a list of Core services which are available
            /// to the ViewModelBase class
            /// </summary>
            static ViewModelBase()
            {
    
                try
                {
                    //regiser defaults
                    RegisterDefaultServices();
    
                    //configure Unity (there could be some different Service implementations
                    //in the config that override the defaults just setup
                    UnityConfigurationSection section = (UnityConfigurationSection)
                                   ConfigurationManager.GetSection("unity");
                    if (section != null && section.Containers.Count > 0)
                    {
                        section.Containers.Default.Configure(UnitySingleton.Instance.Container);
                    }
                    
                    //fetch the core service
                    FetchCoreServiceTypes();
                }
                catch(Exception ex)
                {
                    throw new ApplicationException(
    			           "There was a problem configuring the Unity container\r\n" + ex.Message);
                }
            }
          
    
    
            /// <summary>
            /// This resolves a service type and returns the implementation.
            /// </summary>
            /// <typeparam name="T">Type to resolve</typeparam>
    
            /// <returns>Implementation</returns>
            protected T Resolve<T>()
            {
                return ServiceProvider.Resolve<T>();
            }
    
                
    
            /// <summary>
            /// This method registers default services with the service provider. 
            /// These can be overriden by providing a new service implementation 
            /// and a new Unity config section in the project where the new service 
            /// implementation is defined 
            /// </summary>
    
            private static void RegisterDefaultServices()
            {
                //try and add Logger if there is one available
                try
                {
                    UnitySingleton.Instance.Container.RegisterInstance(
                        typeof(ILoggerService), 
                        new WPFLoggerService());
    
                    logger = (ILoggerService)UnitySingleton.Instance.Container.Resolve(
                        typeof(ILoggerService));
    
                    //Although the ILoggerService is exposed as a regular property, we can
                    //also add it, in case user want to get it using Resolve<T> method as
                    //they do for resolving other services
                    ServiceProvider.Add(typeof(ILoggerService), logger);
                }
                catch
                {
                }
    
    
                //try add other default services
                //users can override this using specific Unity App.Config
                //section entry
                try
                {
    			
                    //IUIVisualizerService : Register a default WPFUIVisualizerService
                    UnitySingleton.Instance.Container.RegisterInstance(
                        typeof(IUIVisualizerService), new WPFUIVisualizerService());			
    			
                    //IMessageBoxService : Register a default WPFMessageBoxService
                    UnitySingleton.Instance.Container.RegisterInstance(
                        typeof(IMessageBoxService), new WPFMessageBoxService());
    
                    //IOpenFileService : Register a default WPFOpenFileService
                    UnitySingleton.Instance.Container.RegisterInstance(
                        typeof(IOpenFileService), new WPFOpenFileService());
    
                    //ISaveFileService : Register a default WPFSaveFileService
                    UnitySingleton.Instance.Container.RegisterInstance(
                        typeof(ISaveFileService), new WPFSaveFileService());
    
                }
                catch (ResolutionFailedException rex)
                {
                    LogExceptionIfLoggerAvailable(rex);
                }
                catch (Exception ex)
                {
                    LogExceptionIfLoggerAvailable(ex);
                }
            }
    
    
            /// <summary>
            /// This method registers services with the service provider.
            /// </summary>
            private static void FetchCoreServiceTypes()
            {
                try
                {
                    //IUIVisualizerService : Allows popup management
                    IUIVisualizerService uiVisualizerService = 
                        (IUIVisualizerService)UnitySingleton.Instance.Container.Resolve(
                            typeof(IUIVisualizerService));
                    ServiceProvider.Add(typeof(IUIVisualizerService), uiVisualizerService);
    
                    //IMessageBoxService : Allows MessageBoxs to be shown 
                    IMessageBoxService messageBoxService = 
                        (IMessageBoxService)UnitySingleton.Instance.Container.Resolve(
                            typeof(IMessageBoxService));
                    ServiceProvider.Add(typeof(IMessageBoxService), messageBoxService);
    
                    //IOpenFileService : Allows Opening of files 
                    IOpenFileService openFileService = 
                        (IOpenFileService)UnitySingleton.Instance.Container.Resolve(
                            typeof(IOpenFileService));
                    ServiceProvider.Add(typeof(IOpenFileService), openFileService);
    
                    //ISaveFileService : Allows Saving of files 
                    ISaveFileService saveFileService =
                        (ISaveFileService)UnitySingleton.Instance.Container.Resolve(
                            typeof(ISaveFileService));
                    ServiceProvider.Add(typeof(ISaveFileService), saveFileService);
    
    
                }
                catch (ResolutionFailedException rex)
                {
                    LogExceptionIfLoggerAvailable(rex);
                }
                catch (Exception ex)
                {
                    LogExceptionIfLoggerAvailable(ex);
                }
            }
        }
    }
    

    Cinch Services Available

    Services are really nothing more than an interface that can implemented any way you like. So to make a WPF implementation of a service you would implement the service interface for WPF. To do a test service you would implement the service for a Unit test. etc etc

    The following subsections shall outline what actual Test/WPF services are available.

    NOTE : I did say WPF not Silverlight, Cinch is a WPF framework, it is not targeting Silverlight, I guess it could be made to do so, but that was not the intention of it.

    EventLogger Service

    1. Available for : WPF UI Project

    Whilst IU totally abhor the Windows EventLog, I do find that it is very easy to write to, so there is a simple EventLogger service that logs to the Windows event log. The service interface for ILoggerService is very simple and looks like this.

    public interface ILoggerService
    {
        void Log(LogType logType, String logEntry);
        void Log(LogType logType, Exception ex);
    }
    

    So to use this from a ViewModel you would simply do something like the following:

    Resolve.<ILoggerService>().Log(LogType.Information,"Saved client Id");

     

    MessageBox Service

    1. Available for : WPF UI Project
    2. Available for : Unit Test Project

    Cinch provides a novel way of dealing with Unit Test service implementations. Whilst it is possible to use your favourite mocking framework (RhinoMocks/Moq etc etc), sometimes that is not enough. Imagine that you have a section of code in a ViewModel something like the following:

    var messageBoxService = this.Resolve();
    
    if (messageBoxService.ShowYesNo("You sure",
        CustomDialogIcons.Question) == CustomDialogResults.Yes)
    {
        if (messageBoxService.ShowYesNo("You totally sure",
            CustomDialogIcons.Question) == CustomDialogResults.Yes)
        {
            //DO IT
        }
    }

    Where we have an atomic bit of code within a ViewModel that needs to be fully tested by Unit Tests. Using Mocks we could provide a Mock Cinch.IMessageBoxService service implementation. But this would NOT work as we could only be able to provide a single response, which is not the same as what the real WPF Cinch.IMessageBoxService would do, as the user would be free to use an actual MessageBox and may pick Yes/No/Cancel at random. So clearly Mocks are not enough. We need a better idea.

    So what Cinch does is to provide a Unit Test Cinch.IMessageBoxService service implementation which allows the Unit Test to enque response Func<CustomDialogResults> (which are after all just delegates), which allows us to provide callback code that will be called by the ViewModel code. This allows us to do whatever the hell we want in the enqueued callback Func<CustomDialogResults>, as supplied by the unit tests.

    This diagram may help to explain this concept a bit better.

    So what happens is that the unit test enques all the responses that are required by using Func<CustomDialogResults> (which are the callback delegates) which are then called from the Unit Test implementation of the Cinch.IMessageBoxService service implementation.

    Here is an example of what the Unit Test implementation of the Cinch.IMessageBoxService service implementation looks like for a ShowYesNo() Cinch.IMessageBoxService service implementation method call.

    /// <summary>
    /// Returns the next Dequeue ShowYesNo response expected. See the tests for 
    /// the Func callback expected values
    /// </summary>
    /// <param name="message">The message to be displayed.</param>
    /// <param name="icon">The icon to be displayed.</param>
    
    /// <returns>User selection.</returns>
    public CustomDialogResults ShowYesNo(string message, CustomDialogIcons icon)
    {
        if (ShowYesNoResponders.Count == 0)
            throw new ApplicationException(
                "TestMessageBoxService ShowYesNo method expects a Func<CustomDialogResults> callback \r\n" +
                "delegate to be enqueued for each Show call");
        else
        {
            Func<CustomDialogResults> responder = ShowYesNoResponders.Dequeue();
            return responder();
        }
    }

    It can be seen that the Unit Test implementation of the Cinch.IMessageBoxService service implementation for the ShowYesNo() method, simple dequeues the next Func<CustomDialogResults> (which are after all just delegates) and calls the Func<CustomDialogResults> (which is queud up in the actaul Unit Test) and uses the result from the call to the Func<CustomDialogResults>.

    Here is an example of how you might set up Unit Test code to enqueue the correct Func<CustomDialogResults> responses for the ViewModel code we saw above.

    testMessageBoxService.ShowYesNoResponders.Enqueue
        (() =>
            {
    		
    	          //return Yes for "Are sure" ViewModel prompt
                return CustomDialogResults.Yes;
            }
        );
    
    testMessageBoxService.ShowYesNoResponders.Enqueue
        (() =>
            {
    
    	          //return Yes for "Are totally sure" ViewModel prompt
                return CustomDialogResults.Yes;
            }
        );
    

    By using this method we can gaurentee we drive the ViewModel code through any test path we want to. It is a very powerful technique.

    The Cinch.IMessageBoxService service interface looks like this

    /// <summary>
    /// This interface defines a interface that will allow 
    /// a ViewModel to show a messagebox
    /// </summary>
    public interface IMessageBoxService
    {
        /// <summary>
        /// Shows an error message
        /// </summary>
    
        /// <param name="message">The error message</param>
        void ShowError(string message);
    
        /// <summary>
        /// Shows an information message
        /// </summary>
        /// <param name="message">The information message</param>
    
        void ShowInformation(string message);
    
        /// <summary>
        /// Shows an warning message
        /// </summary>
        /// <param name="message">The warning message</param>
        void ShowWarning(string message);
    
        /// <summary>
    
        /// Displays a Yes/No dialog and returns the user input.
        /// </summary>
        /// <param name="message">The message to be displayed.</param>
        /// <param name="icon">The icon to be displayed.</param>
    
        /// <returns>User selection.</returns>
        CustomDialogResults ShowYesNo(string message, CustomDialogIcons icon);
    
        /// <summary>
        /// Displays a Yes/No/Cancel dialog and returns the user input.
        /// </summary>
        /// <param name="message">The message to be displayed.</param>
    
        /// <param name="icon">The icon to be displayed.</param>
        /// <returns>User selection.</returns>
        CustomDialogResults ShowYesNoCancel(string message, CustomDialogIcons icon);
    
        /// <summary>
        /// Displays a OK/Cancel dialog and returns the user input.
        /// </summary>
    
        /// <param name="message">The message to be displayed.</param>
        /// <param name="icon">The icon to be displayed.</param>
        /// <returns>User selection.</returns>
    
        CustomDialogResults ShowOkCancel(string message, CustomDialogIcons icon);
    
    }
    

     

    Open File Service

    1. Available for : WPF UI Project
    2. Available for : Unit Test Project

    This works roughly the same way as just outlined above for the Cinch.IMessageBoxService service, but this time the Enqueued values are Queue<Func<bool?>>. Which means you can simulate a file being opened from within the unit test, by enquing the required Func<bool?> values as needed by the ViewModel code currently under test.

    The Cinch.IOpenFileService service interface looks like this

    /// <summary>
    /// This interface defines a interface that will allow 
    /// a ViewModel to open a file
    /// </summary>
    public interface IOpenFileService
    {
        /// <summary>
        /// FileName
        /// </summary>
    
        String FileName { get; set; }
    
        /// <summary>
        /// Filter
        /// </summary>
        String Filter { get; set; }
    
        /// <summary>
        /// Filter
        /// </summary>
        String InitialDirectory { get; set; }
    
        /// <summary>
    
        /// This method should show a window that allows a file to be selected
        /// </summary>
        /// <param name="owner">The owner window of the dialog</param>
        /// <returns>A bool from the ShowDialog call</returns>
        bool? ShowDialog(Window owner);
    }

    Save File Service

    1. Available for : WPF UI Project
    2. Available for : Unit Test Project

    This work roughly the same way as just outlined above for the Cinch.IMessageBoxService service, but this time the Enqueued values are Queue<Func<bool?>>. Which means you can simulate a file being saved from within the unit test, by enquing the required Func<bool?> values as needed by the ViewModel code currently under test.

    For example you may wish to actually create a file in the enqueued Func<bool?> that you queued up in the unit test, and only then return true, which a ViewModel can then check, and proceed to use the file that you actually saved within the Unit Test.

    For example you could do something like this inside a Unit Test

    testSaveFileService.ShowDialogResponders.Enqueue
        (() =>
          {
    
    	      String path = @"c:\test.txt";
            if (!File.Exists(path)) 
            {
                // Create a file to write to.
                using (StreamWriter sw = File.CreateText(path)) 
                {
                    sw.WriteLine("Hello");
                    sw.WriteLine("Cinch");
                }    
            }
    
            testSaveFileService.FileName = path ;
            return true;
          }
        );
    

    The Cinch.ISaveFileService service interface looks like this

    /// <summary>
    /// This interface defines a interface that will allow 
    /// a ViewModel to save a file
    /// </summary>
    public interface ISaveFileService
    {
        /// <summary>
        /// FileName
        /// </summary>
        Boolean OverwritePrompt { get; set; }
    
        /// <summary>
    
        /// FileName
        /// </summary>
        String FileName { get; set; }
    
        /// <summary>
        /// Filter
        /// </summary>
        String Filter { get; set; }
    
        /// <summary>
        /// Filter
        /// </summary>
    
        String InitialDirectory { get; set; }
    
        /// <summary>
        /// This method should show a window that allows a file to be saved
        /// </summary>
        /// <param name="owner">The owner window of the dialog</param>
        /// <returns>A bool from the ShowDialog call</returns>
    
        bool? ShowDialog(Window owner);
    }

    Popup Window Service

    • Available for : WPF UI Project (but not inside Cinch code, see demo app)
    • Available for : Unit Test Project

    I do not know about you lot, but we are in the middle of a very large WPF project at work, and although I am not fan of popup windows, we do have some none the less. Popups kind of don't play well with the normal way that most folk do MVVM. Most folk would make a View a UserControl that has a ViewModel as a DataContext. Which is cool. But occassionally we need to show a popup and have it edit some object within the current ViewModel or allow the user to cancel the edit.

    How Cinch does this, is it provides a service called Cinch.IUIVisualizerService which is a fairly complex beast. But it has to be. Here is the basic idea

    The WPF app that has the popups MUST provide the popups to the Cinch.IUIVisualizerService (the attached demo app shows this). This provided implementatoion is expected to be injected via Unity into Cinch.

    The Cinch.IUIVisualizerService WPF implementation as supplied in the demo app, supplies popup window types that are not available to any other project except the actual WPF app. Which is why Cinch can not provide these popups to the a Cinch.IUIVisualizerService WPF implementation. So what has to happen is that the WPF app must tell the Cinch.IUIVisualizerService WPF implementation what popups are expected. This can be done in the constructor of the apps main window as follows:

    public MainWindow()
    {
        //register known windows
        IUIVisualizerService popupVisualizer = 
            ViewModelBase.ServiceProvider.Resolve<IUIVisualizerService>();
        popupVisualizer.Register("PropertyListPopup", 
            typeof(PropertyListPopup));
        popupVisualizer.Register("ReferencedAssembliesPopup", 
            typeof(ReferencedAssembliesPopup));
        popupVisualizer.Register("StringEntryPopup", 
            typeof(StringEntryPopup));
        this.DataContext = new MainWindowViewModel();
        this.InitializeComponent();
    }

    The demo app supplied WPF implementation of the Cinch.IUIVisualizerService service looks like this:

    using System;
    using System.Collections.Generic;
    using System.Windows;
    
    using Cinch;
    
    namespace MVVM.Demo
    {
        /// <summary>
        /// This class implements the IUIVisualizerService for WPF purposes.
        /// This implementation HAD TO be in the Main interface project, as
        /// it needs to know about Popup windows that are not known about in 
        /// the ViewModel or Cinch projects.
        /// </summary>
    
        public class WPFUIVisualizerService : Cinch.IUIVisualizerService
        {
            #region Data
            private readonly Dictionary<string, Type> _registeredWindows;
            #endregion
    
            #region Ctor
            /// <summary>
            /// Constructor
            /// </summary>
            public WPFUIVisualizerService()
            {
                _registeredWindows = new Dictionary<string, Type>();
    
                //register known windows
                Register("AddEditOrderPopup", typeof(AddEditOrderPopup));
            }
            #endregion
    
            #region Public Methods
            /// <summary>
    
            /// Registers a collection of entries
            /// </summary>
            /// <param name="startupData"></param>
            public void Register(Dictionary<string, Type> startupData)
            {
                foreach (var entry in startupData)
                    Register(entry.Key, entry.Value);
            }
    
            /// <summary>
            /// Registers a type through a key.
            /// </summary>
    
            /// <param name="key">Key for the UI dialog</param>
            /// <param name="winType">Type which implements dialog</param>
            public void Register(string key, Type winType)
            {
                if (string.IsNullOrEmpty(key))
                    throw new ArgumentNullException("key");
                if (winType == null)
                    throw new ArgumentNullException("winType");
                if (!typeof(Window).IsAssignableFrom(winType))
                    throw new ArgumentException("winType must be of type Window");
    
                lock (_registeredWindows)
                {
                    _registeredWindows.Add(key, winType);
                }
            }
    
            /// <summary>
            /// This unregisters a type and removes it from the mapping
            /// </summary>
    
            /// <param name="key">Key to remove</param>
            /// <returns>True/False success</returns>
            public bool Unregister(string key)
            {
                if (string.IsNullOrEmpty(key))
                    throw new ArgumentNullException("key");
    
                lock (_registeredWindows)
                {
                    return _registeredWindows.Remove(key);
                }
            }
    
            /// <summary>
            /// This method displays a modaless dialog associated with the given key.
            /// </summary>
    
            /// <param name="key">Key previously registered with the UI controller.</param>
            /// <param name="state">Object state to associate with the dialog</param>
            /// <param name="setOwner">Set the owner of the window</param>
    
            /// <param name="completedProc">Callback used when UI closes (may be null)</param>
            /// <returns>True/False if UI is displayed</returns>
            public bool Show(string key, object state, bool setOwner, 
                EventHandler<UICompletedEventArgs> completedProc)
            {
                Window win = CreateWindow(key, state, setOwner, completedProc, false);
                if (win != null)
                {
                    win.Show();
                    return true;
                }
                return false;
            }
    
            /// <summary>
    
            /// This method displays a modal dialog associated with the given key.
            /// </summary>
            /// <param name="key">Key previously registered with the UI controller.</param>
            /// <param name="state">Object state to associate with the dialog</param>
            /// <returns>True/False if UI is displayed.</returns>
    
            public bool? ShowDialog(string key, object state)
            {
                Window win = CreateWindow(key, state, true, null, true);
                if (win != null)
                    return win.ShowDialog();
    
                return false;
            }
            #endregion
    
            #region Private Methods
            /// <summary>
            /// This creates the WPF window from a key.
            /// </summary>
            /// <param name="key">Key</param>
            /// <param name="dataContext">DataContext (state) object</param>
    
            /// <param name="setOwner">True/False to set ownership to MainWindow</param>
            /// <param name="completedProc">Callback</param>
            /// <param name="isModal">True if this is a ShowDialog request</param>
    
            /// <returns>Success code</returns>
            private Window CreateWindow(string key, object dataContext, bool setOwner, 
                EventHandler<UICompletedEventArgs> completedProc, bool isModal)
            {
                if (string.IsNullOrEmpty(key))
                    throw new ArgumentNullException("key");
    
                Type winType;
                lock (_registeredWindows)
                {
                    if (!_registeredWindows.TryGetValue(key, out winType))
                        return null;
                }
    
                var win = (Window)Activator.CreateInstance(winType);
                win.DataContext = dataContext;
                if (setOwner)
                    win.Owner = Application.Current.MainWindow;
    
                if (dataContext != null)
                {
                    var bvm = dataContext as ViewModelBase;
                    if (bvm != null)
                    {
                        if (isModal)
                        {
                            bvm.CloseRequest += ((s, e) =>
                            {
                                try
                                {
                                    win.DialogResult = e.Result;
                                }
                                catch (InvalidOperationException)
                                {
                                    win.Close();
                                }
                            });
                        }
                        else
                        {
                            bvm.CloseRequest += ((s, e) => win.Close());
                        }
                        bvm.ActivateRequest += ((s, e) => win.Activate());
                    }
                }
    
                if (completedProc != null)
                {
                    win.Closed +=
                        (s, e) =>
    
                            completedProc
                            (this,new UICompletedEventArgs 
                                {   State = dataContext, 
                                    Result = (isModal) ? win.DialogResult : null 
                                }
                            );
    
                }
    
                return win;
            }
            #endregion
        }
    }
    

    Its job is to set the newly requested popup window to have a DataContext set to some object, and also to listen to close commands coming from the launching ViewModel which instruct the popup to close.

    So to use this service from a ViewModel to show a popup and set its DataContext we would do something like the following :

    addEditOrderVM.CurrentViewMode = ViewMode.AddMode;
    addEditOrderVM.CurrentCustomer = CurrentCustomer;
    bool? result = uiVisualizerService.ShowDialog("AddEditOrderPopup", addEditOrderVM);
    
    if (result.HasValue && result.Value)
    {
        CloseActivePopUpCommand.Execute(true);
    }
    

    It can be seen that this ViewModel code snippet is using one of the names of the registered (from the WPF apps implementation of the Cinch.IUIVisualizerService) popup windows, and then showing the popup modally, and waiting for a DialogResult (bool?), and if the DialogResult was true the ViewModel closes the popup.

    Note that we are able to set the DialogResult value we would like returned when the CloseActivePopupCommand is executed, which may be handy if you programmatically want to close the active popup rather than let the user use the button that is linked to the CloseActivePopUpCommand, which will always return true as shown below.

    This is all cool, so we can now show popups from a ViewModel set a DataContext listen for a DialogResult and then close the popup. Sounds cool. There is however a trick or 2 you need to know. These are as follows:

    Tricks When Working With Popups And Cinch

    Following these simply rules should help

    Make sure your save and cancel buttons have the IsDefault and IsCancel set like

    <Button Content="Save" IsDefault="True" 
    Command="{Binding CloseActivePopUpCommand}"
    CommandParameter="True"/>
    
    <Button Content="Cancel" IsCancel="True"/>
    

    The Cinch.IUIVisualizerService looks like this

    /// <summary>
    /// This interface defines a UI controller which can be used to display dialogs
    /// in either modal or modaless form from a ViewModel.
    /// </summary>
    public interface IUIVisualizerService
    {
        /// <summary>
        /// Registers a type through a key.
        /// </summary>
    
        /// <param name="key">Key for the UI dialog</param>
        /// <param name="winType">Type which implements dialog</param>
        void Register(string key, Type winType);
    
        /// <summary>
        /// This unregisters a type and removes it from the mapping
        /// </summary>
    
        /// <param name="key">Key to remove</param>
        /// <returns>True/False success</returns>
        bool Unregister(string key);
    
        /// <summary>
        /// This method displays a modaless dialog associated with the given key.
        /// </summary>
    
        /// <param name="key">Key previously registered with the UI controller.</param>
        /// <param name="state">Object state to associate with the dialog</param>
        /// <param name="setOwner">Set the owner of the window</param>
    
        /// <param name="completedProc">Callback used when UI closes (may be null)</param>
        /// <returns>True/False if UI is displayed</returns>
        bool Show(string key, object state, bool setOwner, 
            EventHandler<UICompletedEventArgs> completedProc);
    
        /// <summary>
    
        /// This method displays a modal dialog associated with the given key.
        /// </summary>
        /// <param name="key">Key previously registered with the UI controller.</param>
        /// <param name="state">Object state to associate with the dialog</param>
        /// <returns>True/False if UI is displayed.</returns>
    
        bool? ShowDialog(string key, object state);
    }
    

    If your app doesn't use popups you can

    • A) Simply provide a dummy implementation of this service in your app and ensure that it is injected into Cinch using Unity
    • B) Edit the Cinch.ViewModelBase class to remove all references of IUIVisualizerService

     

    Threading Helpers

    Threading is one of those things that we don't have to do that often (or maybe you do), but every time we need to do it again it seems to bite our ass all over again. To this end Cinch provides a couple of useful Threading helper classes which are described below.

    Dispatcher Extension Methods

    Cinch contains several extension methods that are quite useful when working with the Dispatcher, these extension methods allow the user of the extension method to invoke a block of code using the correct UI Dispatcher thread, and optionally at a given DispatcherPriority It is fairly simple and very easy to use. Here is the entire code for the Dispatcher extension methods.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows.Threading;
    
    namespace Cinch
    {
        /// <summary>
        /// Provides a set of commonly used Dispatcher extension methods
        /// </summary>
        public static class DispatcherExtensions
        {
            #region Dispatcher Extensions
            /// <summary>
            /// A simple threading extension method, to invoke a delegate
            /// on the correct thread if it is not currently on the correct thread
            /// which can be used with DispatcherObject types.
            /// </summary>
    
            /// <param name="dispatcher">The Dispatcher object on which to 
            /// perform the Invoke</param>
            /// <param name="action">The delegate to run</param>
            /// <param name="priority">The DispatcherPriority for the invoke.</param>
    
            public static void InvokeIfRequired(this Dispatcher dispatcher,
                Action action, DispatcherPriority priority)
            {
                if (!dispatcher.CheckAccess())
                {
                    dispatcher.Invoke(priority, action);
                }
                else
                {
                    action();
                }
            }
    
            /// <summary>
            /// A simple threading extension method, to invoke a delegate
            /// on the correct thread if it is not currently on the correct thread
            /// which can be used with DispatcherObject types.
            /// </summary>
            /// <param name="dispatcher">The Dispatcher object on which to 
            /// perform the Invoke</param>
            /// <param name="action">The delegate to run</param>
    
            public static void InvokeIfRequired(this Dispatcher dispatcher, Action action)
            {
                if (!dispatcher.CheckAccess())
                {
                    dispatcher.Invoke(DispatcherPriority.Normal, action);
                }
                else
                {
                    action();
                }
            }
    
            /// <summary>
            /// A simple threading extension method, to invoke a delegate
            /// on the correct thread if it is not currently on the correct thread
            /// which can be used with DispatcherObject types.
            /// </summary>
            /// <param name="dispatcher">The Dispatcher object on which to 
            /// perform the Invoke</param>
            /// <param name="action">The delegate to run</param>
    
            public static void InvokeInBackgroundIfRequired(
                this Dispatcher dispatcher, 
                Action action)
            {
                if (!dispatcher.CheckAccess())
                {
                    dispatcher.Invoke(DispatcherPriority.Background, action);
                }
                else
                {
                    action();
                }
            }
    
            /// <summary>
            /// A simple threading extension method, to invoke a delegate
            /// on the correct thread asynchronously if it is not currently 
            /// on the correct thread which can be used with DispatcherObject types.
            /// </summary>
            /// <param name="dispatcher">The Dispatcher object on which to 
            /// perform the Invoke</param>
            /// <param name="action">The delegate to run</param>
    
            public static void InvokeAsynchronouslyInBackground(
                this Dispatcher dispatcher, Action action)
            {
                if (dispatcher != null)
                    dispatcher.BeginInvoke(DispatcherPriority.Background, action);
                else
                    action();
            }
            #endregion
        }
    }
    

    To use this extension method is dead easy, you would just do something like this:

    Dispatcher.InvokeIfRequired(() =>
    {
    
    	//run some code on the correct UI Dispatcher thread
    	//at the DispatcherPriority stated
    
    },DispatcherPriority.Background);
    

    Obviously if you wanted to do something with the Dispatcher for a View from a ViewModel you would need to use the CurrentDispatcher static property.

     

    App.DoEvents

    Within WPF there is no App.DoEvents() that some WinForms convertees might be expecting. For those that have not used the the old WinForms App.DoEvents(). What that method used to do was forced the message pump to process all queued messages. This would sometimes help with things like selection changes, pending events etc etc.

    Basically it was quite a useful feature, but as I say there is nothing like this supplied out of the box in WPF. As luck would have it it is not too much bother to fashion your own using the Dispatcher (the place where messages are queued), and a DispatcherFrame. Cinch provides 2 flavours of App.DoEvents:

    • One where the user specifies a DispatcherPriority for the lower limit that all pending Dispatcher messages should be effectively pumped.
    • One where all Dispatcher messages should be effectively pumped.

    The following code shows how this works

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows.Threading;
    using System.Security.Permissions;
    
    namespace Cinch
    {
        
        public static class ApplicationHelper
        {
            #region DoEvents
            /// <summary>
            /// Forces the WPF message pump to process all enqueued messages
            /// that are above the input parameter DispatcherPriority.
            /// </summary>
            /// <param name="priority">The DispatcherPriority to use
            /// as the lowest level of messages to get processed</param>
            [SecurityPermissionAttribute(SecurityAction.Demand,
                Flags = SecurityPermissionFlag.UnmanagedCode)]
            public static void DoEvents(DispatcherPriority priority)
            {
                DispatcherFrame frame = new DispatcherFrame();
                DispatcherOperation dispatcherOperation = 
                    Dispatcher.CurrentDispatcher.BeginInvoke(priority, 
                        new DispatcherOperationCallback(ExitFrameOperation), frame);
                
                Dispatcher.PushFrame(frame);
    
                if (dispatcherOperation.Status != DispatcherOperationStatus.Completed)
                {
                    dispatcherOperation.Abort();
                }
            }
    
    
            /// <summary>
    
            /// Forces the WPF message pump to process all enqueued messages
            /// that are DispatcherPriority.Background or above
            /// </summary>
            [SecurityPermissionAttribute(SecurityAction.Demand,
                Flags = SecurityPermissionFlag.UnmanagedCode)]
            public static void DoEvents()
            {
                DoEvents(DispatcherPriority.Background);
            }
    
    
            /// <summary>
            /// Stops the dispatcher from continuing
            /// </summary>
            private static object ExitFrameOperation(object obj)
            {
                ((DispatcherFrame)obj).Continue = false;
                return null;
            }
            #endregion
        }
    }
    

    So to use this WPF App.DoEvents() all you need to do is, using call like the following:

    ApplicationHelper.DoEvents();
    

    Or to process all message of a particular DispatcherPriority of above, use the following

    ApplicationHelper.DoEvents(DispatcherPriority.Background);
    

    Background Tasks

    One difficulty I have had when working with large DataSets and MVVM was how to UnitTest and synchronize using Background tasks. Some use the ThreadPool(or the very cool SmartThreadPool hosted here at codeproject), others use BackgroundWorker. I will try and use what fits the job generally. However for Cinch I made the decision to include a BackgroundWorker wrapper class that I found on the internet. I did make a few changes to it to change the completed callback ordering. I have to say it is quite neat.

    Is it called BackgroundTaskManager<T> and takes a generic which indicates the return value when the background task is run. So ideally you would only do an operation that returns a single type in the background, and use that as the result. Of course you could have multiple BackgroundTaskManager<T> objects within a single ViewModel each doing a different background activity.

    What it allows you to do is hook up a Func<T> taskFunc, Action<T> completionAction within the constructor. The Func<T> taskFunc is used wire up against the BackgroundWorker.DoWork() method. Whilst the Action<T> completionAction is called when the BackgroundWorker.Completed event is raised.

    One thing that I added was a AutoResetEvent WaitHandle property, which a Unit test may set to allow the Unit test to wait for a signal on the AutoResetEvent WaitHandle property supplied via the Unit test. More on this later.

    For now let's continue to look at what this BackgroundTaskManager<T> class looks like, here it is:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Text;
    using System.Threading;
    
    namespace MVVM.ViewModels
    {
     
        public class BackgroundTaskManager<T>
        {
            #region Data
            private Func<T> TaskFunc { get; set; }
            private Action<T> CompletionAction { get; set; }
            #endregion
    
            #region Ctor
            /// <summary>
            /// Constructs a new BackgroundTaskManager with
            /// the function to run, and the action to call when the function to run
            /// completes
            /// </summary>
    
            /// <param name="taskFunc">The function to run in the background</param>
            /// <param name="completionAction">The completed action to call
            /// when the background function completes</param>
            public BackgroundTaskManager(Func<T> taskFunc, Action<T> completionAction)
            {
                this.TaskFunc = taskFunc;
                this.CompletionAction = completionAction;
    
            }
            #endregion
    
            #region Public Properties
            /// <summary>
    
            /// Event invoked when a background task is started.
            /// </summary>
            [SuppressMessage("Microsoft.Usage", "CA2211:NonConstantFieldsShouldNotBeVisible",
                Justification = "Add/remove is thread-safe for events in .NET.")]
            public EventHandler<EventArgs> BackgroundTaskStarted;
    
            /// <summary>
            /// Event invoked when a background task completes.
            /// </summary>
            [SuppressMessage("Microsoft.Usage", "CA2211:NonConstantFieldsShouldNotBeVisible",
                Justification = "Add/remove is thread-safe for events in .NET.")]
            public EventHandler<EventArgs> BackgroundTaskCompleted;
    
            /// <summary>
    
            /// Allows the Unit test to be notified on Task completion
            /// </summary>
            public AutoResetEvent CompletionWaitHandle { get; set; }
            #endregion
            
            #region Public Methods
            /// <summary>
            /// Runs a task function on a background thread; 
            /// invokes a completion action on the main thread.
            /// </summary>
            public void RunBackgroundTask()
            {
                // Create a BackgroundWorker instance
                var backgroundWorker = new BackgroundWorker();
    
                // Attach to its DoWork event to run the task function and capture the result
                backgroundWorker.DoWork += delegate(object sender, DoWorkEventArgs e)
                {
                    e.Result = TaskFunc();
                };
    
                // Attach to its RunWorkerCompleted event to run the completion action
                backgroundWorker.RunWorkerCompleted += 
                    delegate(object sender, RunWorkerCompletedEventArgs e)
                {
                    // Call the completion action
                    CompletionAction((T)e.Result);
                    
                    // Invoke the BackgroundTaskCompleted event
                    var backgroundTaskFinishedHandler = BackgroundTaskCompleted;
                    if (null != backgroundTaskFinishedHandler)
                    {
                        backgroundTaskFinishedHandler.Invoke(null, EventArgs.Empty);
                    }
    
                };
    
                // Invoke the BackgroundTaskStarted event
                var backgroundTaskStartedHandler = BackgroundTaskStarted;
                if (null != backgroundTaskStartedHandler)
                {
                    backgroundTaskStartedHandler.Invoke(null, EventArgs.Empty);
                }
    
                // Run the BackgroundWorker asynchronously
                backgroundWorker.RunWorkerAsync();
            }
            #endregion
        }
    
    }
    

    So how do you use one of these here BackgroundTaskManager<T> classes. Well it is fairly easy actually. I am not going to cover unit testing yet, as that is the subject for another article. For now I will just show you how to use it from your ViewModel. Which is done as follows:

    1. Create a private property
    2. Create a public property to allow Unit tests access to the BackgroundTaskManager<T>
    3. Wire up the BackgroundTaskManager<T> Func<T> taskFunc, Action<T> completionAction within the constructor of the BackgroundTaskManager<T>
    4. Have some method do something with the  BackgroundTaskManager<T>

    This is all demonstrated in the code snippet shown below. We will see more of this when we come to create a ViewModel using Cinch and Unit testing with Cinch in later articles.

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Threading;
    using System.Windows.Threading;
    using System.Windows.Data;
    
    using Cinch;
    using MVVM.Models;
    using MVVM.DataAccess;
    
    
    namespace MVVM.ViewModels
    {
    
        public class SomeViewModel : Cinch.WorkspaceViewModel
        {
           
            //background workers
            private BackgroundTaskManager<DispatcherNotifiedObservableCollection<OrderModel>> 
                bgWorker = null;
    
     
            public AddEditCustomerViewModel()
            {
                
                //setup background worker
                SetUpBackgroundWorker();
    
     
            }
            
            /// <summary>
    
            /// Background worker which lazy fetches 
            /// Customer Orders
            /// </summary>
            public BackgroundTaskManager<DispatcherNotifiedObservableCollection<OrderModel>> BgWorker
            {
                get { return bgWorker; }
                set
                {
                    bgWorker = value;
                    OnPropertyChanged(() => BgWorker);
                }
            }
    
        
    
            /// <summary>
            /// Setup backgrounder worker Task/Completion action
            /// to fetch Orders for Customers
            /// </summary>
    
            private void SetUpBackgroundWorker()
            {
                bgWorker = new BackgroundTaskManager<DispatcherNotifiedObservableCollection<OrderModel>>(
                    () =>
                    {
                        return new DispatcherNotifiedObservableCollection<OrderModel>(
                            DataAccess.DataService.FetchAllOrders(
                                CurrentCustomer.CustomerId.DataValue).ConvertAll(
                                    new Converter<Order, OrderModel>(
                                          OrderModel.OrderToOrderModel)));
                    },
                    (result) =>
                    {
    
                        CurrentCustomer.Orders = result;
                        if (customerOrdersView != null)
                            customerOrdersView.CurrentChanged -=
                                CustomerOrdersView_CurrentChanged;
    
                        customerOrdersView =
                            CollectionViewSource.GetDefaultView(CurrentCustomer.Orders);
                        customerOrdersView.CurrentChanged +=
                            CustomerOrdersView_CurrentChanged;
                        customerOrdersView.MoveCurrentToPosition(-1);
    
                        HasOrders = CurrentCustomer.Orders.Count > 0;
                    });
            }
    
    
            /// <summary>
    
            /// Fetches all Orders for customer using BackgroundTaskManager<T>
            /// </summary>
            private void LazyFetchOrdersForCustomer()
            {
            	bgWorker.RunBackgroundTask();
            }
    
    
          
        }
    }
    
    

    ObservableCollection

    Another thing that occurs occasionally is that you may have an ObservableCollection that has items added to it that need to be marshalled back to a UI thread to make use of the new items. Cinch uses the following code to achieve this.

    /// <summary>
    /// This class provides an ObservableCollection which supports the 
    /// Dispatcher thread marshalling for added items. 
    /// 
    /// This class does not take support any thread sycnhronization of
    /// adding items using multiple threads, that level of thread synchronization
    /// is left to the user. This class simply marshalls the CollectionChanged
    /// call to the correct Dispatcher thread
    /// </summary>
    /// <typeparam name="T">Type this collection holds</typeparam>
    public class DispatcherNotifiedObservableCollection<T> : ObservableCollection<T>
    
    {
        #region Ctors
    
        public DispatcherNotifiedObservableCollection()
            : base()
        {
        }
    
        public DispatcherNotifiedObservableCollection(List<T> list)
            : base(list)
        {
        }
    
        public DispatcherNotifiedObservableCollection(IEnumerable<T> collection) 
            : base(collection)
        {
    
        }
        #endregion
    
        #region Overrides
        /// <summary>
        /// Occurs when an item is added, removed, changed, moved, 
        /// or the entire list is refreshed.
        /// </summary>
        public override event NotifyCollectionChangedEventHandler CollectionChanged;
    
        /// <summary>
    
        /// Raises the <see cref="E:System.Collections.ObjectModel.
        /// ObservableCollection`1.CollectionChanged"/> 
        /// event with the provided arguments.
        /// </summary>
        /// <param name="e">Arguments of the event being raised.</param>
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            var eh = CollectionChanged;
            if (eh != null)
            {
                Dispatcher dispatcher = 
                    (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()
                     let dpo = nh.Target as DispatcherObject
                     where dpo != null
                     select dpo.Dispatcher).FirstOrDefault();
    
                if (dispatcher != null && dispatcher.CheckAccess() == false)
                {
                    dispatcher.Invoke(DispatcherPriority.DataBind, 
                        (Action)(() => OnCollectionChanged(e)));
                }
                else
                {
                    foreach (NotifyCollectionChangedEventHandler nh 
                            in eh.GetInvocationList())
                        nh.Invoke(this, e);
                }
            }
        }
        #endregion
    }
    
    

    Doing MenuItems the MVVM way

    Newbies to the MVVM pattern will probably struggle with doing Menus using the MVVM pattern. Which is a shame as they are actually fairly simple and quite easy to tame, after all they are simple a hierarchical structure (think tree) that allows some code to be run either using a Click or can use a ICommand. They actually sound (at least operationally) a lot like Button objects, which also have a Click and can use an ICommand, and we know how to deal with those we just bind their Command property to a ViewModel exposed ICommand property. So why should Menus be any different. It turns out they are not, its just how we create the collection of Menus and Style them in the View that represents any difficulty at all.

    Representing MenuItems In The ViewModel

    Let's start with having a look at how we might represent a ViewModel friendly MenuItem object shall we. We could do something like the following:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows.Controls;
    using System.Windows.Media.Imaging;
    
    namespace Cinch
    {
        /// <summary>
        /// Provides a mechanism for constructing MenuItems
        /// within a ViewModel
        /// </summary>
        public class WPFMenuItem
        {
            #region Public Properties
            public String Text { get; set; }
            public String IconUrl { get; set; }
            public List<WPFMenuItem> Children { get; private set; }
            public SimpleCommand Command { get; set; }
            #endregion
    
            #region Ctor
            public WPFMenuItem(string item)
            {
                Text = item;
                Children = new List<WPFMenuItem>();
            }
            #endregion
        }
    }
    
    
    

    Simple enough right? So how do we expose a Menu from a ViewModel that the View can use, let's look at that next.

    Exposing MenuItems From The ViewModel

    Well this too is actually quite simple. We just expose a property of our ViewModel for the MenuItems we want to expose. Here is an example property within a ViewModel.

    /// <summary>
    /// Returns the bindbable Menu options
    /// </summary>
    public List<WPFMenuItem> OrderMenuOptions
    {
        get
        {
            return CreateMenus();
        }
    }
    
    

    The only part that really needs explaining is the call to the CreateMenus() method. This is nothing clever it just creates the MenuItems to expose from the ViewModel. Here is an example of what this might look like:

    /// <summary>
    /// Creates and returns the menu items
    /// </summary>
    private List<WPFMenuItem> CreateMenus()
    {
    
        var menu = new List<WPFMenuItem>();
    
        var miAddOrder = new WPFMenuItem("Add Order");
        miAddOrder.Command = AddOrderCommand;
        menu.Add(miAddOrder);
    
        var miEditOrder = new WPFMenuItem("Edit Order");
        miEditOrder.Command = EditOrderCommand;
        menu.Add(miEditOrder);
    
        var miDeleteOrder = new WPFMenuItem("Delete Order");
        miDeleteOrder.Command = DeleteOrderCommand;
        menu.Add(miDeleteOrder);
    
        return menu;
    
    }

    Can you see that in this method we are not only creating the structure of the MenuItems but we are also wiring up the MenuItems to the correct ViewModel available ICommands. It is that easy.

    Rendering The ViewModel Exposed MenuItems Within The View

    The last part of the puzzle is how to render the ViewModel exposed MenuItems within a particular View. Again this is not hard this can be achieved by firstly declaring a Menu in the View, and binding that to the ViewModels exposed MenuItems:

    <Menu x:Name="menu" Margin="0,0,0,0" Height="Auto" Foreground="White"
          ItemContainerStyle="{StaticResource ContextMenuItemStyle}"
          ItemsSource="{Binding MenuOptions}"
          VerticalAlignment="Top" Background="#FF000000">
    </Menu>

    Where each individual MenuItem is styled using the following Style.

    
    <Style x:Key="ContextMenuItemStyle">
        <Setter Property="MenuItem.Header" Value="{Binding Text}"/>
        <Setter Property="MenuItem.ItemsSource" Value="{Binding Children}"/>
        <Setter Property="MenuItem.Command" Value="{Binding Command}" />
        <Setter Property="MenuItem.Icon" Value="{Binding IconUrl, 
            Converter={StaticResource MenuIconConv}}" />
    </Style>
    

    Closeable ViewModels

    I don't know how many of the readers of this article are new to WPF and have come from WinForms, or how many of you are actually doing WPF, but I can tell you 1 thing. When WPF came out it did not come with a way to do MDI interfaces out of the box. I fact if you look at Expression blend which is a Microsoft WPF tool for working with WPF, which by the way was also written in WPF, you will see that it looks radically different from previous Microsoft developer tools such as Visual Studio. Expression Blend is a single window application, that uses some simple tricks to manage the content. These tricks are really clever layout such as using lots of expanders/tabs. It is however a 1 Window app.

    Most WPF apps you see out there are also 1 Window apps. When I do a new WPF app I try and make it a 1 Window app, of course sometimes popups are hard to avoid. In fact the demo code has a popup which you will see in a later article.

    But for now let us imagine we want to build a 1 Window app using tabs. How do we do that the MVVM way?

    Let's have a look into that shall we.

    Cinch actually provides a base ViewModel called WorkspaceViewModel, which provides a single Closed event, which can be used as a base class for your own ViewModels. Here is the code for that.

    using System;
    
    namespace Cinch
    {
        /// <summary>
        /// This ViewModelBase subclass requests to be removed 
        /// from the UI when its CloseWorkSpace executes.
        /// This class is abstract.
        /// </summary>
        public abstract class WorkspaceViewModel : ViewModelBase
        {
            #region Data
    
            private SimpleCommand closeWorkSpaceCommand;
            private Boolean isCloseable = true;
    
            #endregion 
    
            #region Constructor
    
            protected WorkspaceViewModel()
            {
                //This is used for popup control only
                closeWorkSpaceCommand = new SimpleCommand
                {
                    CanExecuteDelegate = x => true,
                    ExecuteDelegate = x => ExecuteCloseWorkSpaceCommand()
                };
    
            }
    
            #endregion // Constructor
    
            #region Public Properties
    
            /// <summary>
    
            /// Returns the command that, when invoked, attempts
            /// to remove this workspace from the user interface.
            /// </summary>
            public SimpleCommand CloseWorkSpaceCommand
            {
                get
                {
                    return closeWorkSpaceCommand;
                }
            }
    
            public Boolean IsCloseable
            {
                get { return isCloseable; }
                set
                {
                    isCloseable = value;
                    OnPropertyChanged(() => IsCloseable);
                }
            }
    
            #endregion // CloseCommand
    
            #region Private Methods
    
            /// <summary>
            /// Executes the CloseWorkSpace Command
            /// </summary>
            private void ExecuteCloseWorkSpaceCommand()
            {
                CloseWorkSpaceCommand.CommandSucceeded = false;
    
                EventHandler<EventArgs> handlers = CloseWorkSpace;
    
                // Invoke the event handlers
                if (handlers != null)
                {
                    try
                    {
                        handlers(this, EventArgs.Empty);
                        CloseWorkSpaceCommand.CommandSucceeded = true;
                    }
                    catch
                    {
                        Logger.Log(LogType.Error, "Error firing CloseWorkSpace event");
                    }
                }
    
            }
    
            #endregion
    
            #region CloseWorkSpace Event
            /// <summary>
    
            /// Raised when this workspace should be removed from the UI.
            /// </summary>
            public event EventHandler<EventArgs> CloseWorkSpace;
            #endregion 
        }
    }
    

    So to make use of this simple inherit from this class. Now what we need to do is build up a collection of these WorkspaceViewModel ViewModels and bind them to a TabControl. Lets see that next. Here is an example ViewModel that holds a collection of WorkspaceViewModel ViewModels, and caters for them being added/removed

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Data;
    
    using Cinch;
    using MVVM.Models;
    
    
    namespace MVVM.ViewModels
    {
    
     
        public class MainWindowViewModel : Cinch.ViewModelBase
        {
            
            private ObservableCollection<WorkspaceViewModel> workspaces;
    
            public MainWindowViewModel()
            {
                Workspaces = new ObservableCollection<WorkspaceViewModel>();
                Workspaces.CollectionChanged += this.OnWorkspacesChanged;
                StartPageViewModel startPageViewModel = new StartPageViewModel();
                startPageViewModel.IsCloseable = false;
                Workspaces.Add(startPageViewModel);
    
            }
    
    
            /// <summary>
            /// If we get a request to add a new Workspace, add a new WorkSpace to the 
            /// collection and hook up the CloseWorkSpace event in a weak manner
            /// </summary>
            private void OnWorkspacesChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (e.NewItems != null && e.NewItems.Count != 0)
                    foreach (WorkspaceViewModel workspace in e.NewItems)
                        workspace.CloseWorkSpace +=
                             new EventHandler<EventArgs>(OnCloseWorkSpace).
                                 MakeWeak(eh => workspace.CloseWorkSpace -= eh);
            }
    
    
            /// <summary>
    
            /// If we get a request to close a new Workspace, remove the WorkSpace from the 
            /// collection
            /// </summary>
            private void OnCloseWorkSpace(object sender, EventArgs e)
            {
                WorkspaceViewModel workspace = sender as WorkspaceViewModel;
                workspace.Dispose();
                this.Workspaces.Remove(workspace);
            }
    
    
            /// <summary>
            /// Sets a ViewModel to be active, which for the View equates
            /// to selected Tab
            /// </summary>
            /// <param name="workspace">workspace to activate</param>
    
            private void SetActiveWorkspace(WorkspaceViewModel workspace)
            {
                ICollectionView collectionView = 
                    CollectionViewSource.GetDefaultView(this.Workspaces);
    
                if (collectionView != null)
                    collectionView.MoveCurrentTo(workspace);
            }
    
    
            /// <summary>
            /// The active workspace ViewModels
            /// </summary>
            public ObservableCollection<WorkspaceViewModel> Workspaces
            {
                get { return workspaces; }
                set
                {
                    if (workspaces == null)
                    {
                        workspaces = value;
                        OnPropertyChanged("Workspaces");
                    }
                }
            }
    
    
    
        }
    }
    

    So now that we have a collection of WorkspaceViewModel ViewModels to bind to we simply use an appropriate View control to represent them, such as a TabControl. Here is an example

    <local:TabControlEx x:Name="tabControl" Grid.Row="2" Grid.Column="0" 
            IsSynchronizedWithCurrentItem="True" 
            ItemsSource="{Binding Path=Workspaces}" 
            RenderTransformOrigin="0.5,0.5"
            Template="{StaticResource MainTabControlTemplateEx}">
    </local:TabControlEx>

    Where the TabControlEx template looks like this

    <!-- MainTabControlTemplateEx -->
    <ControlTemplate x:Key="MainTabControlTemplateEx"
    				TargetType="{x:Type controls:TabControlEx}">
        <Grid>
            
            <Grid.RowDefinitions>
    
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="4"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            
            <TabPanel x:Name="tabpanel" 
                Background="{StaticResource OutlookButtonHighlight}"
                Margin="0"
    			Grid.Row="0"
    			IsItemsHost="True" />
            
            <Grid Grid.Row="1" Background="Black" 
                  HorizontalAlignment="Stretch"/>
    
                  
            <Grid x:Name="PART_ItemsHolder"
                  Grid.Row="2"/>
        </Grid>
        <!-- no content presenter -->
        <ControlTemplate.Triggers>
            <Trigger Property="TabStripPlacement" 
    		Value="Top">
                <Setter TargetName="tabpanel" 
    		Property="DockPanel.Dock" Value="Top"/>
    
                <Setter TargetName="PART_ItemsHolder" 
    		Property="DockPanel.Dock" Value="Bottom"/>
            </Trigger>
            <Trigger Property="TabStripPlacement" 
    		Value="Bottom">
                <Setter TargetName="tabpanel" 
    		Property="DockPanel.Dock" Value="Bottom"/>
                <Setter TargetName="PART_ItemsHolder" 
    		Property="DockPanel.Dock" Value="Top"/>
            </Trigger>
    
        </ControlTemplate.Triggers>
    
    </ControlTemplate>
    

    The very last piece in the puzzle is making sure the correct View is shown for the correct bound TabItem.Content (ViewModels that inherit from WorkspaceViewModel that are in the bound list). Basically this just a case of making sure that the correct View DataTemplates are available within a Resources section somewhere.

    
    <DataTemplate DataType="{x:Type VM:StartPageViewModel}">
    
        <AdornerDecorator>
            <local:StartPageView />
        </AdornerDecorator>
    </DataTemplate>
    
    
    <DataTemplate DataType="{x:Type VM:AddEditCustomerViewModel}">
        <AdornerDecorator>
    
            <local:AddEditCustomerView />
        </AdornerDecorator>
    </DataTemplate>
    
    <DataTemplate DataType="{x:Type VM:SearchCustomersViewModel}">
        <AdornerDecorator>
            <local:SearchCustomersView />
    
        </AdornerDecorator>
    </DataTemplate>
    

    Setting Focus

    Cinch contains a little helper class that can be used to set focus to a particular UI element, which in WPF is a lot harder than you think it should be. You need to ensure that there is a message pumped via the Dispatcher to really ensure setting Focus on a control works. Anyway here is the relevant Cinch code to do this.

    /// <summary>
    /// This class forces focus to set on the specified UIElement 
    /// </summary>
    public class FocusHelper
    {
    
        /// <summary>
        /// Set focus to UIElement
        /// </summary>
        /// <param name="element">The element to set focus on</param>
    
        public static void Focus(UIElement element)
        {
            //Focus in a callback to run on another thread, ensuring the main 
            //UI thread is initialized by the time focus is set
            ThreadPool.QueueUserWorkItem(delegate(Object theElement)
            {
                UIElement elem = (UIElement)theElement;
                elem.Dispatcher.Invoke(DispatcherPriority.Normal,
                    (Action)delegate()
                    {
                        elem.Focus();
                        Keyboard.Focus(elem);
                    });
            }, element);
        }
    }

    What's Coming Up?

    In the subsequent articles I will be showcasing it roughly like this

    1. How to develop ViewModels using Cinch
    2. How to Unit test ViewModels using Cinch app, including how to test Background work threads which may run within Cinch ViewModels
    3. A Demo app using Cinch

    That's It Hope You Liked It

    That is actually all I wanted to say right now, but I hope from this article you can see where Cinch is going and how it could help you with MVVM. As we continue our journey we will be covering the remaining items within Cinch and then we will move on to show you how to develop an app with Cinch.

    Thanks.

    As always votes / comments are welcome.

    License

    This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

    About the Author

    Sacha Barber


    Member
    I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

    - MSc (Passed with distinctions), in Information Technology for E-Commerce
    - BSc Hons (1st class) in Computer Science & Artificial Intelligence

    Both of these at Sussex University UK.

    Award(s)

    I am lucky enough to have won a few awards for Zany Crazy code articles over the years

    • Microsoft C# MVP 2009
    • Codeproject MVP 2009
    • Microsoft C# MVP 2008
    • Codeproject MVP 2008
    • And numerous codeproject awards which you can see over at my blog

    Occupation: Software Developer (Senior)
    Location: United Kingdom United Kingdom

    Other popular Windows Presentation Foundation articles:

    Article Top
    You must Sign In to use this message board.
    FAQ FAQ 
     
    Noise Tolerance  Layout  Per page   
     Msgs 1 to 25 of 55 (Total in Forum: 55) (Refresh)FirstPrevNext
    GeneralMenus PinmemberMike Marynowski5:20 2 Nov '09  
    GeneralRe: Menus PinmvpSacha Barber6:28 2 Nov '09  
    QuestionWhy WPFMenuItem and not simply MenuItem PinmemberGautier Boder22:05 14 Oct '09  
    AnswerRe: Why WPFMenuItem and not simply MenuItem PinmvpSacha Barber3:06 15 Oct '09  
    GeneralRe: Why WPFMenuItem and not simply MenuItem PinmemberGautier Boder3:39 15 Oct '09  
    QuestionFocusHelper [modified] Pinmemberjinkeep19:51 7 Oct '09  
    GeneralBug - cancel dialog always results in true [modified] PinmemberAroglDarthu15:17 29 Sep '09  
    GeneralRe: Bug - cancel dialog always results in true PinmvpSacha Barber19:23 30 Sep '09  
    GeneralRe: Bug - cancel dialog always results in true [modified] PinmvpSacha Barber21:37 16 Oct '09  
    GeneralTabControlEx problem PinmemberLovecraft2512:28 27 Sep '09  
    GeneralRe: TabControlEx problem PinmvpSacha Barber7:54 15 Oct '09  
    GeneralVisualizerService PinmemberAroglDarthu8:08 20 Sep '09  
    GeneralRe: VisualizerService PinmvpSacha Barber8:34 20 Sep '09  
    GeneralRe: VisualizerService PinmvpSacha Barber9:18 20 Sep '09  
    GeneralRe: VisualizerService PinmemberAroglDarthu12:43 28 Sep '09  
    GeneralAbsolutely great Pinmemberxr1fab23:21 25 Aug '09  
    GeneralRe: Absolutely great PinmvpSacha Barber3:14 26 Aug '09  
    GeneralAnother great train stop Pinmemberdisore0:00 18 Aug '09  
    GeneralRe: Another great train stop PinmvpSacha Barber1:14 18 Aug '09  
    GeneralOther implementation of ILoggerService PinmemberSteffen Buhr3:51 15 Aug '09  
    GeneralRe: Other implementation of ILoggerService PinmvpSacha Barber4:52 15 Aug '09  
    GeneralExcelent PinmemberRaul Mainardi Neto3:45 13 Aug '09  
    GeneralRe: Excelent PinmvpSacha Barber3:53 13 Aug '09  
    GeneralNice one PinmemberDaniel Vaughan11:25 30 Jul '09  
    GeneralRe: Nice one PinmvpSacha Barber22:34 30 Jul '09  

    General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

    PermaLink | Privacy | Terms of Use
    Last Updated: 17 Oct 2009
    Editor:
    Copyright 2009 by Sacha Barber
    Everything else Copyright © CodeProject, 1999-2009
    Web22 | Advertise on the Code Project