In the last post I explained how to write a behaviour to hook a commmand to an event. That works well but a new behaviour is needed to written for each event. I tried a generic approach to have a single behaviour that can be used to wire any event to a command. It comes witha little performance penalty as reflection is used to get the event and attached a delegate to that event. Below is the code:
using System.Windows.Input;
using System.Windows.Controls.Primitives;
using System.Windows.Controls;
using System.Reflection;
using System.Diagnostics;
public class CommandExecuter
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(CommandExecuter), new PropertyMetadata(CommandPropertyChangedCallback));
public static readonly DependencyProperty OnEventProperty = DependencyProperty.RegisterAttached("OnEvent", typeof(string), typeof(CommandExecuter));
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(CommandExecuter));
public static void CommandPropertyChangedCallback(DependencyObject depObj, DependencyPropertyChangedEventArgs args)
{
string onEvent = (string)depObj.GetValue(OnEventProperty);
Debug.Assert(onEvent != null, "OnEvent must be set.");
var eventInfo = depObj.GetType().GetEvent(onEvent);
if (eventInfo != null)
{
var mInfo = typeof(CommandExecuter).GetMethod("OnRoutedEvent", BindingFlags.NonPublic | BindingFlags.Static);
eventInfo.GetAddMethod().Invoke(depObj, new object[] { Delegate.CreateDelegate(eventInfo.EventHandlerType, mInfo) });
}
else
{
Debug.Fail(string.Format("{0} is not found on object {1}", onEvent, depObj.GetType()));
}
}
public static ICommand GetCommand(UIElement element)
{
return (ICommand)element.GetValue(CommandProperty);
}
public static void SetCommand(UIElement element, ICommand command)
{
element.SetValue(CommandProperty, command);
}
public static string GetOnEvent(UIElement element)
{
return (string)element.GetValue(OnEventProperty);
}
public static void SetOnEvent(UIElement element, string evnt)
{
element.SetValue(OnEventProperty, evnt);
}
public static object GetCommandParameter(UIElement element)
{
return (object)element.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(UIElement element, object commandParam)
{
element.SetValue(CommandParameterProperty, commandParam);
}
private static void OnRoutedEvent(object sender, RoutedEventArgs e)
{
UIElement element = (UIElement)sender;
if (element != null)
{ ICommand command = element.GetValue(CommandProperty) as ICommand;
if (command != null && command.CanExecute(element.GetValue(CommandParameterProperty)))
{
command.Execute(element.GetValue(CommandParameterProperty));
}
}
}
}
Below code shows how to set the command on the controls for any event: (local: is namespace prefix)
<ComboBox local:CommandExecuter.Command="{Binding CommandImpl}" local:CommandExecuter.OnEvent="SelectionChanged" ></ComboBox>
<Button local:CommandExecuter.Command="{Binding AnotherCommandImpl}" local:CommandExecuter.OnEvent="MouseEnter" local:CommandExecuter.CommandParameter="{x:Static null}"></Button>
using System.Windows.Input;
using System.Windows.Controls.Primitives;
using System.Windows.Controls;
using System.Reflection;
using System.Diagnostics;
public class CommandExecuter
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(CommandExecuter), new PropertyMetadata(CommandPropertyChangedCallback));
public static readonly DependencyProperty OnEventProperty = DependencyProperty.RegisterAttached("OnEvent", typeof(string), typeof(CommandExecuter));
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(CommandExecuter));
public static void CommandPropertyChangedCallback(DependencyObject depObj, DependencyPropertyChangedEventArgs args)
{
string onEvent = (string)depObj.GetValue(OnEventProperty);
Debug.Assert(onEvent != null, "OnEvent must be set.");
var eventInfo = depObj.GetType().GetEvent(onEvent);
if (eventInfo != null)
{
var mInfo = typeof(CommandExecuter).GetMethod("OnRoutedEvent", BindingFlags.NonPublic | BindingFlags.Static);
eventInfo.GetAddMethod().Invoke(depObj, new object[] { Delegate.CreateDelegate(eventInfo.EventHandlerType, mInfo) });
}
else
{
Debug.Fail(string.Format("{0} is not found on object {1}", onEvent, depObj.GetType()));
}
}
public static ICommand GetCommand(UIElement element)
{
return (ICommand)element.GetValue(CommandProperty);
}
public static void SetCommand(UIElement element, ICommand command)
{
element.SetValue(CommandProperty, command);
}
public static string GetOnEvent(UIElement element)
{
return (string)element.GetValue(OnEventProperty);
}
public static void SetOnEvent(UIElement element, string evnt)
{
element.SetValue(OnEventProperty, evnt);
}
public static object GetCommandParameter(UIElement element)
{
return (object)element.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(UIElement element, object commandParam)
{
element.SetValue(CommandParameterProperty, commandParam);
}
private static void OnRoutedEvent(object sender, RoutedEventArgs e)
{
UIElement element = (UIElement)sender;
if (element != null)
{ ICommand command = element.GetValue(CommandProperty) as ICommand;
if (command != null && command.CanExecute(element.GetValue(CommandParameterProperty)))
{
command.Execute(element.GetValue(CommandParameterProperty));
}
}
}
}
Below code shows how to set the command on the controls for any event: (local: is namespace prefix)
<ComboBox local:CommandExecuter.Command="{Binding CommandImpl}" local:CommandExecuter.OnEvent="SelectionChanged" ></ComboBox>
<Button local:CommandExecuter.Command="{Binding AnotherCommandImpl}" local:CommandExecuter.OnEvent="MouseEnter" local:CommandExecuter.CommandParameter="{x:Static null}"></Button>
Great Stuff - Thx for publishing
ReplyDeleteAfter I've read it : perhaps it is a good idea the check ICommand.CanExecute before calling the Command - just to make it perfect ;-)
ReplyDeleteprivate static void OnRoutedEvent(object sender, RoutedEventArgs e)
{
UIElement element = (UIElement)sender;
if (element != null)
{
ICommand command = element.GetValue(CommandProperty) as ICommand;
if (command != null)
{
if(command.CanExecute(CommandParameterProperty))
command.Execute(element.GetValue(CommandParameterProperty));
}
}
}
Glad that you liked it. Thanks for the suggestion, I have modified that code.
ReplyDeleteHi Naveen,
DeleteIts a nice Stuff.Thanks for this.
I just want to know how can I get the sender, e values as we normally get in eventhandlers ?
You are already getting those in below method. Do you want to pass them to commands?
ReplyDeleteprivate static void OnRoutedEvent(object sender, RoutedEventArgs e)
useful
ReplyDeleteGood stuff!
ReplyDelete