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!
ReplyDeleteSo it won't work if the property "Command" is set before "OnEvent"?
ReplyDeletewhat if you needed to bind several events to several commands on the same UI element?
ReplyDeleteI tried this on a Window's Closing without success.
ReplyDeleteIt works for MouseEnter, but with Closing as the event it fails on the CreateDelegate line in the CommandChangedPropertyCallback function.
The error: Unable to bind to target event.
I like this example and could you please provide an example to bind multiple event to command to same UI element.
ReplyDelete