XAML Playground
about XAML and other Amenities

Metro: Wire events to ICommand with a (not so simple) extension

2012-04-10T00:58:49+01:00 by codeblock

Looking to the new Metro style applications in Windows 8, I recently found that the easiness of Silverlight lost some points in the move to the new Metro application environment. One of the important things that are missed in XAML for Metro is the ability of wire events of the View to commands in the ViewModel. To be more precise, this capability exists but is limited to instances of ButtonBase class like Button and Hyperlinks. This scenario is known to Silverlight developers but it is easy worked around with Behaviors. Behaviors and Triggers comes from the Expression Blend SDK and are very useful. I think many of you know the EventToCommand class provided by the MVVM Light Toolkit that is able to wire commands to every kind of events.

Unfortunately behaviors are not supported in Metro applications (at least for the moment) so the sole way to handle events is to use codebehind to programmatically call commands of the ViewModel. For this reason I started to work hard to find a way to create a behavior's surrogate, and I finally achieved this result. Please take note that the solution I present here relies on two tricks made to work around some limitations I found. I hope these workarounds will not be required anymore in the future.

Create an infrastructure

As many know, Behaviors are based on Attached Properties. This useful feature still exists in XAML for Metro so it seems possible to replicate the behaviors writing some code. For the purpose of my goal, fully recreate the behaviors infrastructure is too difficult and takes too much time. So I decided to work striping out every kind of generalization and go straight to the minimal implementation.

In the solution attached to this post there are 6 classes that supports the behavior-style:

AttachableCollection: a special collection that can be attached to a DependencyObject. This instance is the element to which the binding is attached (an element of the Visual Tree)

DependencyObjectCollection: a generic collection able to host dependency objects. This collection is a DependencyObject itself.

Command: this class represents the binding of an event to a command. It is able to receive the name of the event, the binding to the command an to the command parameter. All the logic to connect the event to the command is hosted inside this class and its inner classes.

CommandCollection: AttachableCollection that contains the commands related to a single element of the visual tree

IAttachedObject: interface implemented by AttachableCollection and Command. It allows to connect the element source of events to the classes partecipating to the structure.

Extensions: static class used to expose the Attached Property

Thanks to these classes you will be able to write the following XAML:

   1: <Button Content="{Binding ButtonTitle}">
   2:     <xpg:Extensions.Commands>
   3:         <xpg:Command EventName="Click" Binding="{Binding ClickCommand}" ParameterBinding="{Binding ButtonTitle}" />
   4:         <xpg:Command EventName="Holding" Binding="{Binding HoldCommand}" ParameterBinding="{Binding ButtonTitle}" />
   5:     </xpg:Extensions.Commands>
   6: </Button>

Fully explaining the solution is hard. I invite you to explore the source codes I provide to understant the reasons behind every single class. The only thing you must have clear in mind is that most of the classes are made to propagate the instance of the source object along all the commands instances.

How it works

Part of the solution I've created starts from this post in codeproject. It describes a solution, based on attached properties, that enable to connect a single event to a command. Unfortunately this solution works only with the previous Developer Preview but it does not works with the latest Windows 8 Community Preview.

The reason is hard to understant to me, but it seems something in WinRT prevents to programmatically attach events. The following code results in an exception:

   1: if (eventInfo != null)
   2: {
   3:     Delegate handler = eventHooker.GetEventHandler(eventInfo);
   4:     eventInfo.AddEventHandler(d, handler);
   5: }

To work around to this problem I found a new class that seems to be created for this exact purpose. The class WindowsRuntimeMarshal contains a method named AddEventHandler that does the same. The documentation states this class is created to support the .NET Framework. But it is the only way to hook events as we need.

   1: if (eventInfo != null)
   2: {
   3:      Delegate handler = eventHooker.GetEventHandler(eventInfo);
   4:  
   5:      WindowsRuntimeMarshal.AddEventHandler<Delegate>(
   6:          dlg => (EventRegistrationToken)eventInfo.AddMethod.Invoke(dependencyObject, new object[] { dlg }),
   7:          etr => eventInfo.RemoveMethod.Invoke(dependencyObject, new object[] { etr }), handler);
   8: }

After this the events are hooked up and the command need to be raised. This presents another problem. No matter if the base class is DependencyObject or FrameworkElement, there is not any way to get databinding to work as I expect. I need to deep investigate on this problem but for the moment I was able to get the Binding object and to evaluate it using a BindingEvaluator class. This is the main reason because I've not user Command and CommandParameter for the properties but instead I used Binding and ParameterBinding. Here is the code to read databinding:

   1: /// ... omissis
   2:  
   3: private void OnEventRaised(object sender, object e)
   4: {
   5:      BindingEvaluator commandBinding = new BindingEvaluator((FrameworkElement)sender, this.Binding.Binding);
   6:      ICommand command = commandBinding.Value as ICommand;
   7:  
   8:      object commandParameter;
   9:  
  10:      if (this.Binding.ParameterBinding is Binding)
  11:      {
  12:          BindingEvaluator commandParameterBinding = new BindingEvaluator((FrameworkElement)sender, (Binding)this.Binding.ParameterBinding);
  13:          commandParameter = commandParameterBinding.Value;
  14:      }
  15:      else
  16:          commandParameter = this.Binding.Parameter;
  17:  
  18:      if (command != null)
  19:          command.Execute(commandParameter);
  20: }
  21:  
  22: /// ... omissis
  23:  
  24: private sealed class BindingEvaluator : FrameworkElement
  25: {
  26:     /// <summary>
  27:     /// Instance of the binding to evaluate
  28:     /// </summary>
  29:     private Binding Binding { get; set; }
  30:  
  31:     #region Value
  32:  
  33:     /// <summary>
  34:     /// Exposes the Dependency Property related to the Value property
  35:     /// </summary>
  36:     public static readonly DependencyProperty ValueProperty =
  37:         DependencyProperty.Register("Value", typeof(object), typeof(BindingEvaluator), new PropertyMetadata(DependencyProperty.UnsetValue));
  38:  
  39:     /// <summary>
  40:     /// Gests the value retrieved from the binding
  41:     /// </summary>
  42:     public object Value
  43:     {
  44:         get
  45:         {
  46:             return (object)GetValue(ValueProperty);
  47:         }
  48:         private set { SetValue(ValueProperty, value); }
  49:     }
  50:  
  51:     #endregion
  52:  
  53:     /// <summary>
  54:     /// Create an instance of the evaluator
  55:     /// </summary>
  56:     /// <param name="element">Instance of element to inherit data context</param>
  57:     /// <param name="binding">Instance of the binding to evaluate</param>
  58:     public BindingEvaluator(FrameworkElement element, Binding binding)
  59:     {
  60:         this.DataContext = element.DataContext;
  61:         this.Binding = binding;
  62:         SetBinding(BindingEvaluator.ValueProperty, this.Binding);
  63:     }
  64: } 
  65:  

Conclusion

With these workarounds the solution now works. I'm using it in a project I'm working to develop usign MVVM Light Toolkit. It allows to easily connect Views to ViewModels without wasting codebehind with lot of event handlers. If you want I attached the code, side by side with an example. Please feel free to contact me for any issue or suggestion.

Download: http://www.silverlightplayground.org/assets/sources/XPG.Extensions.zip (396kb)