MVVM event to command binding #5: Custom markup extensions

All the approaches we’ve been looking at so far are not sufficiently convenient in some way. In this artice, I’d like to present a solution that is really elegant – unfortunately, it has one major disadvantage.

Surprise, surprise!

This time, I’d like to start… with the result! Let me explain what we’re aiming at:

We all know the syntax . Unfortunately, as I mentioned earlier, this syntax does not work on event attributes. Technically, the Binding operation is referred to as a markup extension, and it is defined as Binding class somerwhere in the core .NET framework (to be exact, within the System.Windows.Data.Binding namespace).

Why am I telling all this? Well, the cool thing is that .NET allows the creation of custom markup extensions! This means, what we’re about to do is define our own operation – let’s call it EventBinding – that can be used in XAML as follows:

<Button Click="{EventBinding SomeCommand}">click me!</Button>

The code:

Custom markup extensions are defined as classes that should comply with two conditions:

  1. Inherit from the System.Windows.Markup.MarkupExtension class

  2. Have a name that ends with the term Extension (in a similar way as all custom Attributes should be named with the Attribute suffix)

In our case – since we’d like to use the term EventBinding as operation identifier, we call the extension class EventBindingExtension. This class is responsible for the following four tasks:

  1. Retrieve the name of a command that is passed to it via the constructor (since XAML, technically speaking, is only text and does not know different types, the command can only be passed as string, which why we cannot use the ICommand type to pass the Command itself but rather need to work with its name)

  2. Store this command identifier in a global variable to have it available any time

  3. Retrieve the user control’s attribute the extension was applied to (in our case, an event) – this is probably the most tricky part, so let’s postpone it for the moment

  4. Whenever the desired event fires, map the command identifier that has been stored to an actual command, and invoke it – I decided to extract this logic into its own method

public class EventBindingExtension : MarkupExtension
{
	private readonly string _commandName;

	public EventBindingExtension(string command)
	{
		_commandName = command;
	}

	private void InvokeCommand(object sender, EventArgs args)
	{
		if (!String.IsNullOrEmpty(_commandName))
		{
			var control = sender as FrameworkElement;
			if (control != null)
			{
				// Find control's ViewModel
				var viewmodel = control.DataContext;
				if (viewmodel != null)
				{
					// Command must be declared as public property within ViewModel
					var commandProperty = viewmodel.GetType().GetProperty(_commandName);
					if (commandProperty != null)
					{
						var command = commandProperty.GetValue(viewmodel) as ICommand;
						if (command != null)
						{
							// Execute Command and pass event arguments as parameter
							if (command.CanExecute(args))
							{
								command.Execute(args);
							}
						}
					}
				}
			}
		}
	}
}

This code snippet should not be too difficult to grasp. Let’s now add the third of the above-mentioned four tasks: To find out which XML attribute our markup extension has been applied to, we can override the ProvideValue method that is provided by the MarkupExtension class. Within this method, we retrieve the InvokeCommand method we’ve defined before (I decided to do this right at the beginning of the method logic, because if this should fail there’s no point in continuing).

Then, we retrieve the extension’s value target which corresponds to the attribute containing the markup extension – this can be done through the IServiceProvider object that is automatically passed to the method. We assume that the value target is an event, therefore we can retrieve it as either EventInfo or MethodInfo object. In both cases, we need to register our InvokeCommand method as event handler. If anything goes wrong within this method, it is recommended to throw a InvalidOperationException:

public override object ProvideValue(IServiceProvider serviceProvider)
{
	// Retrieve a reference to the InvokeCommand helper method declared below, using reflection
	MethodInfo invokeCommand = GetType().GetMethod("InvokeCommand", BindingFlags.Instance | BindingFlags.NonPublic);
	if (invokeCommand != null)
	{
		// Check if the current context is an event or a method call with two parameters
		var target = serviceProvider.GetService(typeof (IProvideValueTarget)) as IProvideValueTarget;
		if (target != null)
		{
			var property = target.TargetProperty;
			if (property is EventInfo)
			{
				// If the context is an event, simply return the helper method as delegate
				// (this delegate will be invoked when the event fires)
				var eventHandlerType = (property as EventInfo).EventHandlerType;
				return invokeCommand.CreateDelegate(eventHandlerType, this);
			}
			else if (property is MethodInfo)
			{
				// Some events are represented as method calls with 2 parameters:
				// The first parameter is the control that acts as the event's sender,
				// the second parameter is the actual event handler
				var methodParameters = (property as MethodInfo).GetParameters();
				if (methodParameters.Length == 2)
				{
					var eventHandlerType = methodParameters[1].ParameterType;
					return invokeCommand.CreateDelegate(eventHandlerType, this);
				}
			}
		}
	}
	throw new InvalidOperationException("The EventBinding markup extension is valid only in the context of events.");
}

Discussion:

At the first glance, this seems to be the perfect solution to the problem of event-to-command binding: It is short, easy to use, and easily readable (and understandable) by fellow developers. However, there is one major drawback: The WinRT / UWP platforms do not allow defining custom markup extensions, which means our approach only works on WPF, Silverlight, and Xamarin (although the syntax differs a bit on Xamarin, e.g. we need to extend the IMarkupExtension interface instead of the MarkupExtension class), but not in Windows Store Apps.

By the way, if you want to see the full EventBindingExtension class at one glance, note that I published it as GitHub project.

In the sixth and final blog post of this series, I’d like to point out a few specifics of the UWP platform that make event binding easier.