![]() |
Platforms, Frameworks & Libraries »
Windows Presentation Foundation »
General
Intermediate
License: The Code Project Open License (CPOL)
WPF : If Heineken did MVVM Frameworks Part 2 of nBy Sacha BarberIt would probably be like Cinch a MVVM framework for WPF |
C# (C# 3.0, C# 4.0), .NET (.NET 3.5, .NET 4.0), WPF, Architect, Dev, Design
|
||||||||||||
|
Advanced Search Add to IE Search |
|
|
||||||||||||||||||
Last time I simply introduced the Cinch the framework article, and this time I stated we would do a walkthrough of Cinch, and it's internals, this is going to be a large article, so it has actually been split over 2 articles. In this article I will be covering the following:
The demo app makes use of :
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
This section will start the dive into the internals of Cinch. As I say there is far too much for 1 article, so I am splitting the internals of Cinch over 2 articles. Hopefully there will be something of use to you here. Well I hope so anyway.
When developing with the MVVM pattern the holy grail is for there to be no coupling at all between the View and the ViewModel. This clean seperation between View and ViewModel is achieved in Cinch, there is no link between them at all, which is ace. However occasionally it is advantageous for the ViewModel to know something about certain View events, such as :
But we just said that there was no referencing between View/ViewModel and
we would like to know about these View events in the ViewModel, so how do
we achieve that. The answer lies in attached behaviour. What we do is we have
a ICommand based property in the ViewModel and we use an attached
View lifecycle behaviour and attach the correct View event to the correct
ViewModel ICommand. So when the View event is raised it will
actually call a ICommand implementation in the ViewModel.
Cinch provides for this using 2 things:
ViewModelBase,
which already contains all the View lifecylce ICommand implementation
you will need, which I should point out can be overriden is inheritors of
the Cinch ViewModelBase class Let us examine one View lifecycle event, Activated, to see how it works, the others are the same.
Starting with the ViewModelBase class we can see from the code
below (extra code removed for clarity) that there is a ICommand called Activated
where the trimmed Cinch ViewModelBase looks
like this
namespace Cinch
{
/// <summary>
/// Provides a base class for ViewModels to inherit from. This
/// base class provides the following
/// <list type="Bullet">
/// <item>Mediator pattern implementation</item>
/// <item>Service resolution</item>
/// <item>Window lifetime virtual method hooks</item>
/// <item>INotifyPropertyChanged</item>
/// </list>
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
{
private SimpleCommand activatedCommand;
/// <summary>
/// Constructs a new ViewModelBase and wires up all the Window based Lifetime
/// commands such as activatedCommand
/// deactivatedCommand/loadedCommand/closeCommand
/// </summary>
public ViewModelBase()
{
activatedCommand = new SimpleCommand
{
CanExecuteDelegate = x => true,
ExecuteDelegate = x => OnWindowActivated()
};
}
/// <summary>
/// Allows Window.Activated hook
/// </summary>
protected virtual void OnWindowActivated()
{
//Should be overriden if required in inheritors
}
/// <summary>
/// ActivatedCommand : Window Lifetime command
/// </summary>
public SimpleCommand ActivatedCommand
{
get { return activatedCommand; }
}
}
}
The eagle eyed amongst you may notice that there is a property called ActivatedCommand
within the code above, which actually returns a SimpleCommand
and not a ICommand that you may have been expecting and maybe
even seen in various other posts. This is all cool don't worry, the XAML parser
and binding system are clever enough to know that any class that implements
ICommand can be used in place of a ICommand in a
binding. The reason to expose the ICommand as a SimpleCommand
is that the SimpleCommand has an extra property that can be used
from inside Unit tests. Namely the CommandSucceeded which is
set true on Command execution completion, so can be used in Unit test Assert
statements.
Here is the SimpleCommand code
using System;
using System.Windows.Input;
/// <summary>
/// This class provides a simple
/// delegate command that implements the ICommand
/// interface
/// </summary>
namespace Cinch
{
/// <summary>
/// Implements the ICommand and wraps up all the verbose stuff so that you
/// can just pass 2 delegates 1 for the CanExecute and one for the Execute
/// </summary>
public class SimpleCommand : ICommand
{
#region Public Properties
public Boolean CommandSucceeded { get; set; }
/// <summary>
/// Gets or sets the Predicate to execute when the
/// CanExecute of the command gets called
/// </summary>
public Predicate<object> CanExecuteDelegate { get; set; }
/// <summary>
/// Gets or sets the action to be called when the
/// Execute method of the command gets called
/// </summary>
public Action<object> ExecuteDelegate { get; set; }
#endregion
#region ICommand Members
/// <summary>
/// Checks if the command Execute method can run
/// </summary>
/// <param name="parameter">THe command parameter to be passed</param>
/// <returns>Returns true if the command can execute.
/// By default true is returned so that if the user of SimpleCommand
/// does not specify a CanExecuteCommand delegate the command
/// still executes.</returns>
public bool CanExecute(object parameter)
{
if (CanExecuteDelegate != null)
return CanExecuteDelegate(parameter);
return true;// if there is no can execute default to true
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// Executes the actual command
/// </summary>
/// <param name="parameter">THe command parameter to be passed</param>
public void Execute(object parameter)
{
if (ExecuteDelegate != null)
{
ExecuteDelegate(parameter);
CommandSucceeded = true;
}
}
#endregion
}
}
Ok so we now have a ViewModel that has a SimpleCommand for Activated
that will call a protected virtual void OnWindowActivated() method,
but how does this Activated SimpleCommand, get executed, well
recall I said there was some attached behaviour magic so lets look at that
next.
Firstly this is how the View would wire up the View Activated attached behavior
<Window x:Class="MVVM.Demo.Views.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Cinch="clr-namespace:Cinch;assembly=Cinch"
Title="Window1" Height="300" Width="300">
Cinch:LifetimeEvent.Activated="{Binding ActivatedCommand}"
....
....
</Window>
So this will then use the attached View lifecyle event, which is shown below.
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
/// <summary>
/// This allows a Windows lifecycle events to call ICommand(s)
/// within a ViewModel. This allows the ViewModel to know something
/// about the Views lifecycle without the need for a strong link
/// to the actual View
/// </summary>
namespace Cinch
{
/// <summary>
/// This class is used to attach the Window lifetime events to ICommand implementations.
/// It allows a ViewModel to hook into the lifetime of the view (when necessary)
/// through simple XAML tags. Supported events are Loaded, Activated, Deactivated
/// and Closing/Closed. For the Closing/Closed event, the CanExecute handler is invoked
/// in response to the Closing event - if it returns true, then the Closed event is
/// allowed and the Execute handler is called in response.
public static class LifetimeEvent
{
#region Activated
/// <summary>
/// Dependency property which holds the ICommand for the Activated event
/// </summary>
public static readonly DependencyProperty ActivatedProperty =
DependencyProperty.RegisterAttached("Activated",
typeof(ICommand), typeof(LifetimeEvent),
new UIPropertyMetadata(null, OnActivatedEventInfoChanged));
/// <summary>
/// Attached Property getter to retrieve the ICommand
/// </summary>
/// <param name="source">Dependency Object</param>
/// <returns>ICommand</returns>
public static ICommand GetActivated(DependencyObject source)
{
return (ICommand)source.GetValue(ActivatedProperty);
}
/// <summary>
/// Attached Property setter to change the ICommand
/// </summary>
/// <param name="source">Dependency Object</param>
/// <param name="command">ICommand</param>
public static void SetActivated(DependencyObject source, ICommand command)
{
source.SetValue(ActivatedProperty, command);
}
/// <summary>
/// This is the property changed handler for the Activated property.
/// </summary>
/// <param name="sender">Dependency Object</param>
/// <param name="e">EventArgs</param>
private static void OnActivatedEventInfoChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
var win = sender as Window;
if (win != null)
{
win.Activated -= OnWindowActivated;
if (e.NewValue != null)
win.Activated += OnWindowActivated;
}
}
/// <summary>
/// This is the handler for the Activated event to raise the command.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void OnWindowActivated(object sender, EventArgs e)
{
var dpo = (DependencyObject)sender;
ICommand activatedCommand = GetActivated(dpo);
if (activatedCommand != null)
activatedCommand.Execute(GetCommandParameter(dpo));
}
#endregion
}
}
The same mechanism is used for
Deactivated event, where the SimpleCommand
in the Cinch ViewModelBase is called DeactivatedCommand,
and the virtual method in the Cinch ViewModelBase
is called OnWindowDeactivated() Closing/Closed event, where the SimpleCommand
in the Cinch ViewModelBase is called Closeommand,
and the virtual method in the Cinch ViewModelBase
is called OnWindowClose() SimpleCommand in the Cinch
ViewModelBase is called LoadedCommand, and the
virtual method in the Cinch ViewModelBase
is called OnWindowLoaded() Again don't forget there is no standard implementation provided by the Cinch
ViewModelBase as this functionality should be performed by overriding
these method in a ViewModel which inherits from the Cinch
ViewModelBase.
So ALL you have to do is inherit from Cinch ViewModelBase,
and you too can use these View lifecyle events.
One very common requirement when developing LOb applications is to have numeric
only data entry on textboxes. Whilst this is possible to achieve using regular
expressions and some code behind and possibly a ValidationRule, wouldn't it
just be easier to not let the textbox accept anything apart from numbers in
the 1st place. To this end Cinch contains a NumericTextBoxBehavior
attached behaviour which looks like this. You should also note that this behaviour
caters for pasted text using the DataObject pasting event.
/// <summary>
/// This forces a TextBoxBase control to be numeric-entry only
/// By using an attached behaviour
/// </summary>
namespace Cinch
{
/// <summary>
/// This forces a TextBoxBase control to be numeric-entry only
/// </summary>
/// <example>
/// <![CDATA[ <TextBox Cinch:NumericTextBoxBehavior.IsEnabled="True" /> ]]>
/// </example>
public static class NumericTextBoxBehavior
{
#region IsEnabled DP
/// <summary>
/// Dependency Property for turning on numeric behavior in a TextBox.
/// </summary>
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool), typeof(NumericTextBoxBehavior),
new UIPropertyMetadata(false, OnEnabledStateChanged));
/// <summary>
/// Attached Property getter for the IsEnabled property.
/// </summary>
/// <param name="source">Dependency Object</param>
/// <returns>Current property value</returns>
public static bool GetIsEnabled(DependencyObject source)
{
return (bool)source.GetValue(IsEnabledProperty);
}
/// <summary>
/// Attached Property setter for the IsEnabled property.
/// </summary>
/// <param name="source">Dependency Object</param>
/// <param name="value">Value to set on the object</param>
public static void SetIsEnabled(DependencyObject source, bool value)
{
source.SetValue(IsEnabledProperty, value);
}
/// <summary>
/// This is the property changed handler for the IsEnabled property.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void OnEnabledStateChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
TextBoxBase tbb = sender as TextBoxBase;
if (tbb == null)
return;
tbb.PreviewKeyDown -= OnKeyDown;
DataObject.RemovePastingHandler(tbb, OnClipboardPaste);
bool b = ((e.NewValue != null && e.NewValue.GetType() == typeof(bool))) ?
(bool)e.NewValue : false;
if (b)
{
tbb.PreviewKeyDown += OnKeyDown;
DataObject.AddPastingHandler(tbb, OnClipboardPaste);
}
}
#endregion
#region Private Methods
/// <summary>
/// This method handles paste and drag/drop events onto the TextBox. It restricts the character
/// set to numerics and ensures we have consistent behavior.
/// </summary>
/// <param name="sender">TextBox sender</param>
/// <param name="e">EventArgs</param>
private static void OnClipboardPaste(object sender, DataObjectPastingEventArgs e)
{
string text = e.SourceDataObject.GetData(e.FormatToApply) as string;
if (!string.IsNullOrEmpty(text))
{
if (text.Count(ch => !Char.IsNumber(ch)) == 0)
return;
}
e.CancelCommand();
}
/// <summary>
/// This checks the PreviewKeyDown on the TextBox and
/// constrains it to a numeric value.
/// </summary>
private static void OnKeyDown(object sender, KeyEventArgs e)
{
if ((e.Key >= Key.D0 && e.Key <= Key.D9) ||
e.Key == Key.Back || e.Key == Key.Delete ||
e.Key == Key.Left || e.Key == Key.Right || e.Key == Key.Tab ||
(e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9))
return;
e.Handled = true;
}
#endregion
}
}
Which can easily be applied to a textbox in your application as follows:
<Window x:Class="MVVM.Demo.Views.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Cinch="clr-namespace:Cinch;assembly=Cinch"
Title="Window1" Height="300" Width="300">
<Grid>
<TextBox Cinch:NumericTextBoxBehavior.IsEnabled="True" />
</Grid>
</Window>
With this attached behaviour enabled only numeric data entry will be allowed for the textbox that has the NumericTextBoxBehavior attached behaviour applied.
Continuing with the attached behaviors it is also sometimes very convenient
to be able to fire a ViewModel ICommand from a FrameworkElement
RoutedEvent. This is something that the WPF framework does not do out of the
box, though Blend 3 does allow this by the use of Blend Interactivity dll.
If you want an example of that have a look at my blog post WPF
: Blend 3 Interactions / Behaviours
But we are where we are, and I want people to know how to do this without using a non released Dll from a different product that will more than likely find its way into WPF proper at some stage. So lets continue with how to do it without the Blend Interactivity dll.
So here is how, Cinch actually provides 2 alternatives here,
attaching a single ICommand to a single FrameworkElement RoutedEvent,
or attaching a collection of ICommands to a FrameworkElement
using different RoutedEvents.
Lets start simple, and build up from there.
This is easily done via yet another attached property or 2, which you can set on a View FrameworkElement like this.
<Window x:Class="MVVM.Demo.Views.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Cinch="clr-namespace:Cinch;assembly=Cinch"
Title="Window1" Height="300" Width="300">
<Grid Background="WhiteSmoke"
Cinch:SingleEventCommand.RoutedEventName="MouseDown"
Cinch:SingleEventCommand.TheCommandToRun=
"{Binding Path=ShowWindowCommand}"/>
</Window>
So we set the RoutedEvent to fire the ICommand and we set the
ViewModel ICommand to execute via a Binding.
So all that is left is to look at how this is achieved, which is done with
a standard DP or 2 and a touch of Reflection to get the RoutedEvent from the
DependencyObject that declares the RoutedEventName
attached DP, and from there we just create a Delegate which is
called when the event is raised, which in turn fires the ICommand
that was specified using the TheCommandToRun DP.
Here is the code.
public static class SingleEventCommand
{
#region TheCommandToRun
/// <summary>
/// TheCommandToRun : The actual ICommand to run
/// </summary>
public static readonly DependencyProperty TheCommandToRunProperty =
DependencyProperty.RegisterAttached("TheCommandToRun",
typeof(ICommand),
typeof(SingleEventCommand),
new FrameworkPropertyMetadata((ICommand)null));
/// <summary>
/// Gets the TheCommandToRun property.
/// </summary>
public static ICommand GetTheCommandToRun(DependencyObject d)
{
return (ICommand)d.GetValue(TheCommandToRunProperty);
}
/// <summary>
/// Sets the TheCommandToRun property.
/// </summary>
public static void SetTheCommandToRun(DependencyObject d, ICommand value)
{
d.SetValue(TheCommandToRunProperty, value);
}
#endregion
#region RoutedEventName
/// <summary>
/// RoutedEventName : The event that should actually execute the
/// ICommand
/// </summary>
public static readonly DependencyProperty RoutedEventNameProperty =
DependencyProperty.RegisterAttached("RoutedEventName", typeof(String),
typeof(SingleEventCommand),
new FrameworkPropertyMetadata((String)String.Empty,
new PropertyChangedCallback(OnRoutedEventNameChanged)));
/// <summary>
/// Gets the RoutedEventName property.
/// </summary>
public static String GetRoutedEventName(DependencyObject d)
{
return (String)d.GetValue(RoutedEventNameProperty);
}
/// <summary>
/// Sets the RoutedEventName property.
/// </summary>
public static void SetRoutedEventName(DependencyObject d, String value)
{
d.SetValue(RoutedEventNameProperty, value);
}
/// <summary>
/// Hooks up a Dynamically created EventHandler (by using the
/// <see cref="EventHooker">EventHooker</see> class) that when
/// run will run the associated ICommand
/// </summary>
private static void OnRoutedEventNameChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
String routedEvent = (String)e.NewValue;
if (d == null || String.IsNullOrEmpty(routedEvent))
return;
//If the RoutedEvent string is not null, create a new
//dynamically created EventHandler that when run will execute
//the actual bound ICommand instance (usually in the ViewModel)
EventHooker eventHooker = new EventHooker();
eventHooker.ObjectWithAttachedCommand = d;
EventInfo eventInfo = d.GetType().GetEvent(routedEvent,
BindingFlags.Public | BindingFlags.Instance);
//Hook up Dynamically created event handler
if (eventInfo != null)
{
eventInfo.RemoveEventHandler(d,
eventHooker.GetNewEventHandlerToRunCommand(eventInfo));
eventInfo.AddEventHandler(d,
eventHooker.GetNewEventHandlerToRunCommand(eventInfo));
}
}
#endregion
}
/// <summary>
/// Contains the event that is hooked into the source RoutedEvent
/// that was specified to run the ICommand
/// </summary>
sealed class EventHooker
{
#region Public Methods/Properties
/// <summary>
/// The DependencyObject, that holds a binding to the actual
/// ICommand to execute
/// </summary>
public DependencyObject ObjectWithAttachedCommand { get; set; }
/// <summary>
/// Creates a Dynamic EventHandler that will be run the ICommand
/// when the user specified RoutedEvent fires
/// </summary>
/// <param name="eventInfo">The specified RoutedEvent EventInfo</param>
/// <returns>An Delegate that points to a new EventHandler
/// that will be run the ICommand</returns>
public Delegate GetNewEventHandlerToRunCommand(EventInfo eventInfo)
{
Delegate del = null;
if (eventInfo == null)
throw new ArgumentNullException("eventInfo");
if (eventInfo.EventHandlerType == null)
throw new ArgumentException("EventHandlerType is null");
if (del == null)
del = Delegate.CreateDelegate(eventInfo.EventHandlerType, this,
GetType().GetMethod("OnEventRaised",
BindingFlags.NonPublic |
BindingFlags.Instance));
return del;
}
#endregion
#region Private Methods
/// <summary>
/// Runs the ICommand when the requested RoutedEvent fires
/// </summary>
private void OnEventRaised(object sender, EventArgs e)
{
ICommand command = (ICommand)(sender as DependencyObject).
GetValue(SingleEventCommand.TheCommandToRunProperty);
if (command != null)
{
command.Execute(null);
}
}
#endregion
}
So this allows a single FrameworkElement RoutedEvent to fire a single ViewModel
ICommand, but Cinch does more than that. So
lets look at the more advanced scenario.
As before let us see how you would use the attached collection of ICommands/RoutedEvents from a View. Here is how you might do that.
<Window x:Class="MVVM.Demo.Views.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Cinch="clr-namespace:Cinch;assembly=Cinch"
Title="Window1" Height="300" Width="300">
<Grid Background="WhiteSmoke">
<Behaviors:EventCommander.Mappings>
<Behaviors:CommandEvent
Command="{Binding MouseEnterCommand}"
Event="MouseEnter" />
<Behaviors:CommandEvent
Command="{Binding MouseLeaveCommand}"
Event="MouseLeave" />
</Behaviors:EventCommander.Mappings>
</Grid>
</Window>
So you can see that this time, there is an attached property (EventCommander.Mappings)
that is expecting a collection of CommandEvent objects. The concept
behind this is largely the same, except this time there is a collection of
these CommandEvent objects, but they largely work the same as
just described above, so I will concentrate on how the EventCommander.Mappings
collection works, and assume the discussion above was enough to explain a
single CommandEvent object.
The EventCommander.Mappings collection, looks like this:
public static class EventCommander
{
#region InternalMappings DP
// Make it internal so WPF ignores the property and always uses the
//public getter/setter. This is per John Gossman blog post - 07/2008.
internal static readonly DependencyProperty MappingsProperty =
DependencyProperty.RegisterAttached("InternalMappings",
typeof(CommandEventCollection), typeof(EventCommander),
new UIPropertyMetadata(null, OnMappingsChanged));
/// <summary>
/// Retrieves the mapping collection
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
internal static CommandEventCollection InternalGetMappingCollection(
DependencyObject obj)
{
var map = obj.GetValue(MappingsProperty) as CommandEventCollection;
if (map == null)
{
map = new CommandEventCollection();
SetMappings(obj, map);
}
return map;
}
/// <summary>
/// This retrieves the mapping collection
/// </summary>
/// <param name="obj">Dependency Object</param>
/// <returns>Mapping collection</returns>
public static IList GetMappings(DependencyObject obj)
{
return InternalGetMappingCollection(obj);
}
/// <summary>
/// This sets the mapping collection.
/// </summary>
/// <param name="obj">Dependency Object</param>
/// <param name="value">Mapping collection</param>
public static void SetMappings(DependencyObject obj,
CommandEventCollection value)
{
obj.SetValue(MappingsProperty, value);
}
/// <summary>
/// This changes the event mapping
/// </summary>
/// <param name="target"></param>
/// <param name="e"></param>
private static void OnMappingsChanged(DependencyObject target,
DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null)
{
CommandEventCollection cec = e.OldValue as CommandEventCollection;
if (cec != null)
cec.Unsubscribe(target);
}
if (e.NewValue != null)
{
CommandEventCollection cec = e.NewValue as CommandEventCollection;
if (cec != null)
cec.Subscribe(target);
}
}
#endregion
}
Where it can be seen that behind the scenes the EventCommander.Mappings
collection is populating a CommandEventCollection. The CommandEventCollection
inherits from Freezable, as this is a trick you can use to get DataContext
inheritence. Basically what happens is that by inheriting from Freezable
a non UI element will also get the current UI elements DataContext
(which is more that likely the ViewModel). If you did not inherit from Freezable,
the CommandEventCollection would not be able to pick up the ViewModels
bound ICommand, as it would know nothing about a current DataContext
object, it would be null. It's a trick, but it works. If you want to know
more read Mike Hillbergs blog entry Mike
Hillbergs Freezable blog post and maybe have a look at Josh Smiths DataContextSpy
post, which is also very useful.
Anyway the which CommandEventCollection looks like this, where
its main job is to maintain the list of current CommandEvents.
public class CommandEventCollection : FreezableCollection<CommandEvent>
{
#region Data
private object _target;
private readonly List<CommandEvent> _currentList = new List<CommandEvent>();
#endregion
#region Ctor
/// <summary>
/// Constructor
/// </summary>
public CommandEventCollection()
{
((INotifyCollectionChanged)this).CollectionChanged += OnCollectionChanged;
}
#endregion
#region Private/Internal Methods
/// <summary>
/// Wire up events to the target
/// </summary>
/// <param name="target"></param>
internal void Subscribe(object target)
{
_target = target;
foreach(var item in this)
item.Subscribe(target);
}
/// <summary>
/// Unwire all target events
/// </summary>
/// <param name="target"></param>
internal void Unsubscribe(object target)
{
foreach (var item in this)
item.Unsubscribe(target);
_target = null;
}
/// <summary>
/// This handles the collection change event - it then subscribes and unsubscribes events.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var item in e.NewItems)
OnItemAdded((CommandEvent)item);
break;
case NotifyCollectionChangedAction.Remove:
foreach (var item in e.OldItems)
OnItemRemoved((CommandEvent)item);
break;
case NotifyCollectionChangedAction.Replace:
foreach (var item in e.OldItems)
OnItemRemoved((CommandEvent)item);
foreach (var item in e.NewItems)
OnItemAdded((CommandEvent)item);
break;
case NotifyCollectionChangedAction.Move:
break;
case NotifyCollectionChangedAction.Reset:
_currentList.ForEach(i => i.Unsubscribe(_target));
_currentList.Clear();
foreach (var item in this)
OnItemAdded(item);
break;
default:
return;
}
}
/// <summary>
/// A new item has been added to the event list
/// </summary>
/// <param name="item"></param>
private void OnItemAdded(CommandEvent item)
{
if (item != null && _target != null)
{
_currentList.Add(item);
item.Subscribe(_target);
}
}
/// <summary>
/// An item has been removed from the event list.
/// </summary>
/// <param name="item"></param>
private void OnItemRemoved(CommandEvent item)
{
if (item != null && _target != null)
{
_currentList.Remove(item);
item.Unsubscribe(_target);
}
}
#endregion
}
In case any of you are wondering how to obtain and use the RoutedEventArgs
and pass them as the ICommand parameter to the ViewModel where
the Command is, Marlon Grech (fellow WPF Disciple and all round great chap),
has a lovely post which is at this url :http://marlongrech.wordpress.com/2009/07/03/how-to-get-the-eventargs-as-a-commandparameter-using-the-attachedcommandbehaviour/
When working with WPF it is very common to have your ViewModel/Model classes
implement the System.ComponentModel.INotifyPropertyChanged interface
for binding notification of property changes. This is typically implemented
like this:
using System.ComponentModel;
namespace SDKSample
{
// This class implements INotifyPropertyChanged
// to support one-way and two-way bindings
// (such that the UI element updates when the source
// has been changed dynamically)
public class Person : INotifyPropertyChanged
{
private string name;
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
public Person()
{
}
public Person(string value)
{
this.name = value;
}
public string PersonName
{
get { return name; }
set
{
name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("PersonName");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
The problem with this approach is that the code has magic string in it which
are easy to get wrong. Have a look at the OnPropertyChanged("PersonName");
code shown above. I had originally being using some static reflection by using
LINQ expression trees to obtain the property name, I was using Bill Kempfs
excellent Reflect code. The problem with the static reflection Expression
tree thing is that it is slow to create the eventArgs each time. One of the
readers of this article actually came up with a better solutuion where the
INPC EventArgs are created once and then re-used. I liked this idea a lot,
so have now included it in Cinch. The readers post is available
here if you want some more information abouts this
CinchII.aspx?msg=3141144#xx3141144xx
Cinch now uses this approach. Here is how it works. there is a static helper class which looks like this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
namespace Cinch
{
/// <summary>
/// A small helper class that has a method to help create
/// PropertyChangedEventArgs when using the INotifyPropertyChanged
/// interface
/// </summary>
public static class ObservableHelper
{
#region Public Methods
/// <summary>
/// Creates PropertyChangedEventArgs
/// </summary>
/// <param name="propertyExpression">Expression to make
/// PropertyChangedEventArgs out of</param>
/// <returns>PropertyChangedEventArgs</returns>
public static PropertyChangedEventArgs CreateArgs<T>(
Expression<Func<T, Object>> propertyExpression)
{
return new PropertyChangedEventArgs(
GetPropertyName<T>(propertyExpression));
}
/// <summary>
/// Creates PropertyChangedEventArgs
/// </summary>
/// <param name="propertyExpression">Expression to make
/// PropertyChangedEventArgs out of</param>
/// <returns>PropertyChangedEventArgs</returns>
public static string GetPropertyName<T>(
Expression<Func<T, Object>> propertyExpression)
{
var lambda = propertyExpression as LambdaExpression;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = lambda.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else
{
memberExpression = lambda.Body as MemberExpression;
}
var propertyInfo = memberExpression.Member as PropertyInfo;
return propertyInfo.Name;
}
#endregion
}
}
Which we can then use like this from any property
static PropertyChangedEventArgs currentCustomerChangeArgs =
ObservableHelper.CreateArgs<AddEditOrderViewModel>(x => x.CurrentCustomer);
public CustomerModel CurrentCustomer
{
get { return currentCustomer; }
set
{
currentCustomer = value;
NotifyPropertyChanged(currentCustomerChangeArgs);
}
}
No more magic strings, at all. Most excellent.
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 individual field level.
So this got me thinking, what we need is a editable state for each data item in a UI Model. I thought about this some more and came up with a generic wrapper class which wraps a single property but also exposes a IsEditable property. Now the ViewModel can access these wrappers as they are public properties on either a UI Model classes or actually public properties in the ViewModel (I expose a CurrentX object of my ViewModel, but others repeat all the UI Model properties in the ViewModel. Me I have no issues with writing straight from the View to the Model, providing it doesn't get to the database if its InValid), so it can bind its data to the wrappers data property and can disable data entry based on the wrappers IsEditable property.
To this end I came up with a simple class that looks like this.
/// <summary>
/// Abstract base class for DataWrapper - allows easier access to
/// methods for the DataWrapperHelper.
/// </summary>
public abstract class DataWrapperBase : EditableValidatingObject
{
#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>
/// 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>
{
#region Data
private T dataValue = default(T);
#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();
}
}
#endregion

Which can then be used as properties on your UI Model classes (or directly
on the ViewModel if you want to), like this, (don't worry about the inheriting
from Cinch.EditableValidatingObject, we'll get to that soon)
public class OrderModel : Cinch.EditableValidatingObject
{
//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 OrderModel()
{
Quantity = new DataWrapper(this, quantityChangeArgs);
....
....
//Setup rules etc etc
}
static PropertyChangedEventArgs quantityChangeArgs =
ObservableHelper.CreateArgs<OrderModel>(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
object are immutable, and are only allowed to be set in the constructor. The
IsEditable and the DataValue can be changed whenever
you like though. The other thing to note is that the Model/ViewModel actually
uses some reflection on construction to obtain a 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 condtrcutor we have something like this:
using System;
using Cinch;
using MVVM.DataAccess;
using System.ComponentModel;
using System.Collections.Generic;
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;
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 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
}
#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:
<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}"/>
So that's all cool, but how do these DataWrapper<T> objects
respond to a change in mode state. Well that is quite simply 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 is is we are trying to change the state for (for
me this is always a UI Model, for others this may 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.
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.WorkspaceViewModel
{
private ViewMode currentViewMode = ViewMode.AddMode;
private OrderModel currentCustomerOrder;
public AddEditOrderViewModel()
{
}
/// <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:
CurrentCustomerOrder = new OrderModel();
this.DisplayName = "Add Order";
break;
case ViewMode.EditMode:
CurrentCustomerOrder.BeginEdit();
this.DisplayName = "Edit Order";
break;
case ViewMode.ViewOnlyMode:
this.DisplayName = "View Order";
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);
NotifyPropertyChanged(currentViewModeChangeArgs);
}
}
/// <summary>
/// Current Customer OrderModel
/// </summary>
static PropertyChangedEventArgs currentCustomerOrderChangeArgs =
ObservableHelper.CreateArgs<AddEditOrderViewModel>(x => x.CurrentCustomerOrder);
public OrderModel CurrentCustomerOrder
{
get { return currentCustomerOrder; }
set
{
currentCustomerOrder = value;
if (currentCustomerOrder != null)
{
if (currentCustomerOrder.ProductId.DataValue > 0)
{
ProductModel prod = this.Products.Where(p => p.ProductId ==
currentCustomerOrder.ProductId.DataValue).Single();
productsCV.MoveCurrentTo(prod);
}
}
NotifyPropertyChanged(currentCustomerOrderChangeArgs);
}
}
....
....
....
}
}
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.
// 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");
}
}
}
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 pauls great idea to use delegates to provide validation for business objects.
The idea is simply the business objects has 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 excellent Delegates and Business Objects article 1st, but basically Cinch makes use of this.
What Cinch provides is
Here is an example of how to use these with Cinch where the property is a simple type such as String/Int32 etc etc
public class OrderModel : Cinch.ValidatingObject
{
public OrderModel()
{
//setup DataWrappers prior to setting up rules
....
....
//setup rules
quantity.AddRule(new SimpleRule("SomeProperty", "Quantity can not be empty",
delegate
{
return this.Quantity.DataValue <= 0;
}));
}
}
However recall I mentioned a special Cinch class to allow the ViewModel to
place a single Model field into edit mode using the Cinch.DataWrapper<T>
, well we need to do something a little different for those, we need to do
the following:
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> customerId;
#endregion
#region Ctor
public OrderModel()
{
//setup DataWrappers prior to setting up rules
....
....
#region Create Validation Rules
quantity.AddRule(new SimpleRule("DataValue", "Quantity can not be empty",
delegate
{
return this.Quantity.DataValue <= 0;
}));
#endregion
}
#endregion
#region Public Properties
static PropertyChangedEventArgs quantityChangeArgs =
ObservableHelper.CreateArgs<OrderModel>(x => x.Quantity);
public Cinch.DataWrapper<Int32> Quantity
{
get { return quantity; }
private set
{
quantity = value;
NotifyPropertyChanged(quantityChangeArgs);
}
}
#endregion
}
We need to declare the validtion 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.ValidatingObject object. Lets say
you have something like this for a UI Model object.
using System;
using Cinch;
using MVVM.DataAccess;
namespace MVVM.Models
{
/// <summary>
/// This class 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;
private Cinch.DataWrapper<Int32> customerId;
private Cinch.DataWrapper<Int32> quantity;
private Cinch.DataWrapper<Int32> productId;
private Cinch.DataWrapper<DateTime> deliveryDate;
private IEnumerable<DataWrapperBase> cachedListOfDataWrappers;
#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(new SimpleRule("DataValue", "Quantity can not be empty",
delegate
{
return this.Quantity.DataValue <= 0;
}));
#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;
}
#endregion
#region Public Properties
/// <summary>
/// OrderId
/// </summary>
static PropertyChangedEventArgs orderIdChangeArgs =
ObservableHelper.CreateArgs<OrderModel>(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<OrderModel>(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<OrderModel>(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<OrderModel>(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<OrderModel>(x => x.DeliveryDate);
public Cinch.DataWrapper<DateTime> DeliveryDate
{
get { return deliveryDate; }
private set
{
deliveryDate = value;
NotifyPropertyChanged(deliveryDateChangeArgs);
}
}
#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> objects, which
is very easy as they also inherit from Cinch.EditableValidatingObject
which in turn inherits from Cinch.ValidatingObject, so they already
have the IDataErrorInfo implementation, so its not that hard
to cope with.
I know this seems a lot of extra work, but the added benifit of the ViewModel being able to set a Models individual field editability state, and have the View relect this seemlessly via bindings, simply can not be ignored.
/// <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);
}
}
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.
<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>
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 allowed
for what the storage of an objects state to a Memento backing object which
had the exact same properties, as the business object it was storing state
for. So when you started an edit on a business objecvt, you store the current
state in a Memento and did your edit. If you cancelled the edit, the business
objects state would be restored from the Memento. This does work very well,
but Microsoft also support this via an interface called IEditableObject
which looks like this:
BeginEdit() CancelEdit() EndEdit() So using this interface we can actually make our business object store their own state. Now I can take no credit for this next piece of code, it comes from Mark Smith excellent work. Actually a fair amount of Cinch is down to Mark Smiths 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 objects state in an internal Dictionary. On Canceldit()
the internal Dictionary values are restored to the current objects properties,
using the property name as a key into the stored Dictionary state.
This diagram shows this

Here is the Cinch base class that does all this.
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 EditableValidatingObject :
ValidatingObject, 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.EditableValidatingObject.
Job done.
So lets see how you might put an object that inherits from Cinch.EditableValidatingObject,
into Edit mode.
Well from the ViewModel we can simply do this.CurrentCustomer.BeginEdit()
its that easy. However what you MUST also do if you have any nested Cinch.DataWrapper<T>
objects is make sure they to are put into the correct state. You would do
this in your UI Model class as follows, where we simple override the
protected virtual void OnBeginEdit() we get from inheriting from Cinch.EditableValidatingObject
Where we may have a UI Model object that looks like
using System;
using Cinch;
using MVVM.DataAccess;
namespace MVVM.Models
{
/// <summary>
/// This class 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;
private Cinch.DataWrapper<Int32> customerId;
private Cinch.DataWrapper<Int32> quantity;
private Cinch.DataWrapper<Int32> productId;
private Cinch.DataWrapper<DateTime> deliveryDate;
private IEnumerable<DataWrapperBase> cachedListOfDataWrappers;
#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(new SimpleRule("DataValue", "Quantity can not be empty",
delegate
{
return this.Quantity.DataValue <= 0;
}));
#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;
}
#endregion
#region Public Properties
/// <summary>
/// OrderId
/// </summary>
static PropertyChangedEventArgs orderIdChangeArgs =
ObservableHelper.CreateArgs<OrderModel>(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<OrderModel>(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<OrderModel>(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<OrderModel>(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<OrderModel>(x => x.DeliveryDate);
public Cinch.DataWrapper<DateTime> DeliveryDate
{
get { return deliveryDate; }
private set
{
deliveryDate = value;
NotifyPropertyChanged(deliveryDateChangeArgs);
}
}
#endregion
}
}
So what we need to do is override the Cinch.EditableValidatingObject
virtual methods as follows:
#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
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 the 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 Model class. If you are feeling a little lost by all this, do not worry one of the subsequent articles will walk through how to create a UI Model using Cinch. This article is more about this Cinch internals for those that may be interested.
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.

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 mananger / 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.
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 obvioulsy 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 prefferable 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. Daniels 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 Grunwalds WeakEvent<T> we can do
the following:
private readonly WeakEvent<EventHandler<EventArgs>>
dependencyChangedEvent =
new WeakEvent<EventHandler<EventArgs>>();
public event EventHandler<EventArgs> DependencyChanged
{
add { dependencyChangedEvent.Add(value); }
remove { dependencyChangedEvent.Remove(value); }
}
dependencyChangedEvent.Raise(this, new EventArgs());
SourceDependency.DependencyChanged += OnSourceChanged;
...
private void OnSourceChanged(object sender, EventArgs e)
{
}
So 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 3rd party control set. So in that case
you may need to use a WeakEvent subcription. Cinch provides
2 methods of doing this.
So above we saw how to raise a WeakEvent using Daniel Grunwalds WeakEvent<T>,
so how about in the case where we want to subcribe 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 2 methods to do this.
Which is a neat little class which Paul Stovell wrote some time ago. The entire class looks like this:
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:
private EventHandler<NotifyCollectionChangedEventArgs>
collectionChangeHandler;
private WeakEventProxy<NotifyCollectionChangedEventArgs>
weakCollectionChangeListener;
if (weakCollectionChangeListener == null)
{
collectionChangeHandler = OnCollectionChanged;
weakCollectionChangeListener =
new WeakEventProxy<NotifyCollectionChangedEventArgs>(
collectionChangeHandler);
}
ncc.CollectionChanged += weakCollectionChangeListener.Handler;
private void OnCollectionChanged(object sender,
NotifyCollectionChangedEventArgs e)
{
}
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 links 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. Lets have a quick look at the syntax for both these operations
We simply do this.
workspace.CloseWorkSpace +=
new EventHandler<EventArgs>(OnCloseWorkSpace).
MakeWeak(eh => workspace.CloseWorkSpace -= eh);
private void OnCloseWorkSpace(object sender, EventArgs e)
{
}
That one line created 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.
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 its 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.
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 no nothing about each other. However they need to know about certain actions that a user performed. Here is a concrete example.
Say you have 2 Views one with customers and one with orders for a customer. Lets 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 changed.
Sounds simple enough right. However we have 2 independant views run by 2 independant 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 tonne 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

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 exposes as a static property on the ViewModelBase
class) that is waiting for objects to subscribe to it either using :
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 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 instances of WeakActions callback
delegates that point to objects that are no longer alive are removed from
the list of Mediator WeakActions 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.
We simply create the correct type of delegate and Register a callback for
a message notification with the Mediator.
public delegate void DummyDelegate(Boolean dummy);
...
Mediator.Register("AddCustomerMessage", new DummyDelegate((x) =>
{
AddCustomerCommand.Execute(null);
}));
This is my favourite approach and is the simplist approach in my opinion.
You just need to register an entire object with the Mediator
and attribute up some message hook methods.
So to register, this is done for you in Cinch, so you don't
have to do anything. Simply inherit from ViewModelBase, job done.
If you are wondering how this is done, the ViewModelBase class in Cinch
simply registers itself with the Mediator like this.
//Register all decorated methods to the Mediator
Mediator.Register(this);
So 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
/// <summary>
/// Mediator callback from StartPageViewModel
/// </summary>
/// <param name="dummy">Dummy not needed</param>
[MediatorMessageSink("AddCustomerMessage", ParameterType = typeof(Boolean))]
private void AddCustomerMessageSink(Boolean dummy)
{
AddCustomerCommand.Execute(null);
}
So how about notification of messages.
That is very easy to do, we simply use the Mediator.NotifyCollegues()
method as follows:
//Use the Mediator to send a Message to MainWindowViewModel to add a new
//Workspace item
Mediator.NotifyColleagues<Boolean>("AddCustomerMessage", true);
So any objects that subscribed to this message will now be called back by
the list of Mediator WeakActions callback delegates.
In the subsequent articles I will be showcasing it roughly like this
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.
As always votes / comments are welcome.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 16 Oct 2009 Editor: |
Copyright 2009 by Sacha Barber Everything else Copyright © CodeProject, 1999-2009 Web17 | Advertise on the Code Project |