We already saw one example how to convert Event into Command here. But if we want to consume more than one event, then we have to write lots of repetitive code. Take a look at the following example, here we handle SelectionChagne and MouseDouble click event of Selector and Control classes respectively.
{
#region SelectionChange Event
private static DependencyProperty _selectionChangedProperty;
private static readonly RoutedEvent _selectionChangedEvent = Selector.SelectionChangedEvent;
public static readonly DependencyProperty SelectionChangedCommandProperty =
DependencyProperty.RegisterAttached("SelectionChangedCommand",
typeof(ICommand),
typeof(EventBehavior),
new PropertyMetadata(new PropertyChangedCallback(SelectionChangedCallBack)));
public ICommand SelectionChangedCommand
{
get { return (ICommand)GetValue(SelectionChangedCommandProperty); }
set { SetValue(SelectionChangedCommandProperty, value); }
}
public static void SetSelectionChangedCommand(UIElement obj, ICommand value)
{
obj.SetValue(SelectionChangedCommandProperty, value);
}
public static ICommand GetSelectionChangedCommand(UIElement obj)
{
return (ICommand)obj.GetValue(SelectionChangedCommandProperty);
}
static void SelectionChangedCallBack(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
UIElement element = obj as UIElement;
_selectionChangedProperty = args.Property;
if (element != null)
{
if (args.OldValue != null)
{
element.AddHandler(_selectionChangedEvent, new RoutedEventHandler(SelectionChangeEventHandler));
}
if (args.NewValue != null)
{
element.AddHandler(_selectionChangedEvent, new RoutedEventHandler(SelectionChangeEventHandler));
}
}
}
public static void SelectionChangeEventHandler(object sender, RoutedEventArgs e)
{
DependencyObject obj = sender as DependencyObject;
if (obj != null)
{
ICommand command = obj.GetValue(_selectionChangedProperty) as ICommand;
if (command != null)
{
if (command.CanExecute(e))
{
command.Execute(e);
}
}
}
}
#endregion
#region MoudeDoubleClick Event
private static DependencyProperty _mouseDoubleclickProperty;
private static readonly RoutedEvent _mouseDoubleClickEvent = Control.MouseDoubleClickEvent;
public static readonly DependencyProperty MouseDoubleClickCommandProperty =
DependencyProperty.RegisterAttached("MouseDoubleClickCommand",
typeof(ICommand),
typeof(EventBehavior),
new PropertyMetadata(new PropertyChangedCallback(MouseDoubleClickCallBack)));
public ICommand MouseDoubleClickCommand
{
get { return (ICommand)GetValue(MouseDoubleClickCommandProperty); }
set { SetValue(MouseDoubleClickCommandProperty, value); }
}
public static void SetMouseDoubleClickCommand(UIElement obj, ICommand value)
{
obj.SetValue(MouseDoubleClickCommandProperty, value);
}
public static ICommand GetMouseDoubleClickCommand(UIElement obj)
{
return (ICommand)obj.GetValue(MouseDoubleClickCommandProperty);
}
static void MouseDoubleClickCallBack(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
UIElement element = obj as UIElement;
_mouseDoubleclickProperty = args.Property;
if (element != null)
{
if (args.OldValue != null)
{
element.AddHandler(_mouseDoubleClickEvent, new RoutedEventHandler(MouseDoubleClickEventHandler));
}
if (args.NewValue != null)
{
element.AddHandler(_mouseDoubleClickEvent, new RoutedEventHandler(MouseDoubleClickEventHandler));
}
}
}
public static void MouseDoubleClickEventHandler(object sender, RoutedEventArgs e)
{
DependencyObject obj = sender as DependencyObject;
if (obj != null)
{
ICommand command = obj.GetValue(_mouseDoubleclickProperty) as ICommand;
if (command != null)
{
if (command.CanExecute(e))
{
command.Execute(e);
}
}
}
}
#endregion
}
Let’s reduce the duplicative work step by step. At first step we can remove the ICommand type properties so we don’t need to call GetValue and SetValue methods of DependencyObject class and don’t have to make it a child class of DependencyObject.
Then we noticed that we have to write a callback method and event handler method for all the event. We can create a separate class to perform this functionality. I simply extract the code of callback method and event handler and insert into my new class EventBahavior. To make it compile I have to create two fields of DependencyProperty and RoutedEvent type. To make sure we always have RoutedEvent we created our own constructor and passed routed event as a parameter. Here is our class.
{
#region Fields
private DependencyProperty property;
private RoutedEvent routedEvent;
#endregion
#region Constructor
public EventBehavior(RoutedEvent routedEvent)
{
this.routedEvent = routedEvent;
}
#endregion
public void CallBack(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
UIElement element = obj as UIElement;
property = args.Property;
if (element != null)
{
if (args.OldValue != null)
{
element.AddHandler(routedEvent, new RoutedEventHandler(EventHandler));
}
if (args.NewValue != null)
{
element.AddHandler(routedEvent, new RoutedEventHandler(EventHandler));
}
}
}
private void EventHandler(object sender, RoutedEventArgs e)
{
DependencyObject obj = sender as DependencyObject;
if (obj != null)
{
ICommand command = obj.GetValue(property) as ICommand;
if (command != null)
{
if (command.CanExecute(e))
{
command.Execute(e);
}
}
}
}
}
We can create an object of this class to make our life easy. Here is updated version of our class to define two event in selector class.
{
#region SelectionChange Event
static EventBehavior ebSelectionChange = new EventBehavior(Selector.SelectionChangedEvent);
public static readonly DependencyProperty SelectionChangedCommandProperty =
DependencyProperty.RegisterAttached("SelectionChangedCommand", typeof(ICommand), typeof(SelectorBehavior),
new PropertyMetadata(null, ebSelectionChange.CallBack));
public static void SetSelectionChangedCommand(DependencyObject o, ICommand value)
{
o.SetValue(SelectionChangedCommandProperty, value);
}
public static ICommand GetSelectionChangedCommand(DependencyObject o)
{
return o.GetValue(SelectionChangedCommandProperty) as ICommand;
}
#endregion
#region MoudeDoubleClick Event
static EventBehavior ebMouseDoubleClick = new EventBehavior(Control.MouseDoubleClickEvent);
public static readonly DependencyProperty MouseDoubleClickProperty =
DependencyProperty.RegisterAttached("MouseDoubleClickCommand", typeof(ICommand), typeof(SelectorBehavior),
new PropertyMetadata(null, ebMouseDoubleClick.CallBack));
public static void SetMouseDoubleClickCommand(DependencyObject o, ICommand value)
{
o.SetValue(MouseDoubleClickProperty, value);
}
public static ICommand GetMouseDoubleClickCommand(DependencyObject o)
{
return o.GetValue(MouseDoubleClickProperty) as ICommand;
}
#endregion
}
It is much less code as compare to the previous version. But here we are creating multiple object of our helper class i.e. EventBahavior. We can encapsulate this in factory class and make constructor of factory class private so no one can create an object of that class. Here is our factory class.
{
#region Private Constructor
private BehaviorFactory()
{
}
#endregion
public static DependencyProperty CreateBehavior(string name, RoutedEvent routedEvent)
{
EventBehavior eb = new EventBehavior(routedEvent);
DependencyProperty dp = DependencyProperty.RegisterAttached(name, typeof(ICommand), typeof(T),
new PropertyMetadata(eb.CallBack));
return dp;
}
}
Here is a usage of our SelectorBehavior class using this Factory class.
{
#region SelectionChange Event
public static readonly DependencyProperty SelectionChangedCommandProperty =
BehaviorFactory<SelectorBehavior>.CreateBehavior("SelectionChangedCommand", Selector.SelectionChangedEvent);
public static void SetSelectionChangedCommand(DependencyObject o, ICommand value)
{
o.SetValue(SelectionChangedCommandProperty, value);
}
public static ICommand GetSelectionChangedCommand(DependencyObject o)
{
return o.GetValue(SelectionChangedCommandProperty) as ICommand;
}
#endregion
#region MoudeDoubleClick Event
public static readonly DependencyProperty MouseDoubleClickProperty =
BehaviorFactory<SelectorBehavior>.CreateBehavior("MouseDoubleClickCommand", Control.MouseDoubleClickEvent);
public static void SetMouseDoubleClickCommand(DependencyObject o, ICommand value)
{
o.SetValue(MouseDoubleClickProperty, value);
}
public static ICommand GetMouseDoubleClickCommand(DependencyObject o)
{
return o.GetValue(MouseDoubleClickProperty) as ICommand;
}
#endregion
}
In this way we reduces lots of our duplicated code if we are going to convert more than one event into command.
Here is simple usage of this in XAML
local:SelectorBehavior.SelectionChangedCommand="{Binding SelectionChanged}"
local:SelectorBehavior.MouseDoubleClickCommand="{Binding MouseDoubleClick}"/>
[…] to command pattern is define here. Here are two classes to implement this […]
By: Switching to MVVM Part 2 | Zeeshan Amjad's WinRT, and WPF Blog on March 29, 2013
at 10:43 pm
Hi, Thanks for your effort in making events/commands implementation clear for all. But I have a doubt over here, We are only adding the event handlers, not removing, will it not have memory leak at some point of time ?
By: SS on May 20, 2014
at 11:44 pm
Yes there is a memory leak. Thanks for pointing it out. I will update it soon. Thanks for visiting the blog.
By: Zeeshan Amjad on November 9, 2014
at 10:44 pm