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

WPF: If Carlsberg did MVVM Frameworks Part 4 of n

By Sacha Barber

It would probably be like Cinch a MVVM framework for WPF
C# (C#3.0, C#4.0), .NET (.NET3.5, .NET4.0), WPF, Architect, Dev, Design
Revision:12 (See All)
Posted:1 Aug 2009
Updated:23 Dec 2009
Views:20,256
Bookmarked:45 times
Unedited contribution
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
56 votes for this article.
Popularity: 8.40 Rating: 4.81 out of 5

1

2
5 votes, 8.9%
3
2 votes, 3.6%
4
49 votes, 87.5%
5

Contents

Cinch Article Series Links

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

    Last time we started looking at the 2nd part of the 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 lets get going shall we, but before we do that I just need to repeat the special thanks section, with one addition, Paul Stovell who I forgot to include last time

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

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

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

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

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

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

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

    Thanks guys/girl, you know who you are

     

    Developing Models Using Cinch

    Now 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 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 InValid 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 teams 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 2 choices.

    Cinch.EditableValidatingObject (or EditableValidatingViewModel if you need to do this work in ViewModel) : A 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 ViewModel) : A validatable (IDataErrorInfo) object. You would need to supply, logic to support 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 you 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 (its 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 objects 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 2 different things. There is some work to do to store the state of the DataWrapper<T> helpers if you choose to use EditableValidatingObject the 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 you 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 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 2 other ViewModel base classes mentioned below. That provides all the services and Window lifecycle events etc 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 use 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 2 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 maybe 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 an Simple Logging Facade (SLF) that is part of Cinch. So 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 an MessageBox service that is part of the core Cinch services. So 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. So 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 an SaveFile service that is part of the core Cinch services. So 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 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 apps main window say. Part 3 talked about this in more details.

    However, providing the WPF UI project does it's 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 causes 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 apps "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 you 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 Part2 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 you Model properties in the ViewModel.

    NOTE : This code only applies if you are using the Cinch DataWrapper<T> helpers. If you using standard types such as Int32/Double etc 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 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 Cinch app, including how to test Background work 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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

    About the Author

    Sacha Barber


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

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

    Both of these at Sussex University UK.

    Award(s)

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

    • Microsoft C# MVP 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

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

    Other popular Windows Presentation Foundation articles:

    Article Top
    You must Sign In to use this message board.
    FAQ FAQ 
     
    Noise Tolerance  Layout  Per page   
     Msgs 1 to 25 of 35 (Total in Forum: 35) (Refresh)FirstPrevNext
    GeneralQuestion about BackgroundTaskManager PinmemberMember 452580223:46 21 Nov '09  
    GeneralRe: Question about BackgroundTaskManager PinmvpSacha Barber0:52 22 Nov '09  
    GeneralHow to hook up to SingleEventCommand PinmemberSevententh6:54 16 Oct '09  
    GeneralRe: How to hook up to SingleEventCommand PinmvpSacha Barber8:11 16 Oct '09  
    GeneralRe: How to hook up to SingleEventCommand PinmemberSevententh1:46 19 Oct '09  
    GeneralExcellent article! PinmemberDrABELL13:00 14 Sep '09  
    GeneralRe: Excellent article! PinmvpSacha Barber22:36 14 Sep '09  
    GeneralSuggestion on services and use with other IOC containers Pinmemberpspidey7:20 23 Aug '09  
    GeneralRe: Suggestion on services and use with other IOC containers Pinmemberpspidey8:42 23 Aug '09  
    GeneralThanks a lot is an understatement PinmemberGDavy22:46 13 Aug '09  
    GeneralRe: Thanks a lot is an understatement PinmvpSacha Barber1:27 14 Aug '09  
    GeneralHum... PinmemberRaul Mainardi Neto3:46 13 Aug '09  
    GeneralRe: Hum... PinmvpSacha Barber3:53 13 Aug '09  
    GeneralGreat Pinmemberarvindjo0:32 12 Aug '09  
    GeneralRe: Great PinmvpSacha Barber3:46 12 Aug '09  
    GeneralAgqain, nice article. I question about PropertyChangedEventHandler and Aspects PinmemberTawani Anyangwe5:55 10 Aug '09  
    GeneralRe: Agqain, nice article. I question about PropertyChangedEventHandler and Aspects PinmvpSacha Barber10:44 10 Aug '09  
    GeneralNice Article, one question PinmemberP P Vilsad8:08 5 Aug '09  
    GeneralRe: Nice Article, one question PinmvpSacha Barber22:41 5 Aug '09  
    GeneralRe: Nice Article, one question PinmemberP P Vilsad3:51 7 Aug '09  
    GeneralRe: Nice Article, one question PinmvpSacha Barber5:42 7 Aug '09  
    GeneralCinch and key bindings Pinmemberc02db5:50 5 Aug '09  
    GeneralRe: Cinch and key bindings PinmvpSacha Barber6:06 5 Aug '09  
    GeneralRe: Cinch and key bindings Pinmemberc02db0:57 8 Aug '09  
    GeneralRe: Cinch and key bindings PinmvpSacha Barber1:59 8 Aug '09  

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

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

    PermaLink | Privacy | Terms of Use
    Last Updated: 23 Dec 2009
    Editor:
    Copyright 2009 by Sacha Barber
    Everything else Copyright © CodeProject, 1999-2010
    Web17 | Advertise on the Code Project