MVVM event to command binding #3: Attached properties

In this article, I’d like to present a rather simple approach towards binding user events to Viewmodel commands in XAML in a shorter way than using behaviors and event triggers: This solution makes use of attached properties for defining both the event to be captured and the command to be invoked. Let’s once again first take a look at the implementation, then discuss its shortcomings.

The code:

First of all, we’ll need a class containing two attached properties. As mentioned before, one resembles the user event to be reacted to, and the other one the Command to be invoked on the event, so let’s call the two properties Event and Command:

class EventToCommand
{
	public static readonly DependencyProperty CommandProperty =
		DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(EventToCommand));

	public static ICommand GetCommand(DependencyObject obj)
	{
		return (ICommand)obj.GetValue(CommandProperty);
	}

	public static void SetCommand(DependencyObject obj, ICommand value)
	{
		obj.SetValue(CommandProperty, value);
	}
	
	public static readonly DependencyProperty EventProperty =
		DependencyProperty.RegisterAttached("Event", typeof(string), typeof(EventToCommand),
			new PropertyMetadata(EventPropertyChangedCallback));

	public static string GetEvent(DependencyObject obj)
	{
		return (string)obj.GetValue(EventProperty);
	}

	public static void SetEvent(DependencyObject obj, string value)
	{
		obj.SetValue(EventProperty, value);
	}
}

The idea is to be able to bind the Command property to some Command defined in the Viewmodel, in the same way as we’re used to from the conventional Command property most user controls provide, and to specify the desired event as string.

You’ll have noticed that the Event property declaration specifies an EventPropertyChangedCallback, which is a method that is invoked each time the Event property’s value changes. This will look as follows:

public static void EventPropertyChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
	string @event = (string)args.NewValue;
	var eventInfo = obj.GetType().GetEvent(@event);
	if (eventInfo != null)
	{
		var methodInfo = typeof(EventToCommand).GetMethod("OnEvent", BindingFlags.Static | BindingFlags.NonPublic);
		eventInfo.GetAddMethod().Invoke(obj,
			new object[] { Delegate.CreateDelegate(eventInfo.EventHandlerType, methodInfo) });
	}
}

So, each time an event is specified to be bound to, the Event property’s content is evaluated, the according event is found on the respective user control, and it is instructed to call the static OnEvent method whenever this event is fired. This method is still missing from our code sample, so here we go:

private static void OnEvent(object sender, RoutedEventArgs e)
{
	UIElement element = (UIElement)sender;
	if (element != null)
	{
		ICommand command = element.GetValue(CommandProperty) as ICommand;
		if (command != null && command.CanExecute(null))
		{
			command.Execute(null);
		}
	}
}

This one’s easily explained: The Viewmodel Command bound to is located, its CanExecute condition is evaluated, and then it is executed. Note that I’ve passed null to both the CanExecute and Execute methods, since in a typical ICommand they expect a command parameter to be passed. Of course, it would easily be possible to define a third attached property CommandParameter, and pass its value at this point.

How is this solution used in practice, e.g. if binding to a button’s MouseDoubleClick event?

<Button
		local:EventToCommand.Command="{Binding SomeCommand}"
		local:EventToCommand.Event="MouseDoubleClick">
	click me
</Button>

Discussion:

The clear advantage of this solution is that it’s short (only two additional properties are necessary on each user control) while still being verbose and self-explanatory. On the other hand, there are three major drawbacks:

  1. Since our two attached properties Command and Event can only be attached once to each user control, it’s only possible to capture one user event.
  2. The desired event is passed as string, which may easily lead to runtime errors due to simple XAML typos. This is a major decline in comfort especially when compared to conventional event handlers, which can be chosen from a list proposed by Intellisense.
  3. The performance issue: Since reflection is used to detect the desired event and attach a delegate to it, the solution might be slightly slower than comparable approached. However, this is not a severe handicap as the reflection logic is only executed once when specifying the event and the Command, not each time the Command is invoked.

In the following blog post, we’ll take a look at an alternative solution which can eliminate at least two of the disadvantages listed above!