MVVM event to command binding #4: Predefined event handlers

In the next step, let’s take a look at an approach that eliminates two of the three major disadvantages of the solution using attached properties, namely performance issues and the fact that the event to be captured must be specified as string.

The code:

Even this one relies on attached properties to some extent, although in a much simpler way: In this case, there’s no need for an Event property, and no callback method is needed, all we need to specify in our EventToCommand helper class is one attached property Command:

class EventToCommand : DependencyObject
{
	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);
	}
}

In addition, it’s easily possible to define a second attached property CommandParameter the value of which can be passed to the Command later on:

public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached(
	"CommandParameter", 
	typeof (object), 
	typeof (EventToCommand), 
	new PropertyMetadata(default(object)));

public static object GetCommandParameter(UIElement element)
{
	return element.GetValue(CommandParameterProperty);
}

public static void SetCommandParameter(UIElement element, object value)
{
	element.SetValue(CommandParameterProperty, value);
}

The idea is to use any user control’s existing event handler instead of specifying its name as string property. To do so, we need a BaseView class that wraps the respective platform’s View class (Window in WPF, Page in Windows Store Apps, etc.) and adds one additional method. All Views present within the project will then need to be derived from this wrapper class.

As an example, let’s take a look at the BaseView class for the WPF platform:

public class BaseView : Window
{
	protected void EventToCommand(object sender, EventArgs args)
	{
		var element = sender as UIElement;
		if (element != null)
		{
			// Check whether the sender has a Command defined in XAML
			var command = Event.GetCommand(element);
			if (command != null)
			{
				// If yes, check whether the sender has a CommandParameter defined in XAML (otherwise, we pass the event
				// args as parameter)
				object parameter = Event.GetCommandParameter(element);
				if (parameter == default(object))
					parameter = args;

				// Now evaluate this Command's CanExecute condition and, if allowed, invoke the Command
				if (command.CanExecute(parameter))
					command.Execute(parameter);
			}
		}
	}
}

All this event handler does is check wether the attached Command property has been specified for a specific user control, test its CanExecute condition, and invoke the Command. In addition, if the attached CommandParameter property has also been specified, we pass its content to the Command as parameter – if not, we simply pass on the event handlers event arguments, which might contain important information concerning the respective event that has been handled.

Usage of this approach in XAML is similar to the previously suggested solution, but with the improvement that the event name need not be passed as string, since the EventToCommand method is a known event handler within the class that contains our button:

<Button MouseDoubleClick="EventToCommand"
		local:EventToCommand.Command="{Binding SomeCommand}"
		local:EventToCommand.CommandParameter="some parameter value">
	click me
</Button>

Discussion:

The most obvious disadvantage of this solution is that it only bind one Event per user control (of course it would be possible to implement several predefined event handlers EventToCommand1, EventToCommand2, etc. and several attached property pairs, but still the number of event-to-command bindings will be limited).

On the other hand the clear advantage (especially compared to the solution I’ll present in the next article, which eliminates the limitation mentioned above) is that it works on all platforms. However, there are exception which need to be taken into account:

  • On the WinRT and UWP platforms, some user events pass arguments of type RoutedEventArgs. To be able to bind these as well to arbitrary Commands, we need an addition event handler to our View wrapper class which is nearly identical to the one seen above. The full BaseView class for the UWP platform looks as follows:

    public abstract class BaseView : Page
    {
    	protected void EventToCommand(object sender, EventArgs args)
    	{
    		var element = sender as UIElement;
    		if (element != null)
    		{
    			var command = Event.GetCommand(element);
    			if (command != null)
    			{
    				object parameter = Event.GetCommandParameter(element);
    				if (parameter == default(object))
    					parameter = args;
    
    				if (command.CanExecute(parameter))
    					command.Execute(parameter);
    			}
    		}
    	}
    
    	protected void EventToCommand(object sender, RoutedEventArgs args)
    	{
    		var element = sender as UIElement;
    		if (element != null)
    		{
    			var command = Event.GetCommand(element);
    			if (command != null)
    			{
    				object parameter = Event.GetCommandParameter(element);
    				if (parameter == default(object))
    					parameter = args;
    
    				if (command.CanExecute(parameter))
    					command.Execute(parameter);
    			}
    		}
    	}
    }
    
  • Silverlight doesn’t allow event handlers defined in base classes to be referenced directly from within XAML (due to a bug which I reported in a previous blog post). This means that we can attach the Command and CommandParameter properties conveniently in XAML, but need to register the EventToCommand method in a code-behind event handler, unfortunately.

In summary, the solution suggested in the article is not fully satisfying. In the next post, I’ll present a much more elegant approach using custom markup extensions!