Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF: If Carlsberg did MVVM Frameworks: Part 4 of n

0.00/5 (No votes)
23 Dec 2009 1  
It would probably be like Cinch, an MVVM framework for WPF.

Contents

Cinch Article Series Links

Introduction

Last time we started looking at the second part of Cinch internals, and this time we are going to look at what a typical Model and ViewModel might contain when using Cinch.

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 let us 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 addresses 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 permission 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.

Developing Models Using Cinch

I know that there are some readers that will find my next statement controversial and some may even say it is downright wrong, but I will go into all of this in a minute. For now, let me just say what I want to say, which is this:

The way I do MVVM is to have an instance of a particular Model (say currentPerson) inside my ViewModel (say PeopleViewModel) which is exposed to the View (say PeopleView). The View binds and edits the Model directly.

This definitely flies in the face of what most people consider to be the holy grail of MVVM pattern, but it's a fairly new pattern, so people are still finding their way with it every day, and this works for me very well. The reason I do what I do, is for the following reasons:

  1. I have always had the luxury of being able to write my own UI specific Model classes. I would even do this if I was using some other Model classes first, such as LINQ to SQL or LINQ to Entity Framework, as these classes don't have everything a proper WPF Model class needs in my humble opinion. Though, they are pretty good, as they are Partial classes and use INotifyPropertyChanged/DataContract etc.
  2. I am a pragmatist and I do not like writing code for the sake of writing code. I have seen some MVVM apps where the author has had a Model with 50 properties on it, that are simply repeated in the ViewModel abstraction, where the ViewModel added nothing. On that day I decided I would never do that unless I have to.
  3. I honestly see no harm in writing directly to the Model from the View, just so long as if the Model is invaid, its data never makes its way to the database. I honestly see no problem with that at all.

So now that I have told you this, let's say I did not have the luxury of being able to create my own Model layer. Let's say you had another team developing the Model layer, and they did things their own way. Well, that's fine; what you would do in that case is treat this section of this article as if I was actually talking about doing this work to the ViewModel that wraps the Model rather than directly to the Model. Basically, apply what I am going to talk about next to your ViewModel (say PeopleViewModel) and leave the other team's provided Model (which presumably you have no control over) alone.

OK, argument over (hopefully). What we now need to decide is what base class you want to use for your Model (or maybe ViewModel if you have no control over your Model). Cinch has two choices.

  • Cinch.EditableValidatingObject (or EditableValidatingViewModel if you need to do this work in ViewModel): An editable (IEditableObject), validatable (IDataErrorInfo) object. You would need to supply logic to support both the editing and the validation within your own Model (or ViewModel if you have no control over your Model).
  • Cinch.ValidatingObject (or ValidatingViewModel if you need to do this work in the ViewModel): A validatable (IDataErrorInfo) object. You would need to supply logic to do validation within your own Model (or ViewModel if you have no control over your Model).

So what I am going to do now is show you en example Model (but this code could be applied directly to a ViewModel, if you need to do that using either ValidatingViewModel or EditableValidatingViewModel).

Inheriting From Cinch.EditableValidatingObject

This is the hardest Cinch base class to inherit from (though it's not that hard) as Cinch does most of the work. You will need to do the following:

  1. Decide if you want to use the DataWrapper<T> helpers that allow your ViewModel to place every individual property into edit mode independent of any other property. This is your decision; you must decide. If you choose not to use a DataWrapper<T> object for your properties, instead of something like this private Cinch.DataWrapper<Int32> orderId = new DataWrapper<Int32>(), you would declare the property as normal, like private Int32 orderId = 0.
  2. You must also provide validation rules so that the native IDataErrorInfo interface does its magic and shows the errors on the View.
  3. You must override IsValid, again the example below shows you how.
  4. You must override the BeginEdit()/OnEndEdit() and OnCancelEdit() methods inherited from the IEditableObject interface that we get by inheriting from Cinch.EditableValidatingObject. Again the code below shows you how to do this.
using System;
 
using Cinch;
using MVVM.DataAccess;
 
namespace MVVM.Models
{
    /// <summary>
    /// Respresents a UI Order Model, which has all the
    /// good stuff like Validation/INotifyPropertyChanged/IEditableObject
    /// which are all ready to use within the base class.
    /// 
    /// This class also makes use of <see cref="Cinch.DataWrapper">
    /// Cinch.DataWrapper</see>s. Where the idea is that the ViewModel
    /// is able to control the mode for the data, and as such the View
    /// simply binds to a instance of a <see cref="Cinch.DataWrapper">
    /// Cinch.DataWrapper</see> for both its data and its editable state.
    /// Where the View can disable a control based on the 
    /// <see cref="Cinch.DataWrapper">Cinch.DataWrapper</see> editable state.
    /// </summary>
 
    public class OrderModel : Cinch.EditableValidatingObject
    {
        #region Data
        //Any data item is declared as a Cinch.DataWrapper, to allow the ViewModel
        //to decide what state the data is in, and the View just renders 
        //the data state accordingly
        private Cinch.DataWrapper<Int32> orderId = new DataWrapper<Int32>();
        private Cinch.DataWrapper<Int32> customerId = new DataWrapper<Int32>();
        private Cinch.DataWrapper<Int32> quantity = new DataWrapper<Int32>();
        private Cinch.DataWrapper<Int32> productId = new DataWrapper<Int32>();
        private Cinch.DataWrapper<DateTime> deliveryDate = new DataWrapper<DateTime>();
        private IEnumerable<DataWrapperBase> cachedListOfDataWrappers;
 
        //rules
        private static SimpleRule quantityRule;
 
        #endregion
 
        #region Ctor
        public OrderModel()
        {
            #region Create DataWrappers
 
            OrderId = new DataWrapper<Int32>(this, orderIdChangeArgs);
            CustomerId = new DataWrapper<Int32>(this, customerIdChangeArgs);
            ProductId = new DataWrapper<Int32>(this, productIdChangeArgs);
            Quantity = new DataWrapper<Int32>(this, quantityChangeArgs);
            DeliveryDate = new DataWrapper<DateTime>(this, deliveryDateChangeArgs);
 
            //fetch list of all DataWrappers, so they can be used again later without the
            //need for reflection
            cachedListOfDataWrappers =
                DataWrapperHelper.GetWrapperProperties<OrderModel>(this);
 
            #endregion
 
            #region Create Validation Rules
 
            quantity.AddRule(quantityRule);
 
            #endregion
 
            //I could not be bothered to write a full DateTime picker in
            //WPF, so for the purpose of this demo, DeliveryDate is
            //fixed to DateTime.Now
            DeliveryDate.DataValue = DateTime.Now;
        }

        static OrderModel()
        {
            quantityRule = new SimpleRule("DataValue", "Quantity can not be < 0",
                      (Object domainObject)=>
                      {
                          DataWrapper<Int32> obj = (DataWrapper<Int32>)domainObject;
                          return obj.DataValue <= 0;
                      });
        }        
        #endregion
 
        #region Public Properties
 
        public Cinch.DataWrapper<Int32> OrderId
        {
            get { return orderId; }
            private set
            {
                orderId = value;
                OnPropertyChanged(() => OrderId);
            }
        }
 
        public Cinch.DataWrapper<Int32> CustomerId
        {
            get { return customerId; }
            private set
            {
                customerId = value;
                OnPropertyChanged(() => CustomerId);
            }
        }
 
        public Cinch.DataWrapper<Int32> ProductId
        {
            get { return productId; }
            private set
            {
                productId = value;
                OnPropertyChanged(() => ProductId);
            }
        }
 
        public Cinch.DataWrapper<Int32> Quantity
        {
            get { return quantity; }
            private set
            {
                quantity = value;
                OnPropertyChanged(() => Quantity);
            }
        }
 
        public Cinch.DataWrapper<DateTime> DeliveryDate
        {
            get { return deliveryDate; }
            private set
            {
                deliveryDate = value;
                OnPropertyChanged(() => DeliveryDate);
            }
        }
        #endregion
 
        #region Overrides
        /// <summary>
 
        /// Override hook which allows us to also put any child 
        /// EditableValidatingObject objects IsValid state into
        /// a combined IsValid state for the whole Model
        /// </summary>
        public override bool IsValid
        {
            get
            {
                //return base.IsValid and use DataWrapperHelper, if you are
                //using DataWrappers
                return base.IsValid &&
                    DataWrapperHelper.AllValid(cachedListOfDataWrappers);
            }
        }
        #endregion
 
        #region EditableValidatingObject overrides
 
        /// <summary>
        /// Override hook which allows us to also put any child 
        /// EditableValidatingObject objects into the BeginEdit state
        /// </summary>
        protected override void OnBeginEdit()
        {
            base.OnBeginEdit();
            //Now walk the list of properties in the CustomerModel
            //and call BeginEdit() on all Cinch.DataWrapper<T>s.
            //we can use the Cinch.DataWrapperHelper class for this
            DataWrapperHelper.SetBeginEdit(cachedListOfDataWrappers);
        }
 
        /// <summary>
 
        /// Override hook which allows us to also put any child 
        /// EditableValidatingObject objects into the EndEdit state
        /// </summary>
        protected override void OnEndEdit()
        {
            base.OnEndEdit();
            //Now walk the list of properties in the CustomerModel
            //and call CancelEdit() on all Cinch.DataWrapper<T>s.
            //we can use the Cinch.DataWrapperHelper class for this
            DataWrapperHelper.SetEndEdit(cachedListOfDataWrappers);
        }
 
        /// <summary>
        /// Override hook which allows us to also put any child 
        /// EditableValidatingObject objects into the CancelEdit state
        /// </summary>
        protected override void OnCancelEdit()
        {
            base.OnCancelEdit();
            //Now walk the list of properties in the CustomerModel
            //and call CancelEdit() on all Cinch.DataWrapper<T>s.
            //we can use the Cinch.DataWrapperHelper class for this
            DataWrapperHelper.SetCancelEdit(cachedListOfDataWrappers);
 
        }
        #endregion
    }
}

Inheriting From Cinch.ValidatingObject

This is the a slightly easier Cinch base class to inherit from (it is basically almost the same as what we just saw without the BeginEdit() / OnEndEdit() and OnCancelEdit() methods inherited from the IEditableObject), as there is no support for editability. There is, of course, still the availability to allow the individual Model (or ViewModel) fields to be editable/read only by using the DataWrapper<T> helpers, but that is your choice. Basically, BeginEdit() copies the object's current values to a temporary storage, which has nothing to do with the DataWrapper<T> helpers. The DataWrapper<T> helpers simply allow the ViewModel to tell the View (through bindings) that a particular Model/ViewModel field should not be editable. They are two different things. There is some work to do to store the state of the DataWrapper<T> helpers if you choose to use EditableValidatingObject but that is mainly taken care of for you, as long as you use the static methods on the DataWrapperHelper class as demonstrated above.

You will need to do the following:

  1. Decide if you want to use the DataWrapper<T> helpers that allow your ViewModel to place every individual property into edit mode independent of any other property. This is your decision; you must decide. If you choose not to use a DataWrapper<T> object for your properties instead of something like this private Cinch.DataWrapper<Int32> orderId = new DataWrapper<Int32>(), you would declare the property as normal, like private Int32 orderId = 0.
  2. You must also provide validation rules so that the native IDataErrorInfo interface does its magic and shows the errors on the View.
  3. You must override IsValid, again the example below shows you how.
using System;
 
using Cinch;
using MVVM.DataAccess;
 
namespace MVVM.Models
{
    /// <summary>
    /// Respresents a UI Order Model, which has all the
    /// good stuff like Validation/INotifyPropertyChanged
    /// which are all ready to use within the base class.
    /// 
    /// This class also makes use of <see cref="Cinch.DataWrapper">
    /// Cinch.DataWrapper</see>s. Where the idea is that the ViewModel
    /// is able to control the mode for the data, and as such the View
    /// simply binds to a instance of a <see cref="Cinch.DataWrapper">
    /// Cinch.DataWrapper</see> for both its data and its editable state.
    /// Where the View can disable a control based on the 
    /// <see cref="Cinch.DataWrapper">Cinch.DataWrapper</see> editable state.
    /// </summary>
 
    public class OrderModel : Cinch.ValidatingObject
    {
        #region Data
        //Any data item is declared as a Cinch.DataWrapper, to allow the ViewModel
        //to decide what state the data is in, and the View just renders 
        //the data state accordingly
        private Cinch.DataWrapper<Int32> orderId = new DataWrapper<Int32>();
        private Cinch.DataWrapper<Int32> customerId = new DataWrapper<Int32>();
        private Cinch.DataWrapper<Int32> quantity = new DataWrapper<Int32>();
        private Cinch.DataWrapper<Int32> productId = new DataWrapper<Int32>();
        private Cinch.DataWrapper<DateTime> deliveryDate = new DataWrapper<DateTime>();
        private IEnumerable<DataWrapperBase> cachedListOfDataWrappers;
        
        //rules
        private static SimpleRule quantityRule;
        #endregion
 
        #region Ctor
        public OrderModel()
        {
            #region Create DataWrappers
 
            OrderId = new DataWrapper<Int32>(this, orderIdChangeArgs);
            CustomerId = new DataWrapper<Int32>(this, customerIdChangeArgs);
            ProductId = new DataWrapper<Int32>(this, productIdChangeArgs);
            Quantity = new DataWrapper<Int32>(this, quantityChangeArgs);
            DeliveryDate = new DataWrapper<DateTime>(this, deliveryDateChangeArgs);
 
            //fetch list of all DataWrappers, so they can be used again later without the
            //need for reflection
            cachedListOfDataWrappers =
                DataWrapperHelper.GetWrapperProperties<OrderModel>(this);
 
            #endregion

            #region Create Validation Rules
 
            quantity.AddRule(quantityRule);
 
            #endregion
 
            //I could not be bothered to write a full DateTime picker in
            //WPF, so for the purpose of this demo, DeliveryDate is
            //fixed to DateTime.Now
            DeliveryDate.DataValue = DateTime.Now;
        }
        
        static OrderModel()
        {
            quantityRule = new SimpleRule("DataValue", "Quantity can not be < 0",
                      (Object domainObject)=>
                      {
                          DataWrapper<Int32> obj = (DataWrapper<Int32>)domainObject;
                          return obj.DataValue <= 0;
                      });
        }        
        #endregion
 
        #region Public Properties
 
        public Cinch.DataWrapper<Int32> OrderId
        {
            get { return orderId; }
            private set
            {
                orderId = value;
                OnPropertyChanged(() => OrderId);
            }
        }
 
        public Cinch.DataWrapper<Int32> CustomerId
        {
            get { return customerId; }
            private set
            {
                customerId = value;
                OnPropertyChanged(() => CustomerId);
            }
        }
 
        public Cinch.DataWrapper<Int32> ProductId
        {
            get { return productId; }
            private set
            {
                productId = value;
                OnPropertyChanged(() => ProductId);
            }
        }
 
        public Cinch.DataWrapper<Int32> Quantity
        {
            get { return quantity; }
            private set
            {
                quantity = value;
                OnPropertyChanged(() => Quantity);
            }
        }
 
        public Cinch.DataWrapper<DateTime> DeliveryDate
        {
            get { return deliveryDate; }
            private set
            {
                deliveryDate = value;
                OnPropertyChanged(() => DeliveryDate);
            }
        }
        #endregion
 
        #region Overrides
        /// <summary>
 
        /// Override hook which allows us to also put any child 
        /// EditableValidatingObject objects IsValid state into
        /// a combined IsValid state for the whole Model
        /// </summary>
        public override bool IsValid
        {
            get
            {
                //return base.IsValid and use DataWrapperHelper, if you are
                //using DataWrappers
                return base.IsValid &&
                    DataWrapperHelper.AllValid(cachedListOfDataWrappers);
            }
        }
        #endregion
   }
}

Note: You can do all of this directly in your ViewModels. There are a few simple things to follow to do this work directly in your ViewModel.

  1. You must either inherit from ValidatingViewModelBase or from EditableValidatingViewModelBase which gives you the functionality outlined above. Both of these classes also inherit from ViewModelBase so you will have things like services and View lifecycle events in there already.
  2. Make sure you do the work outlined above in the Developing Models Using Cinch section to the ViewModels instead of to the Model.

Developing ViewModels Using Cinch

There is actually not much you need to think about if you want to use Cinch to develop ViewModels with. Most of the functionality can be done via inheriting from one of the Cinch ViewMode base classes, and using the exposed services, which is described below. If you want the validation functionality or the editing functionality, just follow the advice I gave above for Developing Models Using Cinch, but this time inherit from ValidatingViewModelBase or EditableValidatingViewModelBase.

Choosing a ViewModel Base Class

As discussed in the Developing Models Using Cinch section, what I typically do is have a Model that I bind my View to directly, where the Model is exposed through a currentXXX property in my ViewModel. I realise that not everyone will be able to do this, so Cinch provides a number of base classes which are:

  • ViewModelBase: The main base class which is also the base class to the two other ViewModel base classes mentioned below. That provides all the services and Window lifecycle events etc. This is discussed in more detail below.
  • ValidatingViewModelBase: Which basically inherits from ViewModelBase and provides support for validation via the same rules you saw demonstrated above (Developing Models Using Cinch) for the Model I showed being developed.
  • EditableValidatingViewModelBase: Which basically inherits from ValidatingViewModelBase and provides support for editing via the same rules IEditableObject support you saw demonstrated above (Developing Models Using Cinch) for the Model I showed being developed.

The decision as to which one to use is up to you.

ViewModelBase Class

The easiest way to get started with Cinch is to inherit from the Cinch ViewModelBase class, as this gives you lots of pre made support for things like:

  • INotifyPropertyChanged
  • Window lifecycle events
  • Services

This class is also the base class for the other two possible base classes you could use for your ViewModels in Cinch. That decision is up to you.

How to Use the Exposed Services

This section will highlight how to use the services exposed by the ViewModelBase (if you inherited from Cinch ViewModelBase that is) class. You should also read how the services are designed, which was covered in Part 3 of the Cinch article series.

Note: I am acutely aware there is going to be quite a bit of repetition here from the last article, but the way I explain it this time may be a little different; there may also be some of you that did not read the previous article, that may actually find this description of the services better/more useful, whilst others may go back to the previous article, but at least you now have options. I leave it up to you. Think of it as reinforced learning.

Logging service

As we saw in the previous article, there is a Simple Logging Facade (SLF) that is part of Cinch. How do we go about using this logging facade? Well, it's very easy (if you inherited from Cinch ViewModelBase that is); here is what you do:

private void LogSomething()
{
    var logger = this.Resolve<ILogger>();
    if (logger != null)
        logger.Error("Some error occurred");
}

Or as the SLF.ILogger is also exposed as a public property on the ViewModelBase class, you could do this in your custom ViewModels, providing they inherit from Cinch.ViewModelBase.

private void LogSomething()
{
    Logger.Error("Some error occurred");
}

It is really just a matter of using the ViewModelBase.Resolve() method (which is really using the ServiceProvider class as mentioned in Part 3) to locate the service and then just use the service.

One thing to note is that the logger is a special case and must be configured through an App.Config, where as if you are happy with all the other Cinch default services, you do not need to do anything to configure them, as the defaults will be used in the case of no configuration details.

Here is an example of an App.Config that configures the Cinch logging:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
    <section name="slf" type="Slf.Config.SlfConfigurationSection, slf"/>
  </configSections>

  <slf>
    <factories>
      <!-- configure single log4net factory, which will get all logging output -->
      <!-- Important: Set a reference to the log4net facade library 
                      to make sure it will be available at runtime -->
      <factory type="SLF.Log4netFacade.Log4netLoggerFactory, SLF.Log4netFacade"/>
    </factories>
  </slf>

  <!-- configures log4net to write into a local file called "log.txt" -->
  <log4net>
    <!--  log4net uses the concept of 'appenders' to indicate 
          where log messages are written to. Appenders can be files, 
          the console, databases, SMTP and much more
    -->
    <appender name="MainAppender" type="log4net.Appender.FileAppender">
      <param name="File" value="log.txt" />
      <param name="AppendToFile" value="true" />
      <!--  log4net can optionally format the logged messages with a pattern. 
            This pattern string details what information
            is logged and the format it takes. A wide range of information 
            can be logged, including message, thread, identity and more,
            see the log4net documentation for details:
            http://logging.apache.org/log4net/release/sdk/log4net.Layout.PatternLayout.html
      -->
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%logger: %date [%thread] %-5level - %message %newline" />
      </layout>
    </appender>
    <root>
      <level value="ALL" />
      <appender-ref ref="MainAppender" />
    </root>
  </log4net>
</configuration>

MessageBox Service

As we saw in the previous article, there is a MessageBox service that is part of the core Cinch services. How do we go about using this MessageBox service? Well, it's very easy (if you inherited from Cinch ViewModelBase that is); here is what you do:

private void ShowMessageBox()
{
    var messager = this.Resolve<IMessageBoxService>();
    
    if (messager != null)
    {
        //EXAMPLE 1 : Simple MessageBox's, expecting NO return value
        messager.ShowError("There was an error");
        messager.ShowInformation("Nice day for MVVVM");
        messager.ShowWarning("Something could be going down");
 
        //EXAMPLE 2 : Simple MessageBox's, expecting return value
        if (messager.ShowYesNo("Do it?",
            CustomDialogIcons.Question) == CustomDialogResults.Yes)
        {
            //YES : Do it
        }
 
        if (messager.ShowYesNoCancel("Do it?",
            CustomDialogIcons.Exclamation) == CustomDialogResults.Cancel)
        {
            //Cancel : Unwinnd operation
        }
    }
}

It is really just a matter of using the ViewModelBase.Resolve() method (which is really using the ServiceProvider class as mentioned in Part 3) to locate the service and then just use the service.

Open File Service

As we saw in the previous article, there is an OpenFile service that is part of the core Cinch services. How do we go about using this OpenFile service? Well, it's very easy (if you inherited from Cinch ViewModelBase that is); here is what you do:

private void OpenFile()
{
    var openFileServ = this.Resolve<IOpenFileService>();
    if (openFileServ != null)
    {
        openFileServ.InitialDirectory = @"C:\";
        openFileServ.Filter = ".txt | Text Files";
 
        var result = openFileServ.ShowDialog(null);
        if (result.HasValue && result.Value == true)
        {
            //use IOpenFileService.FileName
            FileInfo file = new FileInfo(openFileServ.FileName);
            
            //do something with the file
        }
    }
}

It is really just a matter of using the ViewModelBase.Resolve() method (which is really using the ServiceProvider class as mentioned in Part 3) to locate the service and then just use the service.

Save File Service

As we saw in the previous article, there is a SaveFile service that is part of the core Cinch services. How do we go about using this SaveFile service? Well, it's very easy (if you inherited from Cinch ViewModelBase that is); here is what you do:

private void SaveFile()
{
    var saveFileServ = this.Resolve<ISaveFileService>();
    if (saveFileServ != null)
    {
        saveFileServ.InitialDirectory = @"C:\";
        saveFileServ.Filter = ".txt | Text Files";
        saveFileServ.OverwritePrompt  = true;
 
        var result = saveFileServ.ShowDialog(null);
        if (result.HasValue && result.Value == true)
        {
            var messagerServ = this.Resolve<IMessageBoxService>();
            if (messagerServ != null)
                messagerServ.ShowInformation(
                    String.Format("Successfully saved file {0}",
                        saveFileServ.FileName));
        }
    }
}

It is really just a matter of using the ViewModelBase.Resolve() method (which is really using the ServiceProvider class as mentioned in Part 3) to locate the service and then just use the service.

Popup Window Service

As discussed in Part 3, the handling of popups is a bit of a strange case, as Cinch doesn't know about the actual popup types as these are typically not within the Cinch project, they are in the WPF UI project. So the WPF UI project must supply the pop windows to the Cinch contained IUIVisualizerService service within the WPF app's main window, say. Part 3 talked about this in more detail.

However, providing the WPF UI project does its job and provides the popup instance and types somewhere to the IUIVisualizerService service, it really is very easy to show a popup from a ViewModel using Cinch. All we do is something like the following:

var uiVisualizerService = this.Resolve<IUIVisualizerService>();
bool? result = uiVisualizerService.ShowDialog("AddEditOrderPopup", 
               addEditOrderVM);
 
if (result.HasValue && result.Value)
{
    CloseActivePopUpCommand.Execute(true);
}

In the code above, the AddEditOrderPopup popup that is available within the WPF UI project is shown and passed a ViewModel instance variable called addEditOrderVM which will be used as the DataContext for the popup window. Using this technique and the IEditableObject technique discussed in Part 2, it is very easy to have popups that are able to save state (OK button) and also cause an object that is editable (that presumably was edited by the popup) to be rolled back to its previous state using the popup Cancel button, which would cause the IEditableObject to have its CancelEdit() method called to restore the previous state.

When this all works, it is rather lovely. There is a nice example of how this works in the demo app's "AddEditCustomerViewModel" code.

Background Tasks

Threading is hard, there is no doubt about it. There are also loads of different ways of doing things, but if you have read the Cinch article series so far, you will be aware that I have introduced a convenient threading helper class called BackgroundTaskManager<T> that does help out (at least I think so). This class is discussed in Part 3. So I will not go over how it works in more detail as that has already been done in the previous article.

This time I just wanted to show you how you might use the BackgroundTaskManager<T> class inside a ViewModel of your own to do some background task. So without further ado, here is an example. This example is highly fictitious and simply gets a range of future dates. The point is that it is done in the background using the BackgroundTaskManager<T> class inside a ViewModel, so that is what to take away from the following code.

using System;
using ystem.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Windows.Threading;
using System.Windows.Data;
 
using Cinch;
using System.IO;
 
namespace MVVM.ViewModels
{
    /// <summary>
    /// Provides ALL logic for the AddEditCustomerView
    /// </summary>
    public class ExampleViewModelWithBgWorker : Cinch.WorkspaceViewModel
    {
        #region Data
 
        private BackgroundTaskManager<DispatcherNotifiedObservableCollection<DateTime>> 
            bgWorker = null;
 
        private DispatcherNotifiedObservableCollection<DateTime> futureDates
            = new DispatcherNotifiedObservableCollection<DateTime>();
 
        private SimpleCommand getFutureDatesCommand;
 
        #endregion
 
        #region Ctor
        public ExampleViewModelWithBgWorker()
        {
            //Get futures Command
            getFutureDatesCommand = new SimpleCommand
            {
                CanExecuteDelegate = x => CanExecuteGetFutureDatesCommand,
                ExecuteDelegate = x => ExecuteGetFutureDatesCommand()
            };
 
        }
        #endregion
 
        #region Public Properties
 
        /// <summary>
        /// getFutureDatesCommand : Gets future dates in background thread
        /// </summary>
        public SimpleCommand GetFutureDatesCommand
        {
            get { return getFutureDatesCommand; }
        }
 
        /// <summary>
        /// Background worker which fetches 
        /// Date Ranges
        /// </summary>
        public BackgroundTaskManager<DispatcherNotifiedObservableCollection
            <DateTime>> BgWorker
        {
            get { return bgWorker; }
            set
            {
                bgWorker = value;
                OnPropertyChanged(() => BgWorker);
            }
        }
 
        /// <summary>
 
        /// Future Dates
        /// </summary>
        public DispatcherNotifiedObservableCollection<DateTime> FutureDates
        {
            get { return futureDates; }
            set
            {
                futureDates = value;
                OnPropertyChanged(() => FutureDates);
            }
        }
        #endregion
 
        #region Private Methods
 
        /// <summary>
        /// Setup backgrounder worker Task/Completion action
        /// to fetch Orders for Customers
        /// </summary>
 
        private void SetUpBackgroundWorker()
        {
            bgWorker = new BackgroundTaskManager
                    <DispatcherNotifiedObservableCollection<DateTime>>(
                () =>
                {
 
                    var dateRanges = new 
                        DispatcherNotifiedObservableCollection<DateTime>();
 
                    for (int i = 0; i < 10000; i++)
                    {
                        dateRanges.Add(DateTime.Now.AddDays(i));
                    }
 
                    return dateRanges;
                },
                (result) =>
                {
 
                    FutureDates = result;
                });
        }
        #endregion
 
        #region Command Implementation
 
        #region GetFutureDatesCommand
        /// <summary>
 
        /// Logic to determine if GetFutureDatesCommand can execute
        /// </summary>
        private Boolean CanExecuteGetFutureDatesCommand
        {
            get
            {
                return  true;
            }
        }
 
        /// <summary>
        /// Executes the GetFutureDatesCommand
        /// </summary>
        private void ExecuteGetFutureDatesCommand()
        {
            try
            {
                GetFutureDatesCommand.CommandSucceeded = false;
                bgWorker.RunBackgroundTask();
                GetFutureDatesCommand.CommandSucceeded = true;
            }
            catch (Exception ex)
            {
                Logger.Log(LogType.Error, ex);
                var messager = this.Resolve<IMessageBoxService>();
                if (messager != null)
                {
                    messager.ShowError(
                        "Failed to fetch future dates");
                }
            }
        }
        #endregion
        #endregion
    }
}

Mode Support

Some of you may recall from Part 2 that there was a little helper class called DataWrapper<T> which allows the ViewModel to put each piece of bindable data within either the Current Model (the way I do it) or the actual ViewModel bindable properties into an edit/read only state. This was done using the DataWrapper<T> helpers, which you may or may not decide to use. That is entirely up to you. If you do, here is what I suggest you do. I am assuming you have a CurrentXXX Model object exposed as a bindable property from the current ViewModel (basically the way I do it), but read on even if this is not what you do as I have some advice even if you repeat all your Model properties in the ViewModel.

Note: This code only applies if you are using the Cinch DataWrapper<T> helpers. If you are using standard types such as Int32/Double etc., please ignore this section entirely.

/// <summary>
/// The current ViewMode, when changed will loop
/// through all nested DataWrapper objects and change
/// their state also
/// </summary>
public ViewMode CurrentViewMode
{
    get { return currentViewMode; }
    set
    {
        currentViewMode = value;
 
        switch (currentViewMode)
        {
            case ViewMode.AddMode:
                CurrentCustomer = new CustomerModel();
                this.DisplayName = "Add Customer";
                break;
            case ViewMode.EditMode:
                CurrentCustomer.BeginEdit();
                this.DisplayName = "Edit Customer";
                break;
            case ViewMode.ViewOnlyMode:
                this.DisplayName = "View Customer";
                break;
        }
 
        //Now change all the CurrentCustomer.CachedListOfDataWrappers
        //Which sets all the Cinch.DataWrapper<T>s to the correct IsEditable
        //state based on the new ViewMode applied to the ViewModel
        //we can use the Cinch.DataWrapperHelper class for this
        DataWrapperHelper.SetMode(
            CurrentCustomer.CachedListOfDataWrappers,
          currentViewMode);
 
        OnPropertyChanged(() => CurrentViewMode);
    }
}

Final Word

I just wanted to say I have spent a lot of time thinking of things that could go wrong and how to make things better and to think of good approaches, but things get missed, ideas are not as good as they were thought to be etc.

Anyway to cut a long story short...The idea behind Cinch, is if it works for you in its entirety, great, if not just pick the parts that work for you. That's the way I tried to write it all.

So please just use the bits you like.

What's Coming Up?

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

  1. How to Unit test ViewModels using the Cinch app, including how to test background worker threads which may run within Cinch ViewModels
  2. 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.

History

  1. xx/xx/09: Initial release.
  2. 05/12/09: Added new code sections to show users how to add validation rules using the new validation methods within Cinch.
  3. 24/12/09: Replaced ILoggingService with Simple Logging Facade.

Thanks.

As always, votes / comments are welcome.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here