Click here to Skip to main content
15,860,844 members
Articles / Programming Languages / C# 4.0

CinchV2: Version2 of my Cinch MVVM framework: Part 2 of n

Rate me:
Please Sign up or sign in to vote.
4.94/5 (41 votes)
1 Jan 2011CPOL30 min read 131.9K   30   76
If Jack Daniels made MVVM frameworks.

Table of Contents

Introduction

Last time we talked about how Cinch V2 makes use of MefedMVVM for its ViewModel/Service resolution, and we had a brief look at there being a distinction between a Core service and Design time/Runtime services which I am calling UI services. In this article, we will examine the Core services that Cinch V2 supports and discuss Design time/Runtime services (UI services) in a bit more detail.

As promised, within each article, I shall be showing the Cinch V2 compatibility matrix.

The compatibility matrix shows a list of classes along with their general work area, and whether they are compatible with WPF or SL or both.

Work AreaClass NameWPFSilverlight (4 or above)Both
Business objectsEditableValidatingObject.cs  Yes
Business objectsValidatingObject.cs  Yes
Business objectsDataWrapper.cs  Yes
CommandsEventToCommandArgs.cs  Yes
CommandsSimpleCommand.cs  Yes
CommandsWeakEventHandlerManager.cs  Yes
EventsCloseRequestEventArgs.cs  Yes
EventsUICompletedEventArgs.cs  Yes
WeakEventsWeakEvent.cs  Yes
WeakEventsWeakEventHelper.cs  Yes
WeakEventsWeakEventProxy.cs  Yes
Extension MethodsDispatcherExtensions.csYes  
Extension MethodsGenericListExtensions.cs Yes 
Interactivity ActionsCommandDrivenGoToStateAction.cs  Yes
Interactivity BehavioursFocusBehaviourBase.csYes  
Interactivity BehavioursNumericTextBoxBehaviour.csYes  
Interactivity BehavioursSelectorDoubleClickCommandBehavior.csYes  
Interactivity BehavioursTextBoxFocusBehavior.csYes  
Interactivity TriggersCompletedAwareCommandTrigger.cs  Yes
Interactivity TriggersCompletedAwareGotoStateCommandTrigger.cs  Yes
Interactivity TriggersEventToCommandTrigger.cs  Yes
Messager MediatorMediatorMessageSinkAttribute.cs  Yes
Messager MediatorMediatorSingleton.cs  Yes
Services ImplementationChildWindowService.cs Yes 
Services ImplementationSLMessageBoxService.cs Yes 
Services ImplementationViewAwareStatus.cs  Yes
Services ImplementationViewAwareStatusWindow.csYes  
Services ImplementationVSMService.cs  Yes
Services ImplementationWPFMessageBoxService.csYes  
Services ImplementationWPFOpenFileService.csYes  
Services ImplementationWPFSaveFileService.csYes  
Services ImplementationWPFUIVisualizerService.csYes   
Services InterfacesIChildWindowService.cs Yes 
Services InterfacesIMessageBoxService.cs Yes 
Services InterfacesIViewAwareStatus.cs  Yes
Services InterfacesIViewAwareStatusWindow.csYes  
Services InterfacesIVSM.cs  Yes
Services InterfacesIMessageBoxService.csYes  
Services InterfacesIOpenFileService.csYes  
Services InterfacesISaveFileService.csYes  
Services InterfacesIUIVisualizerService.csYes  
Services Test ImplementationsTestChildWindowService.cs Yes 
Services Test ImplementationsTestMessageBoxService.cs Yes 
Services Test ImplementationsTestViewAwareStatus.cs  Yes
Services Test ImplementationsTestViewAwareStatusWindow.csYes  
Services Test ImplementationsTestVSMService.cs  Yes
Services Test ImplementationsTestMessageBoxService.csYes  
Services Test ImplementationsTestOpenFileService.csYes  
Services Test ImplementationsTestSaveFileService.csYes  
Services Test ImplementationsTestUIVisualizerService.csYes  
ThreadingAddRangeObservableCollection.cs (this is a specific SL implementation) Yes 
ThreadingAddRangeObservableCollection.cs (this is a specific WPF implementation)Yes  
ThreadingBackgroundTaskManager.cs  Yes
ThreadingISynchronizationContext.cs  Yes
ThreadingUISynchronizationContext.cs  Yes
ThreadingApplicationHelper.csYes  
ThreadingDispatcherNotifiedObservableCollection.csYes  
MenusCinchMenuItem.cs  Yes
UtilitiesArgumentValidator.cs  Yes
UtilitiesIWeakEventListener.cs (this is a System class missing from SL, so I created it) Yes 
UtilitiesObservableHelper.cs  Yes
UtilitiesPropertyChangedEventManager.cs (this is a System class missing from SL, so I created it) Yes 
UtilitiesPropertyObserver.cs  Yes
UtilitiesBindingEvaluator.csYes  
UtilitiesObservableDictionary.csYes  
UtilitiesTreeHelper.csYes  
ValidationRegexRule.cs  Yes
ValidationRule.cs  Yes
ValidationSimpleRule.cs  Yes
ViewModelsEditableValidatingViewModelBase.cs  Yes
ViewModelsIViewStatusAwareInjectionAware.cs  Yes
ViewModelsValidatingViewModelBase.cs  Yes
ViewModelsViewMode.cs  Yes
ViewModelsViewModelBase.cs  Yes
ViewModelsViewModelBaseSLSpecific.cs Yes 
ViewModelsViewModelBaseWPFSpecific.csYes  
WorkspacesChildWindowResolver.cs Yes 
WorkspacesCinchBootStrapper.cs (SL Version) Yes 
WorkspacesCinchBootStrapper.cs (WPF version)Yes  
WorkspacesPopupNameToViewLookupKeyMetadataAttribute.cs  Yes
WorkspacesIWorkspaceAware.csYes  
WorkspacesMockView.csYes  
WorkspacesNavProps.csYes  
WorkspacesPopupResolver.csYes  
WorkspacesViewnameToViewLookupKeyMetadataAttribute.csYes  
WorkspacesViewResolver.csYes  
WorkspacesWorkspaceData.csYes  

Now that I have shown you what classes will work with WPF/SL, let's get on with the rest of this article, shall we? But first, here are the links to the old Cinch V1 articles.

In case you missed Cinch V1, and have an interest in MVVM, I would strongly recommend that you read all the Cinch V1 articles first, as it will give you a much deeper understanding of the content that will be presented in these Cinch V2 articles.

CinchV1 Article Links

Some of you may never have seen the old Cinch V1 articles, so I will also include a list of these here, as where the Cinch V2 still uses the same functionality as Cinch V1, I will be redirecting people to these articles:

CinchV2 Article Links

That is what the article roadmap looks like. I guess it is now time to dive into the guts of this article, so let's go:

Services/UI Services

In order to understand this article, you will need to have read the first article, so if you have not already read that, you should read that using this URL: CinchV2: Introduction and MEFedMVVM and ViewModel/Service Resolution.

Core Services

Core services within Cinch are services that are shared, and can be used application wide, across multiple ViewModels, and typically are singleton instances.

Cinch V1 always had support for many services such as MessageBox/SaveFile/OpenFile/UIVisualiser, and these have largely remained unchanged in Cinch V2, what is different is that there is no IOC container to deal with any more, as these services are MEFed into the consuming ViewModel constructor where required.

The following sections will show you how to get your own ViewModel to consume these MEF enabled Cinch V2 services.

Importing Services Into ViewModel

Importing the MEF enabled Cinch V2 services into a ViewModel is extremely easy, and can be accomplished using standard MEF attributes. Here is an example of how you might import a variety of MEF enabled Cinch V2 services. It is important to note that since these MEF enabled Cinch V2 services are marked up with [PartCreationPolicy(CreationPolicy.Shared)], they are effectively singleton instances that are shared between all consuming ViewModels.

Here is what a typical consuming ViewModel willl look like. Note: this is a WPF ViewModel, but the same principles apply when developing Cinch V2 Silverlight ViewModels.

C#
[ExportViewModel("ImageLoaderViewModel")]
public class ImageLoaderViewModel : ViewModelBase
{
    private IViewAwareStatus viewAwareStatusService;
    private IMessageBoxService messageBoxService;
    private IOpenFileService openFileService;
    private ISaveFileService saveFileService;
    private IUIVisualizerService uiVisualizerService;

    [ImportingConstructor]
    public ImageLoaderViewModel(
        IMessageBoxService messageBoxService,
        IOpenFileService openFileService,
        ISaveFileService saveFileService,
        IUIVisualizerService uiVisualizerService,
        IViewAwareStatus viewAwareStatusService)
    {
        //setup services
        this.messageBoxService = messageBoxService;<
        this.openFileService = openFileService;
        this.saveFileService = saveFileService;
        this.uiVisualizerService = uiVisualizerService;
        this.viewAwareStatusService = viewAwareStatusService;
    }
}

The beauty of this approach is that:

  1. There is no ServiceResolver<T>, so we do not break the single responsibility pattern.
  2. We are allowing the ViewModel to easily receive mocks or test double services.
  3. If we want to add more services, we can just create a new service contract, implement it, mark it up using the MefedMVVM attributes I showed in the last article, and consume it, as shown above. It is very extensible.

A Matrix of Services

Just before we get into the guts of the services, some of you may find this table helpful:

ContractService ImplementationTest Double ImplementationIs View AwareWPFSL4 (or above)(Both)
IMessageBox Service.cs (Cinch.WPF DLL)WPFMessage BoxService.cs
(Cinch.WPF DLL)
TestMessageBox Service.cs
(Cinch.WPF DLL)
 Yes
IOpenFileService.csWPFOpenFileService.csTestOpenFileService.csYes
ISaveFileService.csWPFSaveFileService.csTestSaveFileService.csYes
IUIVisualizerService.csWPFUIVisualizer Service.csTestUIVisualizerService.csYes
IViewAwareStatus.csViewAwareStatus.csTestViewAwareStatus.csYesYes
IViewAware StatusWindow.csViewAware StatusWindow.csTestViewAware StatusWindow.csYesYes
IVSM.csVSMService.csTestVSMService.csYesYes
IMessageBox Service.cs (Cinch.SL DLL)SLMessageBox Service.cs
(Cinch.SL DLL)
TestMessageBox Service.cs
(Cinch.SL DLL)
  Yes
IChildWindowService.csChildWindowService.csTestChildWindowService.csYes

It can be seen that as well as the service contract and implementation, CinchV2 also provides a test double implementation, which you can use in your unit tests.

Common Services

Within CinchV2, there are two common services that can be used in WPF or SL. These two services are discussed below, but just before we get into them, I just want to go back slightly and mention something I talked about in article 1.

IContextAware

Within MefedMVVM and therfore CinchV2, there is a interface called IContextAware that can be implemented by service contracts. When you implement this interface on a service, the MefedMVVM process of resolving a service that implements IContextAware will also inject some context into the service that implements IContextAware. The context will be the current view. As such, any service that implements IContextAware will obviously need to be marked up like this:

C#
[PartCreationPolicy(CreationPolicy.NonShared)]

As each IContextAware implementing service should know about exactly one view, these types of services can not be shared.

As I stated above, CinchV2 provides two common (WPF and SL) services that implement IContextAware, which we shall now go into below.

IViewAwareStatus

There is a service contract which looks like this:

C#
public interface IViewAwareStatus : IContextAware
{
    event Action ViewLoaded;
    event Action ViewUnloaded;
 
#if !SILVERLIGHT
 
    event Action ViewActivated;
    event Action ViewDeactivated;
 
#endif
 
    Dispatcher ViewsDispatcher { get; }
    Object View { get; }
}

You can see that this service is indeed common for WPF and SL. In WPF, you basically get more events.

So how can we consume and use one of these types of services within a ViewModel? That is trivial. The following example shows you how:

C#
[ExportViewModel("ImageLoaderViewModel")]
public class ImageLoaderViewModel : ViewModelBase
{
    private IViewAwareStatus viewAwareStatusService;


    [ImportingConstructor]
    public ImageLoaderViewModel(
        IViewAwareStatus viewAwareStatusService)
    {
        //setup services
        this.viewAwareStatusService = viewAwareStatusService;
        this.viewAwareStatusService.ViewLoaded += ViewAwareStatusService_ViewLoaded;
    }


    private void ViewAwareStatusService_ViewLoaded()
    {
        //the view is loaded...do something
    }
}

You could do something similar using the other exposed events from the IViewAwareStatus service. But what about some of the other properties available on the service? Well, let's have a look at some of them.

  • ViewsDispatcher: Simply returns the Dispatcher object associated with the View, which could allow you to dispatch threaded code to the UI thread. In the test double, the current Dispatcher is used.
  • View: Simply returns the View. This could be useful if your View implements a certain interface that you wish to use in your ViewModel, just get the View and cast it to your specific interface and job done. You know sometimes you need to talk directly to the View. In the test double, you can supply a value for this object (some IView interface perhaps).

As with all the services within CinchV2, I have provided a test double for your usage within unit tests. Here is the full code listing for the TestViewAwareStatus class.

C#
public class TestViewAwareStatus : IViewAwareStatus
{
    #region Data
    //This should more than likely be some IView type of object
    private object simulatedViewObject;
 
    #endregion
 
    #region Ctor
    public TestViewAwareStatus()
    {
#if SILVERLIGHT
        ViewsDispatcher = System.Windows.Deployment.Current.Dispatcher;
#else
        ViewsDispatcher = Dispatcher.CurrentDispatcher;
#endif
    }
    #endregion
 
    #region IViewAwareStatus Members
 
    public event Action ViewLoaded;
    public event Action ViewUnloaded;
 
#if !SILVERLIGHT
    public event Action ViewActivated;
    public event Action ViewDeactivated;
#endif
 
    public Dispatcher ViewsDispatcher { get; private set; }
 
    public Object View
    {
        get
        {
            return simulatedViewObject;
        }
        set
        {
            simulatedViewObject = value;
        }
    }
 
    #endregion
 
    #region IViewAware Members
 
    public void InjectContext(object view)
    {
        //nothing to do here, we should not be creating a FrameworkElement
        //in a unit test anyway, so we should expect "view" to be null
        //from a unit test case
    }
    #endregion
 
    #region Helpers
 
    /// <summary>
    /// can be called from unit test to simulate view Loaded
    /// </summary>
    public void SimulateViewIsLoadedEvent()
    {
        if (ViewLoaded != null)
            ViewLoaded();
    }
 
    /// <summary>
    /// can be called from unit test to simulate view Unloaded
    /// </summary>
    public void SimulateViewIsUnloadedEvent()
    {
        if (ViewUnloaded != null)
            ViewUnloaded();
    }
 
#if !SILVERLIGHT
 
    /// <summary>
    /// Can be called from unit test to simulate view Activated
    /// </summary>
    public void SimulateViewIsActivatedEvent()
    {
        if (ViewActivated != null)
            ViewActivated();
    }
 
    /// <summary>
    /// Can be called from unit test to simulate view Deactivated
    /// </summary>
    public void SimulateViewIsDeactivatedEvent()
    {
        if (ViewDeactivated != null)
            ViewDeactivated();
    }
 
#endif
 
    #endregion
}

I hope you can see from this that it is possible to simulate all the events and data from within your unit tests. For events, you can use SimulateXXXEvent, and for the View (say if your ViewModel needs to talk to the View via a certain interface), your test can provide a mock view (IView or something), which would keep your ViewModel thinking there actually was a View there.

I have now also included another service which is only for WPF, which works much the same way as the IViewAwareStatus service shown above, except that it caters for a target view of type Window and exposes some more Window type events. Here is the service interface:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Threading;
using System.Windows;
using MEFedMVVM.Services.Contracts;
using System.ComponentModel;

namespace Cinch
{
    public interface IViewAwareStatusWindow : IContextAware
    {
        event Action ViewLoaded;
        event Action ViewUnloaded;
        event Action ViewActivated;
        event Action ViewDeactivated;

        event Action ViewWindowClosed;
        event Action ViewWindowContentRendered;
        event Action ViewWindowLocationChanged;
        event Action ViewWindowStateChanged;
        event EventHandler<CancelEventArgs> ViewWindowClosing;


        Dispatcher ViewsDispatcher { get; }
        Object View { get; }
    }
}

Which as you can see is the same as the IViewAwareStatus but offers a few more events. One interesting thing is that you can use the ViewWindowClosing event's CancelEventArgs to possibly cancel the Window from closing should you not wish it to be closed for any reason.

Important note: As this service is expecting to work with a Window type View, this service can only be used on a Window's ViewModel. Cinch itself does not enforce this but does expect a level of understanding from you lot, hope this is OK.

IVSM

The other IContextAware service is a VisualStateManager service, which allows you to tell the associated View to go to a particular state. Now I can not claim I wrote this code, as I did not, I simply copied it from MefedMVVM and also provided a test version of it, as I wanted to be able to debug it. Cheers Marlon.

Here is the service contract:

C#
public interface IVSM : IContextAware
{
    string LastStateExecuted { get; }
    void GoToState(string stateName);
}

As you can see, it is pretty simple. So how do we go about consuming and using one of these services then? As before, it is very straightforward. Here is an example:

C#
using System;
using System.Collections.Generic;
using System.Windows.Input;
using System.ComponentModel.Composition;
using System.ComponentModel;
using System.Linq;
 
using Cinch;
using MEFedMVVM.ViewModelLocator;
 
namespace CinchV2DemoSL
{
    [ExportViewModel("GameViewModel")]
    public class GameViewModel : ViewModelBase
    {
        private IVSM visualStateManagerService;
        [ImportingConstructor]
        public GameViewModel(            
            IVSM visualStateManagerService)
        {
 
        }
  
        private void CheckForCompleted()
        {
            if (IsCompleted)
            {
                StoreGameState();
                visualStateManagerService.GoToState("WinOrCompletedState");
            }
        }
    }
}

See how easy that is? As always, there is a test double of this service that you can use in your unit tests, it is called TestVVSMService. As you can imagine, without an actual View to show VisualStates in, there is not really too much one can do, so the TestVVSMService simply does nothing whenever its InjectContext(object view)GoToState(string stateName) is called. Although that sounds weird, it is a totally valid thing to do; after all, it keeps your ViewModel happy and allows the testing of it, so it's all cool baby.

Important note: One thing I should mention is that the VisualStateManagerService (IVSM implementation) expects to find VisualStateGroups as the first child of the injected context, as it makes use of the standard VisualStateManager GotoStateAction, which will only work with the first child as it uses the standard VisualStateManager.

Now if you have used Expression Blend in anger, you will know that most of the time this is going to be just fine, but there are occasions where it is quite possible for your VisualStateGroups not to be the first child in the main context container. For example, in a TabControl with TabItems, one could easily imagine that each TabItem has its own VisualStateGroups and VisualStates. If that is the case, using the VisualStateManagerService (IVSM implementation) will not work as it relies on using the standard VisualStateManager, when what you really need to use is the ExtendedVisualStateManager, as it can work with any FrameworkElement.

In subsequent articles, I will show you how Cinch V2 copes with this, but as I say, in 90% of the cases, you should be fine. 

WPF Services

The WPF services within Cinch V2 are pretty much the same as in Cinch V1, with the exception that they are now MEF attributed up and the way they are supplied to the ViewModels uses MEF instead of a ServiceResolver<T> and an IOC container, so if you feel you are up to speed with how these services work from reading Cinch V1 articles, or working with Cinch V1, you may want to gloss over this section.

WPFMessageBoxService

This is a shared service that is designed to work across all ViewModels. The service contract has not changed much since Cinch V1, the only thing that is different is that this service is MEFed into the ViewModel. This is what the Service Contract looks like:

C#
using System;
 
namespace Cinch
{
    /// <summary>
    /// Available Button options. 
    /// Abstracted to allow some level of UI Agnosticness
    /// </summary>
    public enum CustomDialogButtons
    {
        OK,
        OKCancel,
        YesNo,
        YesNoCancel
    }
 
    /// <summary>
    /// Available Icon options.
    /// Abstracted to allow some level of UI Agnosticness
    /// </summary>
    public enum CustomDialogIcons
    {
        None,
        Information,
        Question,
        Exclamation,
        Stop,
        Warning
    }

    /// <summary>
    /// Available DialogResults options.
    /// Abstracted to allow some level of UI Agnosticness
    /// </summary>
    public enum CustomDialogResults
    {
        None,
        OK,
        Cancel,
        Yes,
        No
    }
 
    /// <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);
    }
}

And this is what the actual service class outline looks like:

C#
[PartCreationPolicy(CreationPolicy.Shared)]
[ExportService(ServiceType.Both, typeof(IMessageBoxService))]
public class WPFMessageBoxService : IMessageBoxService
{
 
}

And this is how you could import this into one of your own ViewModels:

C#
namespace CinchV2DemoWPF
{
    [ExportViewModel("ImageLoaderViewModel")]
    public class ImageLoaderViewModel : ViewModelBase
    {
        private IMessageBoxService messageBoxService;
        [ImportingConstructor]
        public ImageLoaderViewModel(
            IMessageBoxService messageBoxService)
        {
            //setup services
            this.messageBoxService = messageBoxService;
        }
 
        private void ExecuteSaveToFileCommand(Object args)
        {
 
            ......
            ......
           messageBoxService.ShowError(
               string.Format("An error occurred saving images to file\r\n{0}",ex.Message));
            ......
            ......
        }
    }
}

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.), sometimes that is not enough. Imagine that you have a section of code in a ViewModel something like the following:

C#
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 would 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 is not enough. We need a better idea.

So what Cinch does is provides a Unit Test Cinch.IMessageBoxService service implementation which allows the Unit Test to enqueue the 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.

Image 1

What happens is that the unit test enqueues 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:

C#
/// <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 simply dequeues the next Func<CustomDialogResults> (which are after all just delegates) and calls the Func<CustomDialogResults> (which is queued up in the actual 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:

C#
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 guarantee we drive the ViewModel code through any test path we want to. It is a very powerful technique.

As always, Cinch V2 provides a test double that you can use in your unit tests which allows you to create code as just shown in the test code above. The test service is called TestMessageBoxService. This is shown in action in the code above.

If you want to know more about testing using this service, you can examine the Cinch V1 article: CinchV.aspx, and in particular, this section: CinchV.aspx#Messager.

WPFOpenFileService

This is a shared service that is designed to work across all ViewModels. The service contract has not changed must since Cinch V1, the only thing that is different is that this service is MEFed into the ViewModel. This is what the service contract looks like:

C#
/// <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);
}

And this is what the actual service class outline looks like:

C#
/// <summary> 
/// This class implements the IMessageBoxService for WPF purposes.
/// </summary> 
[PartCreationPolicy(CreationPolicy.Shared)]
[ExportService(ServiceType.Both, typeof(IOpenFileService))]
public class WPFOpenFileService : IOpenFileService
{
}

And this is how you could import this into one of your own ViewModels:

C#
namespace CinchV2DemoWPF
{
    [ExportViewModel("ImageLoaderViewModel")]
    public class ImageLoaderViewModel : ViewModelBase
    {
        private IOpenFileService openFileService;
        [ImportingConstructor]
        public ImageLoaderViewModel(
            IOpenFileService openFileService)
        {
            //setup services
            this.openFileService = openFileService;
 
        }
 
        /// <summary>
        /// Create a new List<ImageViewModel> by reading a XML file using XLINQ
        /// </summary>
        private void ExecuteOpenExistingFileCommand(Object args)
        {
            openFileService.InitialDirectory = @"C:\";
            openFileService.Filter = ".xml | XML Files";
 
            var result = openFileService.ShowDialog(null);
            if (result.HasValue && result.Value == true)
            {
                string fileName = openFileService.FileName
            }
        }
    }
}

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 enqueing the required Func<bool?> values as needed by the ViewModel code currently under test.

We could deal with this in a test, and supply a valid file name to the ViewModel (just like the user picked an actual file), as follows:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
 
using Cinch;
 
namespace MVVM.Test
{
    [TestFixture]
    public class Tests
    {
        [Test]
        public void OpenSomeFile_Tests()
        {
           //Setup some hyperthetical ViewModel
           TestOpenFileService testOpenFileService = new TestOpenFileService()
           SomeViewModel x = new SomeViewModel(testOpenFileService);
 
 
           //Queue up the responses we expect for our given TestOpenFileService 
           //for a given ICommand/Method call within the test ViewModel
           testOpenFileService.ShowDialogResponders.Enqueue
                (() =>
                    {
                      testOpenFileService.FileName = @"c:\test.txt";
                      return true
                    }
                );
    
           //Do some testing based on the File requested
           .....
           .....
           .....
           .....
        }
    }
}

As always, Cinch V2 provides a test double that you can use in your unit tests which allows you to create code as just shown in the test code above. The test service is called TestOpenFileService, this is shown in action in the code above.

If you want to know more about testing using this service, you can examine the Cinch V1 article: CinchV.aspx, and in particular, this section: CinchV.aspx#OpenFile.

WPFSaveFileService

This is a shared service that is designed to work across all ViewModels. The service contract has not changed must since Cinch V1, the only thing that is different is that this service is MEFed into the ViewModel. This is what the service contract looks like:

C#
/// <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);
}

And this is what the actual service class outline looks like:

C#
/// <summary> 
/// This class implements the ISaveFileService for WPF purposes.
/// </summary> 
[PartCreationPolicy(CreationPolicy.Shared)]
[ExportService(ServiceType.Both, typeof(ISaveFileService))]
public class WPFSaveFileService : ISaveFileService
{
}

And this is how you could import this into one of your own ViewModels:

C#
namespace CinchV2DemoWPF
{
    [ExportViewModel("ImageLoaderViewModel")]
    public class ImageLoaderViewModel : ViewModelBase
    {
        private ISaveFileService saveFileService;
        [ImportingConstructor]
        public ImageLoaderViewModel(
               ISaveFileService saveFileService)
        {
            //setup services
            this.saveFileService = saveFileService;
 
        }
 
        private void ExecuteSaveToFileCommand(Object args)
        {
            saveFileService.InitialDirectory = @"C:\";
            saveFileService.OverwritePrompt = true;
            saveFileService.Filter = ".xml | XML Files";
    
            var result = saveFileService.ShowDialog(null);
            if (result.HasValue && result.Value == true)
            {
                string savedFileName = saveFileService.FileName;
            }
        }
    }
}

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 enqueing the required Func<bool?> values as needed by the ViewModel code currently under test.

We could deal with this in a test, and supply a valid file name to the ViewModel (just like the user picked an actual file), as follows:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
 
using Cinch;
 
namespace MVVM.Test
{
    [TestFixture]
    public class Tests
    {
        [Test]
        public void SaveSomeFile_Tests()
        {
            TestSaveFileService testSaveFileService = new TestSaveFileService();
 
            //Setup some hyperthetical ViewModel
            SomeViewModel x = new SomeViewModel(testSaveFileService);
 
            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;
           });
 
    
             //Do some testing based on the File saved here
            .....
            .....
            .....
            .....
        }
    }
}

As always, Cinch V2 provides a test double that you can use in your unit tests which allows you to create code as just shown in the test code above. The test service is called TestSaveFileService, this is shown in action in the code above.

If you want to know more about testing using this service, you can examine the Cinch V1 article: CinchV.aspx, and in particular, this section: CinchV.aspx#SaveFile.

WPFUIVisualizerService

I do not know about you a lot, but we are in the middle of a very large WPF project at work, and although I am not a fan of popup windows, we do have some nonetheless. 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 occasionally, we need to show a popup and have it edit some object within the current ViewModel, or allow the user to cancel the edit.

In Cinch I solve this by using a service that can handle showing popups. This is a shared service that is designed to work across all ViewModels. The service contract has not changed must since Cinch V1, the only thing that is different is that this service is MEFed into the ViewModel. This is what the service contract looks like: 

C#
/// <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);
}

And this is what the actual service class looks like in its entirety, which I have included as I feel it may help some people understand how the inner workings of these services work:

C#
using System;
using System.Collections.Generic;
using System.Windows;
using System.ComponentModel.Composition;
using MEFedMVVM.ViewModelLocator;

namespace Cinch
{
    /// <summary>
    /// This class implements the IUIVisualizerService for WPF purposes.
    /// If you have attributed up your views
    /// using the ViewnameToViewLookupKeyMetadataAttribute
    /// Registration of Views with the IUIVisualizerService service is automatic.
    /// However you can still register views manually,
    /// to do this simply put some lines like this in you App.Xaml.cs
    /// ViewModelRepository.Instance.Resolver.Container.
    ///    GetExport<IUIVisualizerService>().
    ///    Value.Register("MainWindow", typeof(MainWindow));
    /// </summary>
    [PartCreationPolicy(CreationPolicy.Shared)]
    [ExportService(ServiceType.Both, typeof(IUIVisualizerService))]
    public class WPFUIVisualizerService : IUIVisualizerService
    {
        #region Data
        private readonly Dictionary<string, Type> _registeredWindows;
        #endregion

        #region Ctor
        public WPFUIVisualizerService()
        {
            _registeredWindows = new Dictionary<string, Type>();
        }
        #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);

            if (dataContext is IViewStatusAwareInjectionAware)
            {
                IViewAwareStatus viewAwareStatus = 
                    ViewModelRepository.Instance.Resolver.Container.
                    GetExport<IViewAwareStatus>().Value;
                viewAwareStatus.InjectContext((FrameworkElement)win);
                ((IViewStatusAwareInjectionAware)dataContext).
                  InitialiseViewAwareService(viewAwareStatus);
            }

            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 += ((EventHandler<CloseRequestEventArgs>)((s, e) =>
                        {
                            try
                            {
                                win.DialogResult = e.Result;
                            }
                            catch (InvalidOperationException)
                            {
                                win.Close();
                            }
                        })).MakeWeak(eh => bvm.CloseRequest -= eh);


                    }
                    else
                    {
                        bvm.CloseRequest += 
                          ((EventHandler<CloseRequestEventArgs>)((s, e) => win.Close()))
                                .MakeWeak(eh => bvm.CloseRequest -= eh); 
                    }
                    bvm.ActivateRequest += 
                       ((EventHandler<EventArgs>)((s, e) => win.Activate()))
                        .MakeWeak(eh => bvm.ActivateRequest -= eh); 
                }
            }

            win.Closed += (s, e) =>
            {
                if (completedProc != null)
                {
                    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 (Cinch.ViewModelBase) which instructs 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:

C#
namespace CinchV2DemoWPF
{
    [ExportViewModel("ImageLoaderViewModel")]
    public class ImageLoaderViewModel : ViewModelBase
    {
        private IUIVisualizerService uiVisualizerService;
        [ImportingConstructor]
        public ImageLoaderViewModel(
            IUIVisualizerService uiVisualizerService)
        {
            //setup services
            this.uiVisualizerService = uiVisualizerService;
        }
 
        /// <summary>
        /// Show the AddImageRatingPopup using the IUIVisualizerService, passing
        /// it a ValidatingViewModel that should validate that a valid rating between
        /// 1-5 is entered by the user. If we get a valid rating then apply it to the
        /// currently selected ImageViewModel
        /// </summary>
        private void ExecuteAddImageRatingCommand(Object args)
        {
            ImageRatingViewModel imageRatingViewModel = 
                       new ImageRatingViewModel(messageBoxService);
            imageRatingViewModel.ImageRating.DataValue = 
                      ((ImageViewModel)loadedImagesCV.CurrentItem).Rating;
 
 
            bool? result = uiVisualizerService.ShowDialog(
                    "AddImageRatingPopup", imageRatingViewModel);
            if (result.HasValue && result.Value)
            {
                ((ImageViewModel)loadedImagesCV.CurrentItem).Rating = 
                    imageRatingViewModel.ImageRating.DataValue;
            }
        }
    }
}

It can be seen that this ViewModel code snippet is using one of the names of the registered 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.

The more observant of you may be thinking, OK, so how does the service know about these view strings. Well, there are several options here.

You can attribute up your popup windows using a Cinch V2 PopupNameToViewLookupKeyMetadata attribute as shown below, and use the Cinch V2 boostrapper in App.xaml.cs. This will trawl all Types in the the Assemblies you specify that have this PopupNameToViewLookupKeyMetadata attribute and add it to the service for you.

C#
[PopupNameToViewLookupKeyMetadata("AddImageRatingPopup",typeof(AddImageRatingPopup))]
public partial class AddImageRatingPopup : Window

So you would have this in your App.xaml.cs:

C#
CinchBootStrapper.Initialise(new List<Assembly> { typeof(App).Assembly });

The only issue with this approach is if you are using dynamically loaded assemblies or dynamic composition using MEF, you will not necessarily have all the Assemblies to pass to the Cinch V2 boostrapper. So what can we do in that case? Well, the answer is pretty simple, we just add them manually directly into the service. Recall, this is a shared service, so whenever we add something to it, every ViewModel that makes use of this service will see the effect of it. So let's have a look at how to add a View manually to the WPFUIVisualizerService.

C#
IUIVisualizerService uiVisualizerService = 
     ViewModelRepository.Instance.Resolver.Container.GetExport<IUIVisualizerService>().Value;
uiVisualizerService .Register("SomeString",typeof(SomeChildWindow));

See how we are adding stuff directly to the WPFUIVisualizerService there? One thing to note is you obviously need to do this somewhere where you know about the Type of the ChildWindow.

Closing Popups Programmatically

Note that we are also able to programmatically close an active popup using the following logic inside a ViewModel:

C#
CloseActivePopUpCommand.Execute(true);

This will set the DialogResult value we would like returned when the ViewModelBase.CloseActivePopupCommand is executed, which may be handy if you want to close the active popup programmatically, rather than let the user use the button that is linked to the CloseActivePopUpCommand. This code will return true when the popup closes.

The actual ViewModelBase code for the CloseActivePopupCommand.Execute looks like this:

C#
/// <summary>
/// Raises RaiseCloseRequest event, passing back correct DialogResult
/// </summary>
private void ExecuteCloseActivePopupCommand(Object param)
{
    if (param is Boolean)
    {
        // Close the dialog using DialogResult requested
        RaiseCloseRequest((bool)param);
        return;
    }
 
    //param is not a bool so try and parse it to a Bool
    Boolean popupAction = true;
    Boolean result = Boolean.TryParse(param.ToString(), out popupAction);
    if (result)
    {
        // Close the dialog using DialogResult requested
        RaiseCloseRequest(popupAction);
    }
    else
    {
        // Close the dialog passing back true as default
        RaiseCloseRequest(true);
    }
}

You may recall from the actual WPFUIVisualizerService implementation that the WPFUIVisualizerService is listening for the ViewModelBase.CloseRequest event being raised, and when the IUIVisualizerService sees this event being raised, it will close the active popup and use the ViewModelBase.CloseRequest EventArgs to determine what result the popup should exit with.

So just to go over that one more time, we have a WPFUIVisualizerService that can show popups. The WPFUIVisualizerService listens to the ViewModelBase.CloseRequest. We also have a ViewModelBase.CloseActivePopupCommand that we can run, passing it a bool parameter, this command will then execute, and ViewModelBase will raise the ViewModelBase.CloseRequest event passing it some EventArgs contained in the requested programmatic exit value for the popup. The WPFUIVisualizerService implementation will then use the requested value as the exit value for the popup and will proceed to close the popup.

Easy peasy.

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 two you need to know. These are as follows:

Tricks When Working With Popups and Cinch

Following these simple rules should help:

Make sure your Save and Cancel buttons have the IsDefault and IsCancel set like:

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

For more background reading on this service, I suggest you read the Cinch V1 article section: CinchIII.aspx#PopServ.

As always, Cinch V2 provides a test double that you can use in your unit tests which allows you to create code as just shown in the test code above. The test service is called TestUIVisualizerService; this is shown in action in the code above.

If you want to know more about testing using this service, you can examine the Cinch V1 article: CinchV.aspx, and in particular, this section: CinchV.aspx#UIVisualizer.

SL Services

For Silverlight, there is obviously less you can do as you are effectively running in a sandbox. As such, there are less services available within Cinch V2. That said, the ones that there are should cover most cases. So let us go on to look at the available Silverlight services available inside of Cinch V2.

IMessageBoxService

This is a shared service between all ViewModels that use this service.

Which works much the same as its fuller WPF buddy. But as the MessageBox API that is currently available within Silverlight is not as rich as the one in WPF, the things you can do with the service are obviously less. At present, in Silverlight, you can only use MessageBox.Show with a single string or a string/caption and an OK/Cancel set of buttons.

Here is the complete IMessageBoxService Silverlight contract, which as I say works much the same as the WPF MessageBoxService we discussed above.

C#
/// 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 OK/Cancel dialog and returns the user input.
    /// </summary>
    /// <param name="message">The message to be displayed.</param>
    /// <returns>User selection.</returns>
    CustomDialogResults ShowOkCancel(string message);
 
}

As before, I have provided a TestMessageBoxService which you may use in your tests. Again, this works much the same as its fuller WPF equivalent.

IChildWindowService

This is a shared service between all ViewModels that use this service.

In Silverlight 3 (or so I recall), there is a new object included called a ChildWindow. By using one of these, you are able to show a popup window, which acts like a Modal dialog that you may use when showing a standard Window modally in other frameworks such as WPF/WinForms.

It does behave slightly differently, but it is not that bad once you get the hang of it. At any rate, Cinch V2 provides a service for this too. In theory, it is not that different to the WPFUIVisualizerService found in Cinch V2 for WPF. Let's have a look at the service contract, shall we?

C#
/// <summary>
/// This interface defines a UI controller which can be used to ChildWindows
/// from a ViewModel
/// </summary>
public interface IChildWindowService
{
    /// <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 ChildWindow associated with the given key
    /// calling code is not blocked, and will not wait on the ChildWindow being
    /// closed. So this should only be used when there is no code dependant on
    /// the ChildWindows DialogResult. If you want to use the result of the ChildWindow
    /// being shown you can should create a callback delegate for the completedProc
    /// </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="completedProc">Callback used when UI closes (may be null)</param>
    void Show(string key, object state, EventHandler<UICompletedEventArgs> completedProc);
}

Inside the actual Silverlight implementation of this service is a Dictionary<string,Type> where the ViewModel is able to ask this service to create a particular ChildWindow using a string key, and the Silverlight implementation of this service will create and show an instance of that type, and pass us the state parameter value as the DataContext to the newly constructed ChildWindow that matches the requested string.

Here is an example of how to use this service from a Silverlight ViewModel. One thing to note is that although the ChildWindow looks Modal, it does not block calling code from continuing, i.e., it is non blocking, so do not do anything else if you rely on the result from a ChildWindow. Do all that work inside the callback from the ChildWindow, which is called when it is closed. This is shown in the example below.

C#
bool? dialogResult = null;
ChildWindowService.Show("PlayedGameChildWindow", 
      new PlayedGameViewModel(GameText), (s, e) =>
        {
            dialogResult = e.Result;
            string result = dialogResult.HasValue && 
                            dialogResult.Value ? "ok" : "Cancel";
            //you can do what you like with dialogResult
        });
//NOTE : You should not do anymore here, as the ChildWindow, although it appears
//modal, it does not block parent code.

The more observant of you may be thinking, OK, so how does the service know about these view strings. Well, there are several options here.

You can attribute up your custom ChildWindows using a Cinch V2 PopupNameToViewLookupKeyMetadata attribute as shown below, and use the Cinch V2 boostrapper in App.xaml.cs. This will trawl all Types in the the Assemblies you specify that have this PopupNameToViewLookupKeyMetadata attribute and add it to the service for you.

C#
[PopupNameToViewLookupKeyMetadata("PlayedGameChildWindow",typeof(PlayedGameChildWindow))]
public partial class PlayedGameChildWindow : ChildWindow

So you would have this in your App.xaml.cs:

C#
CinchBootStrapper.Initialise(new List<Assembly> { typeof(App).Assembly });

The only issue with this approach is, if you are using multiple XAPs which you are downloading using MEF DeploymentCatalogs, you will not necessarily have all the Assemblies to pass to the Cinch V2 boostrapper. So what can we do in that case? Well, the answer is pretty simple, we just add them manually directly into the service. Recall, this is a shared service, so whenever we add something to it, every ViewModel that makes use of this service will see the effect of it. So let's have a look at how to add a view manually to the ChildWindowService.

C#
IChildWindowService childWindowService = 
     ViewModelRepository.Instance.Resolver.Container.GetExport<IChildWindowService>().Value;
childWindowService.Register("SomeString",typeof(SomeChildWindow));

See how are we adding stuff directly to the ChildWindowService there? One thing to note is you obviously need to do this somewhere where you know about the Type of the ChildWindow.

There is also a test version of this service provided that you can use in your testing, that works much the same way as the IUIVisualizerService found in Cinch V2 for WPF. Here is a quick snippet of what some test code may look like:.

C#
testChildWindowService.ShowResultResponders.Enqueue
    (() =>
    {
        return new UICompletedEventArgs()
                    {
                        State = WHATEVER STATE YOU LIKE,
                        Result = true
                    } ;
    }
    );

The idea being the same as most of the test Cinch V2 services, that we queue up some Func delegates that we use in our test cases that would simulate what the user could do had they been using the actual UI. Anyway, as I say, this is similar to the IUIVisualizerService found in Cinch V2.

UI Services

UI services are really non-shared services that can be used to provide either design time/runtime data to a particular ViewModel. Cinch V2 is written with the use of services in mind, to provide the ViewModel with data. This is good for many reasons such as:

  • All services use an interface, so this facilitates them to be easily mocked, or even replaced (say you had to support two different WCF services, or two different databases, just swap in the appropriate service).
  • They allow there to be a design time service and a runtime service.
  • They allow for synchronous fetching in test code, whilst allowing asynchronous fetching within the actual runtime services.

I do not want to dive into this area too much within this article, but I will show you an example of how you might configure a synchronous service and an asynchronous service. We will be looking into this in much more detail when we discuss the demo apps.

Synchronous service example

Suppose we have a ViewModel that is expecting to make use of a UI service (this one is from the WPF demo app included with the Cinch V2 codebase) that needs to do some disk operations to save and load some XML data, where the service contract looks like this:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace CinchV2DemoWPF
{
    /// <summary>
    /// Data service used by the <c>ImageLoaderViewModel</c> to carry out Save/open
    /// operations
    /// </summary>
    public interface IImageDiskOperations
    {
        /// <summary>
        /// Saves viewModelsToSave to a XML file, this demonstrates the use of
        /// the <c>SaveFileService</c> from the <c>ImageLoaderViewModel</c>
        /// </summary>
        bool Save(string fileName, IEnumerable<ImageViewModel> viewModelsToSave);
 
        /// <summary>
        /// retusn a  List<ImageViewModel> from an XML file, this demonstrates the use of
        /// the <c>OpenFileService</c> from the <c>ImageLoaderViewModel</c>
        /// </summary>
        List<ImageViewModel> Open(string fileName);
    }
}

And we have some ViewModel code that looks like this:

C#
[ExportViewModel("ImageLoaderViewModel")]
public class ImageLoaderViewModel : ViewModelBase
{
    private IImageDiskOperations imageDiskOperations;
 
     [ImportingConstructor]
    public ImageLoaderViewModel(
        IImageDiskOperations imageDiskOperations)
    {
        //setup services
        this.imageDiskOperations = imageDiskOperations;
    }
 
 
    /// <summary>
    /// Create a new List<ImageViewModel> by reading a XML file using XLINQ
    /// </summary>
    private void ExecuteOpenExistingFileCommand(Object args)
    {
        openFileService.InitialDirectory = @"C:\";
        openFileService.Filter = ".xml | XML Files";

        var result = openFileService.ShowDialog(null);
        if (result.HasValue && result.Value == true)
        {
            try
            {
        //use the service to load some XML data of disk
                List<ImageViewModel> xmlReadViewModels = 
        imageDiskOperations.Open(openFileService.FileName);

        ....
        ....
            }
            catch (Exception ex)
            {
                messageBoxService.ShowError(
                    string.Format("An error occurred opening file\r\n{0}", ex.Message));
            }
        }
    }
}

We could construct a design time service for this, but there is little point, as this code is happening based on a user clicking a button which fires the ICommand, so for this particular service, we do not need to provide a design time service, but we obviously do need to create a runtime one, but we can mark it up as being shared between design time/runtime using MEFedMVVM goodness. Here is what the synchronous demo app service implementation looks like.

Note: I am only showing the IImageDiskOperations.Open(string fileName) method for brevity.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.Xml.Linq;
using System.Xml;
 
using MEFedMVVM.ViewModelLocator;
 
namespace CinchV2DemoWPF
{
    //Extension method helper class
    public static class CustomXElementExtensions
    {
        public static string SafeValue(this XElement input)
        {
            return (input == null) ? string.Empty : (string)input.Value;
        }
    }
 
    //The actual service, see how its shared between design time/runtime
    [PartCreationPolicy(CreationPolicy.Shared)]
    [ExportService(ServiceType.Both, typeof(IImageDiskOperations))]
    public class ImageDiskOperations : IImageDiskOperations
    {
 
        public bool Save(string fileName, 
        IEnumerable<ImageViewModel> viewModelsToSave)
        {
        //skipped for brevity  
        }
 
        public List<ImageViewModel> Open(string fileName)
        {
            var xmlImageViewModelResults =
                from imageVM in StreamElements(fileName, "ImageVM")
                select new ImageViewModel
                {
                    ImagePath = imageVM.Element("ImagePath").SafeValue(),
                    FileName = imageVM.Element("FileName").SafeValue(),
                    FileDate = DateTime.Parse(imageVM.Element("FileDate").SafeValue()),
                    FileExtension = imageVM.Element("FileExtension").SafeValue(),
                    FileSize = int.Parse(imageVM.Element("FileSize").SafeValue()),
                    Rating = int.Parse(imageVM.Element("Rating").SafeValue())
                };
 
            return xmlImageViewModelResults.ToList();
        }
 
        public static IEnumerable<XElement> StreamElements(string uri, string name)
        {
            using (XmlReader reader = XmlReader.Create(uri))
            {
                reader.MoveToContent();
                while (reader.Read())
                {
                    if ((reader.NodeType == XmlNodeType.Element) &&
                      (reader.Name == name))
                    {
                        XElement element = (XElement)XElement.ReadFrom(reader);
                        yield return element;
                    }
                }
                reader.Close();
            }
        }
    }
}

I hope you can see that because this service really just implements the service contract interface IImageDiskOperations, it could be easily mocked using one of the standard mocking libraries such as Moq/RhinoMocks. In fact, we could even create a test double (manual mock), which would be a class that could be used in a test that simply implements the service contract interface IImageDiskOperations.

So that shows you how you might use a synchronous service. I have not talked about design time vs. runtime yet, but I will get to that in the next part, and also in subsequent articles.

Asynchronous service example

Synchronous services are all well and good, but occasionally (more often than not), we need to make long running operations that could make our UI unresponsive, so we need to thread those calls. How can we create UI services that the ViewModel can make use of that could potentially take a long time? Well, it turns out it is not that different. Let's examine one of these.

Suppose we have a service contract interface (again, taken from the Cinch V2 WPF demo app) that needs to fetch data in the background, using whatever background fetch technique you prefer (BackgroundWorker, ThreadPool, Thread, Task, Cinch BackgroundTaskManager.... your call).

C#
/// <summary>
/// Data service used by the <c>ImageLoaderViewModel</c> to obtain data
/// </summary>
public interface IImageProvider
{
    void FetchImages(string imagePath, Action<List<ImageData>> callback);
}

And suppose we have a ViewModel that needs to make use of this service, something like this:

C#
[ExportViewModel("ImageLoaderViewModel")]
public class ImageLoaderViewModel : ViewModelBase
{
    private IImageProvider imageProvider;
 
     [ImportingConstructor]
    public ImageLoaderViewModel(
        IImageProvider imageProvider)
    {
        //setup services
        this.imageProvider= imageProvider;
    }

    //left out for brevity
    //left out for brevity
    //left out for brevity
    //left out for brevity
    

    private void LoadImages(string imagePath)
    {
        imageProvider.FetchImages(imagePath, LoadImagesFromRetrievedData);
    }


    private void LoadImagesFromRetrievedData(List<ImageData> data)
    {
        //left out for brevity
        //left out for brevity
        //left out for brevity
        //left out for brevity
    }
}

We can see that the service is called but we provide a callback Action<List<ImageData> delegate to it, so when it completes, it is able to call back the required ViewModel method using the callback delegate that is expecting the results.

Let's have a look at the runtime version of this service contract implementation, shall we? Here is one from the Cinch V2 WPF demo app. Note that I am using Cinch BackGroundTaskManager to do the background work, but you can use what you like. Oh also, I am showing the complete service implementation here, as I think it is best to see the entire sample for this one.

C#
/// <summary>
/// Runtime implementation of the 
/// Data service used by the <c>ImageLoaderViewModel</c> to obtain data
/// </summary>
[PartCreationPolicy(CreationPolicy.NonShared)]
[ExportService(ServiceType.Runtime, typeof(IImageProvider))]
public class RunTimeImageProvider : IImageProvider
{
    #region Data
    private BackgroundTaskManager<string, List<ImageData>> bgWorker = 
        new BackgroundTaskManager<string, List<ImageData>>();
    #endregion
 
    #region Public Methods/Properties
 
    public void FetchImages(string imagePath, Action<List<ImageData>> callback)
    {
        bgWorker.TaskFunc = (argument) =>
            {
                return FetchImagesInternal(argument);
            };
 
        bgWorker.CompletionAction = (result) =>
            {
                callback(result);
            };
 
        bgWorker.WorkerArgument = imagePath;
        bgWorker.RunBackgroundTask();
 
    }
 
    /// <summary>
    /// To allow this class to be unit tested stand alone
    /// See CinchV1 articles about Unit Testing for this
    /// Or comments in Cinch BackgroundTaskManager<T> class
    /// </summary>
    public BackgroundTaskManager<string,List<ImageData>> BgWorker
    {
        get { return bgWorker; }
    }
 
    #endregion
 
    #region Private Methods
    private List<ImageData> FetchImagesInternal(string imagePath)
    {
        List<string> imageFiles = new List<string>();
 
        string strFilter = "*.jpg;*.png;*.gif";
        string[] filters = strFilter.Split(';');
        foreach (string filter in filters)
        {
            imageFiles.AddRange(Directory.GetFiles(imagePath, filter));
        }
 
        List<ImageData> images = new List<ImageData>();
 
        if (imageFiles.Count > 0)
        {
            int maxImages = imageFiles.Count > 20 ? 20 : imageFiles.Count;
 
            for (int i = 0; i < maxImages; i++)
            {
                FileInfo fi = new FileInfo(imageFiles[i]);
                ImageData id = new ImageData();
                id.ImagePath = imageFiles[i];
                id.FileDate = fi.LastWriteTime;
                id.FileExtension = fi.Extension;
                id.FileName = fi.Name;
                id.FileSize = (int)fi.Length / 1024;
                images.Add(id);
            }
        }
 
        return images;
 
    }
    #endregion
}

That is how the runtime service works, what about the design time one? Well, here it is in its entirety:

C#
[PartCreationPolicy(CreationPolicy.NonShared)]
[ExportService(ServiceType.DesignTime, typeof(IImageProvider))]
public class DesigntimeImageProvider : IImageProvider
{
    public void FetchImages(string imagePath, Action<List<ImageData>> callback)
    {
        List<ImageData> fakeImages = new List<ImageData>();
        for (int i = 0; i < 10; i++)
        {
            ImageData id = new ImageData();
            id.ImagePath = @"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg";
            id.FileDate = DateTime.Now;
            id.FileExtension = "*.jpg";
            id.FileName = "Desert.jpg";
            id.FileSize = 223;
            fakeImages.Add(id);
        }
        callback(fakeImages);
    }
}

What about testing this? Well, you have several choices. Moq/RhinoMocks provides facilities to call callback delegates from mocks. Or you could just create a test double that would more than likely look quite similar to the design time service shown above. If you are doing a complete UI-database test (integration test), you will more than likely also want to test the threading, but I leave that as an exercise for the reader. What I will say is that Cinch V1 did provide support for testing its own BackGroundTaskManager, which you can read more about using this link: CinchV.aspx.

As I just discussed, Moq/RhinoMocks does provide a way of dealing with callback delegates; in fact, here is a small example of how to test a callback delegate using Moq:

C#
Mock<IImageProvider> mockImageProvider = new Mock<IImageProvider>();
Action<List<ImageData> action = null;
 
mockImageProvider.Setup(b => b.GetData(It.IsAny<Action<List<ImageData>>()))
.Callback<Action<List<ImageData>>(a => action = a);
 
ImageLoaderViewModel vm = new ImageLoaderViewModel(mockImageProvider.Object);
 
action.Invoke(GetFakeListImageData());

But like I say, you could use something similar to the design time service or a test double if you don't like mocking frameworks.

That's It ....For Now

Could I just ask if you have enjoyed this article, and feel it is going to help you out, could you please show your support by leaving a vote/comment?

As before, if you have any deep MEF related questions, you should direct this to Marlon Grech either by using his blog C# Disciples, or by using the MEFedMVVM CodePlex site. Any other Cinch V2 questions will be answered by the next CinchV2 articles.

License

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


Written By
Software Developer (Senior)
United Kingdom United Kingdom
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 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • 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

Comments and Discussions

 
GeneralPrevent popup from closing Pin
Germghost9-May-11 4:10
Germghost9-May-11 4:10 
GeneralRe: Prevent popup from closing Pin
Germghost9-May-11 4:16
Germghost9-May-11 4:16 
GeneralRe: Prevent popup from closing Pin
Sacha Barber9-May-11 5:38
Sacha Barber9-May-11 5:38 
GeneralRe: Prevent popup from closing Pin
Sacha Barber9-May-11 5:41
Sacha Barber9-May-11 5:41 
GeneralRe: Prevent popup from closing Pin
Germghost9-May-11 8:54
Germghost9-May-11 8:54 
GeneralRe: Prevent popup from closing Pin
Sacha Barber9-May-11 19:57
Sacha Barber9-May-11 19:57 
QuestionCan the button labels in message box be changed? Pin
ACanadian9-Mar-11 11:58
ACanadian9-Mar-11 11:58 
AnswerRe: Can the button labels in message box be changed? Pin
Sacha Barber9-Mar-11 19:21
Sacha Barber9-Mar-11 19:21 
GeneralRe: Can the button labels in message box be changed? Pin
ACanadian10-Mar-11 5:00
ACanadian10-Mar-11 5:00 
GeneralRe: Can the button labels in message box be changed? Pin
Sacha Barber10-Mar-11 5:17
Sacha Barber10-Mar-11 5:17 
QuestionIViewAwareStatusWindow in pop up window? Pin
3epo416-Jan-11 20:46
3epo416-Jan-11 20:46 
AnswerRe: IViewAwareStatusWindow in pop up window? [modified] Pin
Sacha Barber16-Jan-11 21:38
Sacha Barber16-Jan-11 21:38 
GeneralRe: IViewAwareStatusWindow in pop up window? [modified] Pin
3epo416-Jan-11 23:40
3epo416-Jan-11 23:40 
GeneralRe: IViewAwareStatusWindow in pop up window? Pin
Sacha Barber16-Jan-11 23:54
Sacha Barber16-Jan-11 23:54 
GeneralRe: IViewAwareStatusWindow in pop up window? Pin
Sacha Barber16-Jan-11 23:56
Sacha Barber16-Jan-11 23:56 
GeneralRe: IViewAwareStatusWindow in pop up window? Pin
grant_h3-Feb-11 9:41
professionalgrant_h3-Feb-11 9:41 
GeneralRe: IViewAwareStatusWindow in pop up window? Pin
Sacha Barber3-Feb-11 19:11
Sacha Barber3-Feb-11 19:11 
GeneralMy vote of 5 Pin
ACanadian7-Jan-11 18:35
ACanadian7-Jan-11 18:35 
GeneralRe: My vote of 5 Pin
Sacha Barber19-Jan-11 3:56
Sacha Barber19-Jan-11 3:56 
GeneralMy vote of 5 Pin
acryll13-Dec-10 3:18
acryll13-Dec-10 3:18 
QuestionTypo?? Pin
GerhardKreuzer5-Sep-10 5:56
GerhardKreuzer5-Sep-10 5:56 
AnswerRe: Typo?? Pin
Sacha Barber5-Sep-10 19:57
Sacha Barber5-Sep-10 19:57 
GeneralIViewAware Pin
kangaroo23200215-Aug-10 3:05
kangaroo23200215-Aug-10 3:05 
GeneralRe: IViewAware Pin
Sacha Barber15-Aug-10 7:06
Sacha Barber15-Aug-10 7:06 
GeneralMore parameters to IUIVisualizerService Pin
tonygeek19-Jul-10 13:59
tonygeek19-Jul-10 13:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.