Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF

CinchV2 :Version 2 of my Cinch MVVM framework: Part 4 of n

Rate me:
Please Sign up or sign in to vote.
4.89/5 (32 votes)
17 Mar 2011CPOL29 min read 136.4K   35   85
If Jack Daniels made MVVM Frameworks

Table of Contents

Introduction

Last time, we talked about what's brand new in Cinch V2. In this article, we will compare Cinch V2 with Cinch V1 and talk about what has changed and what has stayed the same, and where appropriate, I shall show links to both Cinch V1 code and Cinch V2 code, so you can see for yourself what has changed.

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 specific SL implementation) Yes 
ThreadingAddRangeObservableCollection.cs (this is 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:

What Has Changed / What Has Stayed the Same?

Now we can get into the guts of this article which is really all about showing new/old Cinch users what has changed and what has stayed the same.

The Changes From V1 to V2

This sub section simply discusses what aspects of Cinch V1 have changed from V1 to V2.

SimpleCommand

Within Cinch V1, I did have a basic delegate style command, but I did not really make it that easy to use so in Cinch V2, I took it a step further and created a better SimpleCommand which allows you to pass in a Func<T,TResult> for the CanExecute and an Action<T> for the Execute. I also provide a CommandCompleted event which is useful for all sorts of things. Have a look at this link to find out more: CinchV2_3.aspx#SimpleCommand.

Attached Properties / Event To Command

Within Cinch V1, I offered a number of attached DPs. These have largely been replaced by Blend Interactivity Actions/Triggers/Behaviours. Let's have a look at each of the Cinch V1 offerings and I will show you what they have been replaced with in Cinch V2.

View LifeCyle Events

These were simple attached DPs that you could use to run commands in your ViewModel. There were ones for Loaded/Unloaded/Activated/DeActivated etc. These have now been replaced by the Cinch V2 IViewAwareStatus service.

You can read more about the way Cinch V1 did this using this link: CinchII.aspx#Lifecycle, and how the Cinch V2 IViewAwareStatus service works using this link: CinchV2_2.aspx#CoreServices.

Numeric TextBox Attached Behaviour

This was a simple attached DP that allowed the user to specify that a TextBox should only accept numeric characters. This has simply been turned into a Blend Behaviour.

You can read more about the way Cinch V1 did this using this link: CinchII.aspx#NumericAtt, and how the Cinch V2 Blend Behaviour works using this link: CinchV2_3.aspx#Interactivity.

Attached Command Behaviour

This was a collection of attached DPs that allowed the user to wire up an event from a certain FrameworkElement to a bound ICommand in their ViewModel. This has now been replaced by a single Blend Trigger called EventToCommandTrigger.

You can read more about the way Cinch V1 did this using this link: CinchII.aspx#CommandAtt, and how the Cinch V2 EventToCommandTrigger works using this link: CinchV2_3.aspx#Interactivity.

Workspaces

Within Cinch V1, I offered a ViewModel first type of approach, and suggested View-ViewModel resolution using a DataTemplate style approach. This is still supported in Cinch V2, but the design time support offered is far less than the new method of managing workspaces in Cinch V2.

You can read more about both these approaches using these links:

Threading Helpers

Within Cinch V1, I already included a number of threading related helpers, such as Application.DoEvents and Dispatcher extension methods. For Cinch V2, I have included far more threading helper classes, and also included the original Cinch V1 classes.

Utilities

Within Cinch V1, I already included a number of utilities related helpers, such as ObservableHelper, PropertyObserver. For Cinch V2, I have included far more utility helper classes, and also included the original Cinch V1 classes.

The Same From V1 to V2

This sub section simply discusses what aspects of Cinch V1 remains the same for V1 and V2.

ViewModel Modes

One of the things I have always struggled with when working with MVVM and using it to produce LOB apps is View mode. For example, it would be nice to have a View that is read only and then the user clicks edit and then all the fields on the View are editable. Now, this could be achieved by having a command in the ViewModel that changes from ReadOnly mode to EditMode say, and all the UIElements on the View could bind to some CurrentMode property on the ViewModel. Sounds do'able, but as we all know, things are never as clean cut as that. In my workplace, we have complicated requirements around data entry and there is no way a single mode can be applied to all data entry fields on a single View, no way. We need very granular data entry rights, down to the individual field level.

So this got me thinking. What we need is an editable state for each data item in a UI ViewModel. I thought about this some more and came up with a generic wrapper class which wraps a single property but also exposes an IsEditable property. Now the View can access these wrappers as they are public properties in the ViewModel, so it can bind its data to the wrapper's data property and can disable data entry based on the wrapper's IsEditable property.

To this end, I came up with a simple class that looks like this:

C#
using System;
using System.Reflection;
using System.Diagnostics;
using System.Linq;
using System.ComponentModel;

using System.Collections.Generic;

namespace Cinch
{
    /// <summary>
    /// Abstract base class for DataWrapper which will support IsDirty. So in your ViewModel
    /// you could do something like
    /// 
    /// <example>
    /// <![CDATA[
    /// 
    ///public bool IsDirty
    ///{
    ///   get
    ///   {
    ///     return cachedListOfDataWrappers.Where(x => (x is IChangeIndicator)
    ///     &&  ((IChangeIndicator)x).IsDirty).Count() > 0;
    ///   }
    ///
    /// } 
    /// ]]>
    /// </example>
    /// </summary>
    public abstract class DataWrapperDirtySupportingBase : EditableValidatingObject
    {
        #region Public Properties
        /// <summary>
        /// Deteremines if a property has changes since is was put into edit mode
        /// </summary>
        /// <param name="propertyName">The property name</param>
        /// <returns>True if the property has changes
        ///    since is was put into edit mode</returns>
        public bool HasPropertyChanged(string propertyName)
        {
            if (_savedState == null)
                return false;

            object saveValue;
            object currentValue;
            if (!_savedState.TryGetValue(propertyName, out saveValue) ||
                  !this.GetFieldValues().TryGetValue(propertyName, out currentValue))
                return false;
            if (saveValue == null || currentValue == null)
                return saveValue != currentValue;

            return !saveValue.Equals(currentValue);
        }
        #endregion
    }

    /// <summary>
    /// Abstract base class for DataWrapper - allows easier access to
    /// methods for the DataWrapperHelper.
    /// </summary>
    public abstract class DataWrapperBase : DataWrapperDirtySupportingBase
    {
        #region Data
        private Boolean isEditable = false;

        private IParentablePropertyExposer parent = null;
        private PropertyChangedEventArgs parentPropertyChangeArgs = null;
        #endregion

        #region Ctors
        public DataWrapperBase()
        {
        }

        public DataWrapperBase(IParentablePropertyExposer parent,
            PropertyChangedEventArgs parentPropertyChangeArgs)
        {
            this.parent = parent;
            this.parentPropertyChangeArgs = parentPropertyChangeArgs;
        }
        #endregion

        #region Protected Methods

        /// <summary>
        /// Notifies all the parent (INPC) objects
        /// INotifyPropertyChanged.PropertyChanged subscribed delegates
        /// that an internal DataWrapper property value
        /// has changed, which in turn raises the appropriate
        /// INotifyPropertyChanged.PropertyChanged event on the parent (INPC) object
        /// </summary>
        protected internal void NotifyParentPropertyChanged()
        {
            if (parent == null || parentPropertyChangeArgs == null)
                return;

            //notify all delegates listening
            //to DataWrapper<T> parent objects PropertyChanged event
            Delegate[] subscribers = parent.GetINPCSubscribers();
            if (subscribers != null)
            {
                foreach (PropertyChangedEventHandler d in subscribers)
                {
                    d(parent, parentPropertyChangeArgs);
                }
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// The editable state of the data, the View
        /// is expected to use this to enable/disable
        /// data entry. The ViewModel would set this
        /// property
        /// </summary>
        static PropertyChangedEventArgs isEditableChangeArgs =
            ObservableHelper.CreateArgs<DataWrapperBase>(x => x.IsEditable);

        public Boolean IsEditable
        {
            get { return isEditable; }
            set
            {
                if (isEditable != value)
                {
                    isEditable = value;
                    NotifyPropertyChanged(isEditableChangeArgs);
                    NotifyParentPropertyChanged();
                }
            }

        }
        #endregion
    }

    /// <summary>
    /// This interface is here so to ensure that both DataWrapper of T
    /// and DataWrapperExt of T have a commonly named property for
    /// the data (DataValue) and that we can safely retrieve this
    /// name elsewhere via static reflection.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IDataWrapper<T>
    {
        T DataValue { get; set; }
    }

    /// <summary>
    /// Allows IsDierty to be determined for a cached list of DataWrappers
    /// </summary>
    public interface IChangeIndicator
    {
        bool IsDirty { get; }
    }


    /// <summary>
    /// This interface is implemented by both the 
    /// <see cref="ValidatingObject">ValidatingObject</see> and the
    /// <see cref="ViewModelBase">ViewModelBase</see> classes, and is used
    /// to expose the list of delegates that are currently listening to the
    /// <see cref="System.ComponentModel.INotifyPropertyChanged">INotifyPropertyChanged</see>
    /// PropertyChanged event. This is done so that the internal 
    /// <see cref="DataWrapper">DataWrapper</see> classes can notify their parent object
    /// when an internal <see cref="DataWrapper">DataWrapper</see> property changes
    /// </summary>
    public interface IParentablePropertyExposer
    {
        Delegate[] GetINPCSubscribers();
    }

    /// <summary>
    /// Provides a wrapper around a single piece of data
    /// such that the ViewModel can put the data item
    /// into a editable state and the View can bind to
    /// both the DataValue for the actual Value, and to 
    /// the IsEditable to determine if the control which
    /// has the data is allowed to be used for entering data.
    /// 
    /// The Viewmodel is expected to set the state of the
    /// IsEditable property for all DataWrappers in a given Model
    /// </summary>
    /// <typeparam name="T">The type of the Data</typeparam>
    public class DataWrapper<T> : DataWrapperBase, 
                 IDataWrapper<T>, IChangeIndicator
    {
        #region Data
        private T dataValue = default(T);
        private bool isDirty = false;
        #endregion

        #region Ctors
        public DataWrapper()
        {
        }

        public DataWrapper(T initialValue)
        {
            dataValue = initialValue;
        }

        public DataWrapper(IParentablePropertyExposer parent,
            PropertyChangedEventArgs parentPropertyChangeArgs)
            : base(parent, parentPropertyChangeArgs)
        {
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// The actual data value, the View is
        /// expected to bind to this to display data
        /// </summary>
        static PropertyChangedEventArgs dataValueChangeArgs =
            ObservableHelper.CreateArgs<DataWrapper<T>>(x => x.DataValue);

        public T DataValue
        {
            get { return dataValue; }
            set
            {
                dataValue = value;
                NotifyPropertyChanged(dataValueChangeArgs);
                NotifyParentPropertyChanged();
                IsDirty = this.HasPropertyChanged("dataValue"); 
            }
        }


        /// <summary>
        /// The IsDirty status of this DataWrapper
        /// </summary>
        static PropertyChangedEventArgs isDirtyChangeArgs =
            ObservableHelper.CreateArgs<DataWrapper<T>>(x => x.IsDirty);

        public bool IsDirty
        {
            get { return isDirty; }
            set
            {
                isDirty = value;
                NotifyPropertyChanged(isDirtyChangeArgs);
                NotifyParentPropertyChanged();
            }
        }
        #endregion
    }


    /// <summary>
    /// Provides helper methods for dealing with DataWrappers
    /// within the Cinch library. 
    /// </summary>
    public class DataWrapperHelper
    {
        #region Public Methods
        // The following functions may be used when dealing with model/viewmodel objects
        // whose entire set of DataWrapper properties are immutable (only have a getter
        // for the property). They avoid having to do reflection to retrieve the list
        // of wrapper properties every time a mode change, edit state change

        /// <summary>
        /// Set all Cinch.DataWrapper properties to have the correct Cinch.DataWrapper.IsEditable 
        /// to the correct state based on the current ViewMode 
        /// </summary>
        /// <param name="wrapperProperties">The properties
        ///     on which to change the mode</param>
        /// <param name="currentViewMode">The current ViewMode</param
        public static void SetMode(IEnumerable<DataWrapperBase> wrapperProperties,
            ViewMode currentViewMode)
        {
            bool isEditable = currentViewMode ==
                    ViewMode.EditMode || currentViewMode == ViewMode.AddMode;

            foreach (var wrapperProperty in wrapperProperties)
            {
                try
                {
                    wrapperProperty.IsEditable = isEditable;
                }
                catch (Exception)
                {
                    Debug.WriteLine("There was a problem setting the currentViewMode");
                }
            }
        }

        /// <summary>
        /// Loops through a source object (UI Model class is expected really) and attempts
        /// to call the BeginEdit() method of all the  Cinch.DataWrapper fields
        /// </summary>
        /// <param name="wrapperProperties">The DataWrapperBase objects</param>
        public static void SetBeginEdit(IEnumerable<DataWrapperBase> wrapperProperties)
        {
            foreach (var wrapperProperty in wrapperProperties)
            {
                try
                {
                    wrapperProperty.BeginEdit();
                    wrapperProperty.NotifyParentPropertyChanged();
                }
                catch (Exception)
                {
                    Debug.WriteLine("There was a problem calling " + 
                          "the BeginEdit method for the current DataWrapper");
                }
            }
        }

        /// <summary>
        /// Loops through a source object (UI Model class is expected really) and attempts
        /// to call the CancelEdit() method of all the  Cinch.DataWrapper fields
        /// </summary>
        /// <param name="wrapperProperties">The DataWrapperBase objects</param>
        public static void SetCancelEdit(IEnumerable<DataWrapperBase> wrapperProperties)
        {
            foreach (var wrapperProperty in wrapperProperties)
            {
                try
                {
                    wrapperProperty.CancelEdit();
                    wrapperProperty.NotifyParentPropertyChanged();
                }
                catch (Exception)
                {
                    Debug.WriteLine("There was a problem calling " + 
                          "the CancelEdit method for the current DataWrapper");
                }
            }
        }

        /// <summary>
        /// Loops through a source object (UI Model class is expected really) and attempts
        /// to call the EditEdit() method of all the  Cinch.DataWrapper fields
        /// </summary>
        /// <param name="wrapperProperties">The DataWrapperBase objects</param>
        public static void SetEndEdit(IEnumerable<DataWrapperBase> wrapperProperties)
        {
            foreach (var wrapperProperty in wrapperProperties)
            {
                try
                {
                    wrapperProperty.EndEdit();
                    wrapperProperty.NotifyParentPropertyChanged();
                }
                catch (Exception)
                {
                    Debug.WriteLine("There was a problem calling " + 
                          "the EndEdit method for the current DataWrapper");
                }
            }
        }

        /// <summary>
        /// Loops through a source object (UI Model class is expected really) and attempts
        /// to call the EditEdit() method of all the  Cinch.DataWrapper fields
        /// </summary>
        /// <param name="wrapperProperties">The DataWrapperBase objects</param>
        public static Boolean AllValid(IEnumerable<DataWrapperBase> wrapperProperties)
        {

            Boolean allValid = true;

            foreach (var wrapperProperty in wrapperProperties)
            {
                try
                {
                    allValid &= wrapperProperty.IsValid;
                    if (!allValid)
                        break;
                }
                catch (Exception)
                {
                    allValid = false;
                    Debug.WriteLine("There was a problem calling " + 
                          "the IsValid method for the current DataWrapper");
                }
            }

            return allValid;
        }

        /// <summary>
        /// Get a list of the wrapper properties on the parent object.
        /// </summary>
        /// <typeparam name="T">The type of object</typeparam>
        /// <param name="parentObject">The parent object to examine</param>
        /// <returns>A IEnumerable of DataWrapperBase</returns>
        public static IEnumerable<DataWrapperBase> GetWrapperProperties<T>(T parentObject)
        {
            var properties = parentObject.GetType().GetProperties(
                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

            List<DataWrapperBase> wrapperProperties = new List<DataWrapperBase>();

            foreach (var propItem in parentObject.GetType().GetProperties(
                           BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
            {
                // check make sure can read and that the property is not an indexed property
                if (propItem.CanRead && propItem.GetIndexParameters().Count() == 0)
                {
                    // we ignore any property whose type CANNOT store a DataWrapper;
                    // this means any property whose type is not in the inheritance hierarchy
                    // of DataWrapper. For example a property of type Object could potentially
                    // store a DataWrapper since Object is in DataWrapper's inheritance tree.
                    // However, a boolean property CANNOT since it's not in the wrapper's
                    // inheritance tree.
                    if (typeof(DataWrapperBase).IsAssignableFrom(propItem.PropertyType) == false)
                        continue;

                    // make sure properties value is not null ref
                    var propertyValue = propItem.GetValue(parentObject, null);
                    if (propertyValue != null && propertyValue is DataWrapperBase)
                    {
                        wrapperProperties.Add((DataWrapperBase)propertyValue);
                    }
                }
            }

            return wrapperProperties;
        }
        #endregion
    }
}

Which can then be used as properties on your ViewModel, like this (don't worry about the inheriting from Cinch.EditableValidatingViewModelBase, we'll get to that soon):

C#
public class OrderViewModel : Cinch.EditableValidatingViewModelBase 
{
    //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> quantity;

    public OrderViewModel()
    {
        Quantity = new DataWrapper<int32>(this, quantityChangeArgs);
        ....
        ....
        //Setup rules etc etc
        
    }

    static PropertyChangedEventArgs quantityChangeArgs =
        ObservableHelper.CreateArgs<OrderViewModel>(x => x.Quantity);

    public Cinch.DataWrapper<Int32> Quantity
    {
        get { return quantity; }
        private set
        {
            quantity = value;
            NotifyPropertyChanged(quantityChangeArgs);
        }
    }
}

Notice the setter is private, this is due to the fact that these objects are immutable, and are only allowed to be set in the constructor. The IsEditable and DataValue can be changed whenever you like though. The other thing to note is that the ViewModel actually uses some Reflection on construction to obtain an IEnumerable<DataWrapperBase> which is then used as a cache, so setting any of the cached DataWrapper<T> properties from that point on is very quick. This is achieved as follows:

In the constructor, we have something like this:

C#
using System;

using Cinch;
using MVVM.DataAccess;
using System.ComponentModel;
using System.Collections.Generic;

namespace MVVM.ViewModels
{
    /// <summary>
    /// Respresents a UI OrderViewModel, 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 OrderViewModel : Cinch.EditableValidatingViewModelBase 
    {
        #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;
        private Cinch.DataWrapper<Int32> customerId;
        private Cinch.DataWrapper<Int32> productId;
        private Cinch.DataWrapper<Int32> quantity;
        private Cinch.DataWrapper<DateTime> deliveryDate;
        private IEnumerable<DataWrapperBase> cachedListOfDataWrappers;
        #endregion

        #region Ctor
        public OrderViewModel()
        {
            #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<OrderViewModel>(this);

            #endregion
        }
        #endregion
    }
}

And then from then on, whenever we deal with the DataWrapper<T> properties, we can use the cached list.

So getting back to how we might use these in our Views, I simply bind to these DataWrapper<T> properties as follows:

XML
<TextBox FontWeight="Normal" FontSize="11" Width="200"
    Cinch:NumericTextBoxBehavior.IsEnabled="True"            
    Text="{Binding Path=CurrentCustomerOrder.Quantity.DataValue,
    UpdateSourceTrigger=LostFocus, ValidatesOnDataErrors=True,
    ValidatesOnExceptions=True}"

    Style="{StaticResource ValidatingTextBox}"
    IsEnabled="{Binding Path=CurrentCustomerOrder.Quantity.IsEditable}"/>

That's all cool, but how do these DataWrapper<T> objects respond to a change in mode state? Well, that is quite simple actually. We do have a Cinch.ViewMode in the ViewModel and whenever that changes state, we need to update the state of all the nested DataWrapper<T> objects in whatever object it is we are trying to change the state for (which could be the ViewModel itself).

Here is an example AddEditOrderViewModel, which for me holds a single UI Model of type OrderModel. As I say, others will not like this and would have the ViewModel expose all the properties available within a UI Model of type OrderModel. The thing with MVVM is that you do it your own way, and this is my way. I don't care if InValid data gets to the Model just so long as that Model can not be saved to the database.

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Data;
using System.Linq;

using Cinch;
using MVVM.Models;
using MVVM.DataAccess;

namespace MVVM.ViewModels
{
    /// <summary>
    /// Provides ALL logic for the AddEditOrderView
    /// </summary>
    public class AddEditOrderViewModel : Cinch.EditableValidatingViewModelBase 
    {
        private ViewMode currentViewMode = ViewMode.AddMode;
        private Cinch.DataWrapper<Int32> quantity;

        #region Ctor
        public AddEditOrderViewModel()
        {
            #region Create DataWrappers

            Quantity= new DataWrapper<Int32>(this, quantityChangeArgs );
           
            //fetch list of all DataWrappers, so they can be used again later without the
            //need for reflection
            cachedListOfDataWrappers =
                DataWrapperHelper.GetWrapperProperties<AddEditOrderViewModel>(this);

            #endregion
        }
        #endregion
        
        static PropertyChangedEventArgs quantityChangeArgs =
            ObservableHelper.CreateArgs<OrderViewModel>(x => x.Quantity);
    
        public Cinch.DataWrapper<Int32> Quantity
        {
            get { return quantity; }
            private set
            {
                quantity = value;
                NotifyPropertyChanged(quantityChangeArgs);
            }
        }

        /// <summary>
        /// The current ViewMode, when changed will loop
        /// through all nested DataWrapper objects and change
        /// their state also
        /// </summary>

        static PropertyChangedEventArgs currentViewModeChangeArgs =
            ObservableHelper.CreateArgs<AddEditOrderViewModel>(x => x.CurrentViewMode);

        public ViewMode CurrentViewMode
        {
            get { return currentViewMode; }
            set
            {
                currentViewMode = value;

                switch (currentViewMode)
                {
                    case ViewMode.AddMode:
                        Quantity.DataValue= 0;
                        this.DisplayName = "Add Order"
                        break;
                    case ViewMode.EditMode:
                        this.DisplayName = "Edit Order";
                        break;
                    case ViewMode.ViewOnlyMode:
                        this.DisplayName = "View Order";
                        break;
                }

                //Now change all the 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(
                    CachedListOfDataWrappers,
                    currentViewMode);

                NotifyPropertyChanged(currentViewModeChangeArgs);
            }
        }
        ....
        ....
    }
}

One thing worth mentioning here is that when the CurrentViewMode property changes, a DataWrapperHelper class is used to set all the cached DataWrapper<T> objects for a particular object into the same state as that just requested. Here is the code that does that:

C#
// The following functions may be used when dealing with model/viewmodel objects
// whose entire set of DataWrapper properties are immutable (only have a getter
// for the property). They avoid having to do reflection to retrieve the list
// of wrapper properties every time a mode change, edit state change

/// <summary>
/// Set all Cinch.DataWrapper properties
/// to have the correct Cinch.DataWrapper.IsEditable 
/// to the correct state based on the current ViewMode 
/// </summary>
/// <param name="wrapperProperties">The properties
///    on which to change the mode</param>
/// <param name="currentViewMode">The current ViewMode</param>
public static void SetMode(IEnumerable<DataWrapperBase> wrapperProperties,
       ViewMode currentViewMode)
{
    bool isEditable = currentViewMode ==
            ViewMode.EditMode || currentViewMode == ViewMode.AddMode;

    foreach (var wrapperProperty in wrapperProperties)
    {
        try
        {
            wrapperProperty.IsEditable = isEditable;
        }
        catch (Exception)
        {
            Debug.WriteLine("There was a problem setting the currentViewMode");
        }
    }
}

Validation Rules/IDataErrorInfo Integration

I recall some time ago Paul Stovell published a great article Delegates and Business Objects which I simply loved, as it seemed to make so much sense to me. To this end, Cinch makes use of Paul's great idea to use delegates to provide validation for business objects.

The idea is simply the business objects have the AddRule(Rule newRule) method which is used to add rules, the business object also implements IDataErrorInfo, which is the preferred WPF validation technique. Then, what basically happens is that when the IDataErrorInfo.IsValid property is called against a particular business object, all the validation rules (delegates) are checked and a list of broken rules (as dictated by the delegate rules added to the object) are presented as the IDataErrorInfo.Error string.

I urge you all to read Paul Stovell's excellent Delegates and Business Objects article first, but basically, Cinch makes use of this.

What Cinch provides is:

  • A ValidatingObject base class that can be used which accepts any Rule based class to be added.
  • SimpleRule, a simple delegate rule.
  • RegexRule, a regular expression rule.
  • Quite nicely only declares the rules once per Type (as they are static fields) which saves on the amount of memory that is required for business object validation.

Here is an example of how to use these with Cinch where the property is a simple type such as String/Int32 etc.

C#
public class OrderViewModel : Cinch.EditableValidatingViewModelBase 
{
    private Int32 quantity;

    //rules
    private static SimpleRule quantityRule;

    public OrderViewModel()
    {
        #region Create Validation Rules

        quantity.AddRule(quantityRule);

        #endregion
    }

    static OrderViewModel()
    {
        quantityRule = new SimpleRule("Quantity", 
                           "Quantity can not be < 0",
                  (Object domainObject)=>
                  {
                      OrderModel obj = (OrderModel)domainObject;
                      return obj.Quantity <= 0;
                  });
    }
}

However, recall I mentioned a special Cinch class to allow the ViewModel to place a single View field into edit mode using the Cinch.DataWrapper<T>? Well, we need to do something ever so slightly different for those, we need to do the following:

C#
public class OrderViewModel : Cinch.EditableValidatingViewModelBase 
{
    #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> customerId;
    
    //rules
    private static SimpleRule quantityRule;    
    #endregion

    #region Ctor
    public OrderViewModel()
    {
        //setup DataWrappers prior to setting up rules
        ....
        ....
    
        #region Create Validation Rules

        quantity.AddRule(quantityRule);

        #endregion
    }
    
    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

    static PropertyChangedEventArgs quantityChangeArgs =
            ObservableHelper.CreateArgs<OrderViewModel>(x => x.Quantity);

    public Cinch.DataWrapper<Int32> Quantity
    {
        get { return quantity; }
        private set
        {
            quantity = value;
            NotifyPropertyChanged(quantityChangeArgs);
        }
    }
    #endregion
}

We need to declare the validation rule like this for Cinch.DataWrapper<T> objects as they are not simply properties but are actual classes, so we need to specify the DataValue property of the individual Cinch.DataWrapper<T> object to validate for the rule.

This also comes into play within the IsValid method you get when you inherit from a Cinch.EditableValidatingViewModelBase object. Let's say you have something like this for a UI ViewModel object:

C#
using System;

using Cinch;
using MVVM.DataAccess;
using System.ComponentModel;
using System.Collections.Generic;

namespace MVVM.ViewModels
{
    /// <summary>
    /// Respresents a UI Order ViewModel, 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 OrderViewModel : Cinch.EditableValidatingViewModelBase 
    {
        #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;
        private Cinch.DataWrapper<Int32> customerId;
        private Cinch.DataWrapper<Int32> productId;
        private Cinch.DataWrapper<Int32> quantity;
        private Cinch.DataWrapper<DateTime> deliveryDate;
        private IEnumerable<DataWrapperBase> cachedListOfDataWrappers;

        //rules
        private static SimpleRule quantityRule;

        #endregion

        #region Ctor
        public OrderViewModel()
        {
            #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<OrderViewModel>(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

        /// <summary>
        /// OrderId
        /// </summary>
        static PropertyChangedEventArgs orderIdChangeArgs =
               ObservableHelper.CreateArgs<OrderViewModel>(x => x.OrderId);

        public Cinch.DataWrapper<Int32> OrderId
        {
            get { return orderId; }
            private set
            {
                orderId = value;
                NotifyPropertyChanged(orderIdChangeArgs);
            }
        }

        /// <summary>
        /// CustomerId
        /// </summary>
        static PropertyChangedEventArgs customerIdChangeArgs =
            ObservableHelper.CreateArgs<OrderViewModel>(x => x.CustomerId);

        public Cinch.DataWrapper<Int32> CustomerId
        {
            get { return customerId; }
            private set
            {
                customerId = value;
                NotifyPropertyChanged(customerIdChangeArgs);
            }
        }

        /// <summary>
        /// ProductId
        /// </summary>
        static PropertyChangedEventArgs productIdChangeArgs =
            ObservableHelper.CreateArgs<OrderViewModel>(x => x.ProductId);

        public Cinch.DataWrapper<Int32> ProductId
        {
            get { return productId; }
            private set
            {
                productId = value;
                NotifyPropertyChanged(productIdChangeArgs);
            }
        }

        /// <summary>
        /// Quantity
        /// </summary>
        static PropertyChangedEventArgs quantityChangeArgs =
            ObservableHelper.CreateArgs<OrderViewModel>(x => x.Quantity);

        public Cinch.DataWrapper<Int32> Quantity
        {
            get { return quantity; }
            private set
            {
                quantity = value;
                NotifyPropertyChanged(quantityChangeArgs);
            }
        }

        /// <summary>
        /// DeliveryDate
        /// </summary>
        static PropertyChangedEventArgs deliveryDateChangeArgs =
            ObservableHelper.CreateArgs<OrderViewModel>(x => x.DeliveryDate);

        public Cinch.DataWrapper<DateTime> DeliveryDate
        {
            get { return deliveryDate; }
            private set
            {
                deliveryDate = value;
                NotifyPropertyChanged(deliveryDateChangeArgs);
            }
        }

        /// <summary>
        /// Returns cached collection of DataWrapperBase
        /// </summary>
        public IEnumerable<DataWrapperBase> CachedListOfDataWrappers
        {
            get { return cachedListOfDataWrappers; }
        }
        #endregion

        #region Overrides
        /// <summary>
        /// Is the Model Valid
        /// </summary>
        public override bool IsValid
        {
            get
            {
                //return base.IsValid and use DataWrapperHelper, if you are
                //using DataWrappers
                return base.IsValid &&
                    DataWrapperHelper.AllValid(cachedListOfDataWrappers);
            }
        }
        #endregion
    }
}

You would then need to override the IsValid property to look like this, where we come up with a combined IsValid for the entire object based not only on its IsValid but also the IsValid state of any nested Cinch.DataWrapper<T> object, which is very easy as they also inherit from Cinch.EditableValidatingViewModelBase which in turn inherits from Cinch.ValidatingViewModelBase, so they already have the IDataErrorInfo implementation, so it is not that hard to cope with.

I know this seems a lot of extra work, but the added benefit of the ViewModel being able to set an individual field's editability state, and have the View reflect this seamlessly via bindings, simply can not be ignored.

C#
/// <summary>
/// Override hook which allows us to also put any child 
/// EditableValidatingViewModelBase 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);
    }
}

Typically, the WPF style we would use for a TextBox that needed to supply validation support for IDataErrorInfo would look something like the following, where we use the Validation.HasError property to change the border color of the TextBox when there is a validation error present.

XML
<Style x:Key="ValidatingTextBox" TargetType="{x:Type TextBoxBase}">
    <Setter Property="SnapsToDevicePixels" Value="True"/>
    <Setter Property="OverridesDefaultStyle" Value="True"/>

    <Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>

    <Setter Property="MinWidth" Value="120"/>

    <Setter Property="MinHeight" Value="20"/>

    <Setter Property="AllowDrop" Value="true"/>
    <Setter Property="Validation.ErrorTemplate" Value="{x:Null}"/>

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBoxBase}">
                <Border 
                      Name="Border"
                      CornerRadius="5" 
                      Padding="2"

                      Background="White"
                      BorderBrush="Black"
                      BorderThickness="2" >
                    <ScrollViewer Margin="0" x:Name="PART_ContentHost"/>

                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsEnabled" Value="False">
                        <Setter TargetName="Border" 
                            Property="Background" Value="LightGray"/>

                        <Setter TargetName="Border" 
                            Property="BorderBrush" Value="Black"/>
                        <Setter Property="Foreground" Value="Gray"/>

                    </Trigger>
                    <Trigger Property="Validation.HasError" Value="true">
                        <Setter TargetName="Border" Property="BorderBrush" 
                                Value="Red"/>

                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip"
                        Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                        Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
        </Trigger>
    </Style.Triggers>
</Style>

IEditableObject Support

I have in the past used a pattern called Memento which basically is a cool pattern for support of Undo on business objects. Basically, what it allows for is the storage of an object's state to a Memento backing object which has the exact same properties as the business object it was storing state for. So when you start an edit on a business object, you store the current state in a Memento and do your edit. If you cancell the edit, the business object's state would be restored from the Memento. This does work very well, but Microsoft also supports this via an interface called IEditableObject which looks like this:

  • BeginEdit()
  • CancelEdit()
  • EndEdit()

Using this interface, we can actually make our business objects store their own state. Now, I can take no credit for this next piece of code, it comes from Mark Smith's excellent work. Actually, a fair amount of Cinch is down to Mark Smith's work; again, I did ask Mark if I could poach his code, he said yes, great cheers Mark.

What Cinch does is provide a base class that can be used for you to inherit from for your business objects. This base class also supports validation via the IDataErrorInfo interface we just discussed above. Here is how it works. On BeginEdit(), a little bit of Reflection/LINQ is used to store the current object's state in an internal Dictionary. On Canceldit(), the internal Dictionary values are restored to the current object's properties using the property name as a key into the stored Dictionary state.

Here is the Cinch base class that does all this:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Diagnostics;

namespace Cinch
{
    /// <summary>
    /// Provides a IDataErrorInfo validating object that is also
    /// editable by implementing the IEditableObject interface
    /// </summary>
    public abstract partial class EditableValidatingViewModelBase :
        ValidatingViewModelBase, IEditableObject
    {
        #region Data
        /// <summary>
        /// This stores the current "copy" of the object. 
        /// If it is non-null, then we are in the middle of an 
        /// editable operation.
        /// </summary>
        private Dictionary<string, object> _savedState;
        #endregion

        #region Public/Protected Methods
        /// <summary>
        /// Begins an edit on an object.
        /// </summary>

        public void BeginEdit()
        {
            OnBeginEdit();
            _savedState = GetFieldValues();
        }

        /// <summary>
        /// Interception point for derived logic to do work when beginning edit.
        /// </summary>
        protected virtual void OnBeginEdit()
        {
        }

        /// <summary>
        /// Discards changes since the last 
        /// <see cref="M:System.ComponentModel.IEditableObject.BeginEdit"/> call.
        /// </summary>
        public void CancelEdit()
        {
            OnCancelEdit();
            RestoreFieldValues(_savedState);
            _savedState = null;
        }

        /// <summary>
        /// This is called in response CancelEdit and provides an interception point.
        /// </summary>
        protected virtual void OnCancelEdit()
        {
        }

        /// <summary>
        /// Pushes changes since the last 
        /// <see cref="M:System.ComponentModel.IEditableObject.BeginEdit"/> 
        /// or <see cref="M:System.ComponentModel.IBindingList.AddNew"/> 
        /// call into the underlying object.
        /// </summary>
        public void EndEdit()
        {
            OnEndEdit();
            _savedState = null;
        }

        /// <summary>
        /// This is called in response EndEdit and provides an interception point.
        /// </summary>
        protected virtual void OnEndEdit()
        {
        }

        /// <summary>
        /// This is used to clone the object.
        /// Override the method to provide a more efficient clone.
        /// The default implementation simply reflects across 
        /// the object copying every field.
        /// </summary>
        /// <returns>Clone of current object</returns>
        protected virtual Dictionary<string, object> GetFieldValues()
        {
            return GetType().GetProperties(BindingFlags.Public |
                BindingFlags.NonPublic | BindingFlags.Instance)
                .Where(pi => pi.CanRead && pi.GetIndexParameters().Length == 0)
                .Select(pi => new { Key = pi.Name, Value = pi.GetValue(this, null) })
                .ToDictionary(k => k.Key, k => k.Value);

        }

        /// <summary>
        /// This restores the state of the current object from the passed clone object.
        /// </summary>
        /// <param name="fieldValues">Object to restore state from</param>
        protected virtual void RestoreFieldValues(Dictionary<string, object> fieldValues)
        {
            foreach (PropertyInfo pi in GetType().GetProperties(
                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Where(pi => pi.CanWrite && pi.GetIndexParameters().Length == 0) )
            {
                object value;
                if (fieldValues.TryGetValue(pi.Name, out value))
                    pi.SetValue(this, value, null);
                else
                {
                    Debug.WriteLine("Failed to restore property " +
                    pi.Name + " from cloned values, property not found in Dictionary.");
                }
            }
        }
        #endregion
    }
}

So all you have to do to get editability support is inherit your UI model objects from Cinch.EditableValidatingViewModelBase. Job done.

Let's see how you might put an object that inherits from Cinch.EditableValidatingViewModelBase into Edit mode.

From the ViewModel, we can simply do this.BeginEdit(), it's that easy. However, what you must also do if you have nested Cinch.DataWrapper<T> objects is make sure they too are put into the correct state. You would do this in your UI ViewModel class as follows, where we simply override the protected virtual void OnBeginEdit() we get from inheriting from Cinch.EditableValidatingViewModelBase.

Where we may have a UI ViewModel object that looks like:

C#
using System;
using Cinch;
using MVVM.DataAccess;
using System.ComponentModel;
using System.Collections.Generic;

namespace MVVM.ViewModels
{
    /// <summary>
    /// Respresents a UI Order ViewModel, 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 OrderViewModel : Cinch.EditableValidatingViewModelBase 
    {
        #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;
        private Cinch.DataWrapper<Int32> customerId;
        private Cinch.DataWrapper<Int32> productId;
        private Cinch.DataWrapper<Int32> quantity;
        private Cinch.DataWrapper<DateTime> deliveryDate;
        private IEnumerable<DataWrapperBase> cachedListOfDataWrappers;

        //rules
        private static SimpleRule quantityRule;

        #endregion

        #region Ctor
        public OrderViewModel()
        {
            #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<OrderViewModel>(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

        /// <summary>
        /// OrderId
        /// </summary>
        static PropertyChangedEventArgs orderIdChangeArgs =
            ObservableHelper.CreateArgs<OrderViewModel>(x => x.OrderId);

        public Cinch.DataWrapper<Int32> OrderId
        {
            get { return orderId; }
            private set
            {
                orderId = value;
                NotifyPropertyChanged(orderIdChangeArgs);
            }
        }

        /// <summary>
        /// CustomerId
        /// </summary>
        static PropertyChangedEventArgs customerIdChangeArgs =
            ObservableHelper.CreateArgs<OrderViewModel>(x => x.CustomerId);

        public Cinch.DataWrapper<Int32> CustomerId
        {
            get { return customerId; }
            private set
            {
                customerId = value;
                NotifyPropertyChanged(customerIdChangeArgs);
            }
        }

        /// <summary>
        /// ProductId
        /// </summary>
        static PropertyChangedEventArgs productIdChangeArgs =
            ObservableHelper.CreateArgs<OrderViewModel>(x => x.ProductId);

        public Cinch.DataWrapper<Int32> ProductId
        {
            get { return productId; }
            private set
            {
                productId = value;
                NotifyPropertyChanged(productIdChangeArgs);
            }
        }

        /// <summary>
        /// Quantity
        /// </summary>
        static PropertyChangedEventArgs quantityChangeArgs =
            ObservableHelper.CreateArgs<OrderViewModel>(x => x.Quantity);

        public Cinch.DataWrapper<Int32> Quantity
        {
            get { return quantity; }
            private set
            {
                quantity = value;
                NotifyPropertyChanged(quantityChangeArgs);
            }
        }

        /// <summary>
        /// DeliveryDate
        /// </summary>
        static PropertyChangedEventArgs deliveryDateChangeArgs =
            ObservableHelper.CreateArgs<OrderViewModel>(x => x.DeliveryDate);

        public Cinch.DataWrapper<DateTime> DeliveryDate
        {
            get { return deliveryDate; }
            private set
            {
                deliveryDate = value;
                NotifyPropertyChanged(deliveryDateChangeArgs);
            }
        }

        /// <summary>
        /// Returns cached collection of DataWrapperBase
        /// </summary>
        public IEnumerable<DataWrapperBase> CachedListOfDataWrappers
        {
            get { return cachedListOfDataWrappers; }
        }
        #endregion

        #region Overrides
        /// <summary>
        /// Is the Model Valid
        /// </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 EditableValidatingViewModelBase overrides

        /// <summary>
        /// Override hook which allows us to also put any child 
        /// EditableValidatingViewModelBase objects into the BeginEdit state
        /// </summary>
        protected override void OnBeginEdit()
        {
            base.OnBeginEdit();
            //Now walk the list of properties in the OrderViewModel
            //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 
        /// EditableValidatingViewModelBase objects into the EndEdit state
        /// </summary>
        protected override void OnEndEdit()
        {
            base.OnEndEdit();
            //Now walk the list of properties in the OrderViewModel
            //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 
        /// EditableValidatingViewModelBase objects into the CancelEdit state
        /// </summary>
        protected override void OnCancelEdit()
        {
            base.OnCancelEdit();
            //Now walk the list of properties in the OrderViewModel
            //and call CancelEdit() on all Cinch.DataWrapper<T>s.
            //we can use the Cinch.DataWrapperHelper class for this
            DataWrapperHelper.SetCancelEdit(cachedListOfDataWrappers);
        }
        #endregion
    }
}

What we need to do is override the Cinch.EditableValidatingViewModelBase virtual method as follows:

C#
#region EditableValidatingViewModelBase overrides

/// <summary>
/// Override hook which allows us to also put any child 
/// EditableValidatingViewModelBase objects into the BeginEdit state
/// </summary>
protected override void OnBeginEdit()
{
    base.OnBeginEdit();
    //Now walk the list of properties in the OrderViewModel
    //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 
/// EditableValidatingViewModelBase objects into the EndEdit state
/// </summary>
protected override void OnEndEdit()
{
    base.OnEndEdit();
    //Now walk the list of properties in the OrderViewModel
    //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 
/// EditableValidatingViewModelBase objects into the CancelEdit state
/// </summary>
protected override void OnCancelEdit()
{
    base.OnCancelEdit();
    //Now walk the list of properties in the OrderViewModel
    //and call CancelEdit() on all Cinch.DataWrapper<T>s.
    //we can use the Cinch.DataWrapperHelper class for this
    DataWrapperHelper.SetCancelEdit(cachedListOfDataWrappers);

}
#endregion

Where the Cinch framework provides a static helper called DataWrapperHelper, which you must use to set the correct edit state of the nested DataWrapper<T> objects; you can use these helper methods:

  • DataWrapperHelper.SetBeginEdit(IEnumerable<DataWrapperBase> wrapperProperties)
  • DataWrapperHelper.SetEndEdit(IEnumerable<DataWrapperBase> wrapperProperties)
  • DataWrapperHelper.SetCancelEdit(IEnumerable<DataWrapperBase> wrapperProperties)

Where IEnumerable<DataWrapperBase> wrapperProperties is actually the cachedListOfDataWrappers which was obtained during the object construction.

You do not have to worry about this. Cinch will do this for you, providing you do the right thing in the UI ViewModel class. If you are feeling a little lost by all this, do not worry, help is at hand. You can read all about creating ViewModels using Cinch using this article link: CinchIV.aspx#DevelopingVMs.

WeakEvent Creation

Before I start talking about how to create WeakEvents, I think this may be a good place to start a small discussion. I imagine there are loads of readers/.NET developers that think Events in .NET are cool. Well, me too, I love events. The thing is, how many of you think you need to worry much about Garbage Collection and when dealing with events, .NET manages its own memory via the GC right? Well yeah, it does, but Events are one area that are, shall we say, a little gray in .NET.

Image 1

In the diagram above, there is an object ("eventExposer") that declares an event ("SpecialEvent"). Then, a form is created ("myForm") that adds a handler to the event. The form is closed and the expectation is that the form will be released to garbage collection, but it isn't. Unfortunately, the underlying delegate of the event still maintains a strong reference to the form because the form's handler wasn't removed.

The image and text are borrowed from http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx.

In typical applications, it is possible that handlers that are attached to event sources will not be destroyed in coordination with the listener object that attached the handler to the source. This situation can lead to memory leaks. Windows Presentation Foundation (WPF) introduces a particular design pattern that can be used to address this issue, by providing a dedicated manager class for particular events and implementing an interface on listeners for that event. This design pattern is known as the WeakEvent pattern.

MSDN: http://msdn.microsoft.com/en-us/library/aa970850.aspx

Now if you have ever looked into the Weak Event manager / interface implementation, you will realise it is quite a lot of work and you must have a new WeakEventManager per Event type. This to me sounds like too much work, so I would prefer some other mechanisms, such as have a WeakEvent in the beginning. Better still, maybe have a weak listener that only reacts to the source event if the source of the event is still alive and has not been GC'd.

Raise a WeakEvent<T>

So without further ado, let me show you some handy little helpers that are available within Cinch when dealing with Events, and possibly making them Weak. It is still obviously better to Add/Remove the delegates for an event manually where possible, but sometimes you just do not know the lifecyles of the objects involved, so it is preferable to opt for a WeakEvent strategy.

Firstly, let's use the absolutely brilliant WeakEvent<T> from the very, very talented Daniel Grunwald, who published a superb article on WeakEvents some time ago. Daniel's WeakEvent<T> shows how you can raise an event in a weak manner.

I am not going to bore you with all the code for WeakEvent<T> but one thing that you should probably get familiar with, if you are not already, is the WeakReference class. This is a standard .NET class which references an object while still allowing that object to be reclaimed by garbage collection.

Pretty much any WeakEvent subscription/raising of event will use an internal WeakReference class to allow the source of the event or the subscriber to be GC'd.

Anyway, to use Daniel Grunwald's WeakEvent<T>, we can do the following:

Declaring the WeakEvent<T>
C#
private readonly WeakEvent<EventHandler<EventArgs>> 
     dependencyChangedEvent =
         new WeakEvent<EventHandler<EventArgs>>();

public event EventHandler<EventArgs> DependencyChanged
{
  add { dependencyChangedEvent.Add(value); }
  remove { dependencyChangedEvent.Remove(value); }
}
Raising the WeakEvent<T>
C#
dependencyChangedEvent.Raise(this, new EventArgs());
Listening to WeakEvent<T>
C#
SourceDependency.DependencyChanged += OnSourceChanged;
...
private void OnSourceChanged(object sender, EventArgs e)
{
}

That is how you could make a WeakEvent<T>, but sometimes it is not your own code and you are not in charge of the Events contained in the code. Perhaps you are using a third party control set. In that case, you may need to use a WeakEvent subscription. Cinch provides two methods of doing this.

WeakEvent Subscription

Above we saw how to raise a WeakEvent using Daniel Grunwald's WeakEvent<T>. How about in the case where we want to subscribe to an existing event? Again, this is typically achieved using a WeakReference class to check the WeakReference.Target for null. If the value is null, the source of the event has been garbage collected so do not fire the invocation list delegate; if it is not null, the source of the event is alive so call the invocation list delegate which subscribed to the event.

Cinch provides two methods to do this.

WeakEventProxy

Which is a neat little class which Paul Stovell wrote some time ago. The entire class looks like this:

C#
using System;

namespace Cinch
{
    public class WeakEventProxy<TEventArgs> : IDisposable
        where TEventArgs : EventArgs
    {
        #region Data
        private WeakReference callbackReference;
        private readonly object syncRoot = new object();
        #endregion

        #region Ctor
        /// <summary>
        /// Initializes a new instance of the <see 
          /// cref="WeakEventProxy<TEventArgs>"/> class.
        /// </summary>

        /// <param name="callback">The callback.</param>
        public WeakEventProxy(EventHandler<TEventArgs> callback)
        {
            callbackReference = new WeakReference(callback, true);
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Used as the event handler which should be subscribed to source collections.
        /// </summary>

        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void Handler(object sender, TEventArgs e)
        {
            //acquire callback, if any
            EventHandler<TEventArgs> callback;
            lock (syncRoot)
            {
                callback = callbackReference == null ? null : 
            callbackReference.Target as EventHandler<TEventArgs>;
            }

            if (callback != null)
            {
                callback(sender, e);
            }
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, 
        /// releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            lock (syncRoot)
            {
                GC.SuppressFinalize(this);

                if (callbackReference != null)
                {
                    //test for null in case the reference was already cleared
                    callbackReference.Target = null;
                }

                callbackReference = null;
            }
        }
        #endregion
    }
}

And to use this, we can simply do the following:

Declare Event Handlers like
C#
private EventHandler<NotifyCollectionChangedEventArgs> 
     collectionChangeHandler;
private WeakEventProxy<NotifyCollectionChangedEventArgs> 
     weakCollectionChangeListener;
And wire up the event subscription delegate like
C#
if (weakCollectionChangeListener == null)
{
  collectionChangeHandler = OnCollectionChanged;
  weakCollectionChangeListener = 
     new WeakEventProxy<NotifyCollectionChangedEventArgs>(
         collectionChangeHandler);
}
ncc.CollectionChanged += weakCollectionChangeListener.Handler;

private void OnCollectionChanged(object sender, 
        NotifyCollectionChangedEventArgs e)
{
}

WeakEvent Subscriber With Auto Unsubscription

I was trawling the internet one day and found this superb article on WeakEvents: http://diditwith.net/PermaLink,guid,aacdb8ae-7baa-4423-a953-c18c1c7940ab.aspx. This link contained some cool code that I have used within Cinch, which not only allows users to create WeakEvent subscriptions, but allows the user to specify an auto unsubscribe callback delegate. In addition, using a small variation to this code, it is possible to make all subscribed event handlers weak. Let's have a quick look at the syntax for both these operations.

Specifying a WeakEvent Subscription With Unhook

We simply do this:

C#
workspace.CloseWorkSpace +=
    new EventHandler<EventArgs>(OnCloseWorkSpace).
       MakeWeak(eh => workspace.CloseWorkSpace -= eh);
       
private void OnCloseWorkSpace(object sender, EventArgs e)
{
}

That one line creates a Weak listener with an auto unsubscribe. Neat huh?

I mentioned that you can also use this code to create a WeakEvent, such that all subscribers to a particular event would be Weak. This is how you could do that using this code:

C#
public class EventProvider
{
    private EventHandler<EventArgs> closeWorkSpace;
    public event EventHandler<EventArgs> CloseWorkSpace
    {
        add
        {
            closeWorkSpace += value.MakeWeak(eh => closeWorkSpace -= eh);
        }
        remove
        {
        }
    }
}

As I say, I can not take much credit for this code, it came from the link specified, but I do think it's very handy. We actually use it in production code without too many issues. The only thing I have noticed is that it doesn't play well with the CollectionChanged of ObservableCollection<T>, but then I just use the WeakEventProxy that I also mentioned above that is part of Cinch, and that works just fine.

Mediator Messaging

Now I do not know about you, but generally when I work with the MVVM framework, I do not have a single ViewModel managing the whole shooting match. I actually have a number of them (in fact, we have loads). One thing that is an issue using the standard MVVM pattern is cross ViewModel communication. After all, the ViewModels that form an application may all be disparate unconnected objects that know nothing about each other. However, they need to know about certain actions that a user performs. Here is a concrete example.

Say you have two Views, one with customers and one with orders for a customer. Let's say the Orders view was using a OrdersViewModel and that the Customers view was using a CustomersViewModel, and when a Customers order is updated, deleted, or added to that, the Customer view should show some sort of visual trigger to alert the user that some order detail of the customer has changed.

Sounds simple enough, right? However, we have two independent Views run by two independent ViewModels, with no link, but clearly, there needs to be some sort of connection from the OrdersViewModel to the CustomersViewModel, some sort of messaging perhaps.

This is exactly what the Mediator pattern is all about, it is a simple light weight messaging system. I wrote about this some time ago on my blog, which in turn got made a ton better by Josh Smith / Marlon Grech (as an atomic pair) who came up with the Mediator implementation you will see in Cinch.

So how does the Mediator work?

This diagram may help:

Image 2

The idea is a simple one, the Mediator listens for incoming messages, sees who is interested in a particular message, and calls each of those that are subscribed against a given message. The messages are usually strings.

Basically, what happens is that there is a single instance of the Mediator sitting somewhere (usually exposed as a static property on the ViewModelBase class) that is waiting for objects to subscribe to it either using:

  • An entire object reference. Then any Mediator message methods that have been marked up with the MediatorMessageSinkAttribute attribute will be located on the registered object (using Reflection) and will have a callback delegate automatically created.
  • An actual Lambda callback delegate.

In either case, the Mediator maintains a list of WeakActions callback delegates. Where each WeakAction is a delegate which uses an internal WeakReference class to check the WeakReference.Target for null, before calling back the delegate. This caters for the fact that the target of the callback delegate may no longer be alive as it may have been Garbage Collected. Any instance of WeakActions callback delegates that point to objects that are no longer alive are removed from the list of Mediator WeakAction callback delegates.

When a callback delegate is obtained, either the original callback delegate is called or the Mediator message methods that have been marked up with the MediatorMessageSinkAttribute attribute will be called.

Here is an example of how to use the Mediator in all the different possible manners.

Registering for Messages

Using an explicit callback delegate (this is not my proffered option though).

We simply create the correct type of delegate and Register a callback for a message notification with the Mediator.

C#
public delegate void DummyDelegate(Boolean dummy);
...

Mediator.Instance.Register("AddCustomerMessage", new DummyDelegate((x) =>
{
    AddCustomerCommand.Execute(null);
}));

Register an entire object, and use the MediatorMessageSinkAttribute attribute

This is my favourite approach and is the simplest approach in my opinion. You just need to register an entire object with the Mediator and attribute up some message hook methods.

Within Cinch V1, I would automatically register any ViewModel that inherited from ViewModelBase for you. Now for Cinch V2, I decided against this, as you may have many ViewModels that do not ever require the Mediator at all, so you have to manually register with the Mediator yourself in your ViewModel, which is done as follows: 

C#
//Register all decorated methods to the Mediator
Mediator.Instance.Register(this);

Any method that is marked up with the MediatorMessageSinkAttribute attribute will be located on the registered object (using Reflection) and will have a callback delegate automatically created. Here is an example:

C#
/// <summary>
/// Mediator callback from StartPageViewModel
/// </summary>
/// <param name="dummy">Dummy not needed</param>

[MediatorMessageSink("AddCustomerMessage"))]
private void AddCustomerMessageSink(Boolean dummy)
{
    AddCustomerCommand.Execute(null);
}

So how about notification of messages?

Message Notification

That is very easy to do. We simply use the Mediator.Instance.NotifyCollegues() method as follows:

C#
//Use the Mediator to send a Message to MainWindowViewModel to add a new 
//Workspace item
Mediator.Instance.NotifyColleagues<Boolean>("AddCustomerMessage", true);

You can also use the Mediator asynchronously as follows:

C#
Mediator.Instance.NotifyColleaguesAsync<Boolean>("AddCustomerMessage", true);

Choice of Model Bases

In Cinch V1, I started off telling people to expose a CurrentXXXModel off their ViewModel and bind to that. Several people started to complain that they could not edit the model. Now for me, the UI stack was always something like the one directly below:

DB -> LINQ to SQL/EF -> Server Side Model -> WCF -> UI Model -> UI ViewModel -> View

where the UI was always in charge of its own Model. In fact, that is what the Cinch V1 demo app shows people, using LINQ to EF. Where there is no sharing of Model objects between the server and client, each side has its own types. So one is able to expose a current Model (where the Model is a Cinch based Model).

Cinch provides several Model base classes that you can use, these are as follows: 

  • ValidatingObject: Provides support for DataWrappers and INotifyPropertyChanged implementation, and support for Validation rules via an IDataErrorInfo implementation.
  • EditableValidatingObject: Provides editable object support via an IEditableObject implementation.

If this describes your situation, you may want to read this section of an older Cinch V1 article: CinchIV.aspx#DevelopingModels.

Important note: As I effectively changed my mind to match what most people wanted (below), the Cinch code generator will output code where the code produced is expecting to inherit from a Cinch ViewModel, so be warned, if you go down the exposed CurrentXXXModel off your ViewModel, the code generator will not help you at all.

Complaints Complaints Complaints

That said, a lot of people stated that their stack looked more like this:

DB -> LINQ to SQL/EF (shared) -> Server Side Model -> WCF -> LINQ to SQL/EF (shared) -> UI ViewModel -> View

So they could not modify their model to have it inherit from a Cinch V1 Model class. This is fair enough; in fact, I have done a 360 degree turn around on my thinking, and no longer advocate exposing a CurrentXXXModel off a ViewModel, but rather pass things from the View into the ViewModel and then into the Model. As such, all Validation Rules (IDataErrorInfo) / ViewMode changes / IEditableObject operations should be made against a ViewModel that supports these operations. I discuss these options in the next section.

In fact, there is as entire Cinch V1 article dedicated to discussing the whole exposed Model/ViewModel debate, which you can find at: CinchIV.aspx.

Choice of ViewModel Bases

As I just stated, Cinch does actually allow you to either expose a CurrentXXXModel off your ViewModel which supports DataWrappers/Validation Rules (IDataErrorInfo)  / ViewMode changes / IEditableObject operations, by use of the two Cinch Model classes mentioned above.

But as I also stated above, I no longer recommend that approach, and think that the Model should be left alone, and that you should do all your DataWrappers/Validation Ruless (IDataErrorInfo)  / ViewMode changes / IEditableObject operations in your ViewModel. As such, Cinch provides several ViewModel base classes that you can use; these are as follows: 

  • ViewModelBase: Provides support for DataWrappers and an INotifyPropertyChanged implementation
  • ValidatingViewModelBase: Provides support for Validation rules via an IDataErrorInfo implementation
  • EditableValidatingViewModelBase: Provides editable object support via an IEditableObject implementation

As before, please see the Cinch V1 article dedicated to discussing the whole exposed Model/ViewModel debate, which you can find at: CinchIV.aspx.

And this section will be of particular interest: CinchIV.aspx#DevelopingVMs; you will of course have to ignore how the services are retrieved, as these are now provided by meffedMVVM.

Unit Testing

Although a few of the services have changed and a couple more have been added, Unit Testing of Cinch remains largely unchanged from Cinch V1 to Cinch V2. As such, the initial Cinch V1 Unit Testing article is still totally accurate, and you can read more about this using the following link: CinchV.aspx.

As for the additional IStatusAware service that was not part of Cinch V1, I have provided a test double called TestViewAwareStatus within Cinch V2 which should be easy enough to figure out how to use. Just examine the names of the various test methods such as SimulateViewIsLoadedEvent(), which will simulate a Loaded event from an actual View.

Code Generator

Cinch V1 provides a code generator that aids in the development of Cinch ViewModels. It produces two partial parts to a partial class. More details can be found on the Cinch code generator using this link: CinchCodeGen.aspx.

The code produced by the generator is perfectly valid for Cinch V1 and Cinch V2.

The article for the code generator is quite interesting I think, as it uses Compiler Services/CodeDOM to make sure the generated code actually works. It even won two awards, so it is worth a gander I think.

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 Cinch V2 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

 
GeneralMy vote of 5 Pin
SuperJMN-CandyBeat8-Feb-13 14:24
SuperJMN-CandyBeat8-Feb-13 14:24 
GeneralRe: My vote of 5 Pin
Sacha Barber16-Feb-13 8:40
Sacha Barber16-Feb-13 8:40 
QuestionHow to Dispose UserControl Pin
WSSPagina2-Dec-12 16:25
WSSPagina2-Dec-12 16:25 
AnswerRe: How to Dispose UserControl Pin
Sacha Barber2-Dec-12 20:05
Sacha Barber2-Dec-12 20:05 
AnswerRe: How to Dispose UserControl Pin
Sacha Barber2-Dec-12 20:11
Sacha Barber2-Dec-12 20:11 
GeneralRe: How to Dispose UserControl Pin
WSSPagina3-Dec-12 19:49
WSSPagina3-Dec-12 19:49 
QuestionEditableValidatingObject.CancelEdit and NPC Pin
ChrDressler30-Aug-11 0:02
ChrDressler30-Aug-11 0:02 
AnswerRe: EditableValidatingObject.CancelEdit and NPC Pin
Sacha Barber6-Sep-11 6:07
Sacha Barber6-Sep-11 6:07 
AnswerRe: EditableValidatingObject.CancelEdit and NPC Pin
Sacha Barber7-Sep-11 10:05
Sacha Barber7-Sep-11 10:05 
AnswerRe: EditableValidatingObject.CancelEdit and NPC Pin
ChrDressler9-Sep-11 9:29
ChrDressler9-Sep-11 9:29 
GeneralMy vote of 5 Pin
acryll13-Dec-10 22:50
acryll13-Dec-10 22:50 
GeneralLocating a view Pin
Germghost10-Dec-10 1:14
Germghost10-Dec-10 1:14 
GeneralRe: Locating a view [modified] Pin
Sacha Barber10-Dec-10 2:48
Sacha Barber10-Dec-10 2:48 
GeneralRe: Locating a view Pin
Germghost10-Dec-10 2:53
Germghost10-Dec-10 2:53 
GeneralRe: Locating a view Pin
Sacha Barber10-Dec-10 3:06
Sacha Barber10-Dec-10 3:06 
GeneralRe: Locating a view Pin
Germghost10-Dec-10 3:29
Germghost10-Dec-10 3:29 
GeneralRe: Locating a view Pin
Germghost10-Dec-10 3:49
Germghost10-Dec-10 3:49 
GeneralRe: Locating a view Pin
Sacha Barber10-Dec-10 3:58
Sacha Barber10-Dec-10 3:58 
GeneralRe: Locating a view Pin
Germghost10-Dec-10 4:01
Germghost10-Dec-10 4:01 
GeneralRe: Locating a view Pin
Sacha Barber10-Dec-10 4:03
Sacha Barber10-Dec-10 4:03 
GeneralClosing View from View Model Pin
sagoo331-Dec-10 12:39
sagoo331-Dec-10 12:39 
GeneralRe: Closing View from View Model Pin
Sacha Barber1-Dec-10 20:10
Sacha Barber1-Dec-10 20:10 
GeneralRe: Closing View from View Model Pin
sagoo332-Dec-10 8:07
sagoo332-Dec-10 8:07 
GeneralRe: Closing View from View Model [modified] Pin
Sacha Barber2-Dec-10 19:04
Sacha Barber2-Dec-10 19:04 
GeneralRe: Closing View from View Model Pin
sagoo335-Dec-10 22:58
sagoo335-Dec-10 22:58 

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.