HowTo hook an Event to a Command in XAML with EventCommandSetter

Apr 22, 2010 at 7:49 PM
Edited Apr 22, 2010 at 8:05 PM

.Net Version 4.0 (Visual Studio 2010) with MVVM Templates from CodePlex WPF Futures (http://wpf.codeplex.com/Wikipage)

I am setting up a Project with WPF and MVVM and was wondering how it could be made easy to hook a Command to an Event.
After searching around on the Internet the only solution I found was with AttachedProperties.
I didnt like the high amount of maintenance with that and tried to find other solutions.
My first attempt was with a MarkupExtension, but obviously the Xaml Parser handles Events differently and doesnt allow for MarkupExtensions as Value.
It was clear that i needed my own layer between the Event and the Command. The only remaining clean place for that is a Setter in a Style or Template.
After 2 days of research I finally came up with a solution that looks like the following in Xaml.

<ad:DockingManager Name="dm1" DocumentsSource="{Binding Pages}">
 <ad:DocumentPane />
 <ad:DockingManager.Style>
  <Style TargetType="ad:DockingManager">
   <!-- Routed Event -->
   <hlp:EventCommandSetter Event="KeyDown" Command="{StaticResource RequestCloseCommand}" />
   <!-- OldFashioned .Net Event -->
   <hlp:EventCommandSetter RegularEvent="RequestDocumentClose" Command="{StaticResource RequestCloseCommand}" />
  </Style>
 </ad:DockingManager.Style>
</ad:DockingManager>

I looked at the EventSetter and found that its mostly allready doing what we need. It hooks to a RoutedEvent and calls a Handler.
So if we derive from EventSetter and just set the Handler to our own, that finally calls the Command, everything is fine and working.
But what happens, if we want to hook to an old fashioned .net event? The events in the Avalon DockingManager are not RoutedEvents, for example.
For that we need an encapsulation of the event, like the RoutedEvent class is. I called this class Event and used it in my EventCommandSetter.

public class EventCommandSetter : EventSetter
{
 private ICommand _command;
 private Event _regularEvent;
 private static readonly RoutedEvent _regularRoutedEvent = EventManager.RegisterRoutedEvent(
  "RegularRouted",
  RoutingStrategy.Bubble,
  typeof(RoutedEventHandler),
  typeof(EventCommandSetter)
  );
 private void OnEvent(object sender, EventArgs e)
 {
  if (_command == null) throw new ArgumentException("EventCommandSetter.Command must not be null!");
  EventCommandArgs args = new EventCommandArgs(sender, e);
  if (_command.CanExecute(args))
  {
   _command.Execute(args);
   if (typeof(RoutedEventArgs).IsAssignableFrom(e.GetType())) ((RoutedEventArgs)e).Handled = true;
  }
 }
 public Event RegularEvent
 {
  get { return _regularEvent; }
  set
  {
   if (value == null) throw new ArgumentNullException("value");
   _regularEvent = value;
   this.Event = _regularRoutedEvent;
  }
 }
 public ICommand Command
 {
  get { return _command; }
  set
  {
   if (value == null) throw new ArgumentNullException("value");
   if (this.Event == null) throw new ArgumentException("Either Event or RegularEvent must be set!");
   this.Handler = Delegate.CreateDelegate(this.Event.HandlerType, this, ((EventHandler)OnEvent).Method);
   if (_regularEvent != null) _regularEvent.Handler += new EventHandler(OnEvent);
   _command = value;
  }
 }
}

The Event class is responsible for registering the source Event and passing it to our EventCommandSetter.
Also, I couldn't find any existing TypeConverter for regular Events, so I needed something where I could define a TypeConverter on.

[TypeConverter(typeof(EventConverter))]
public class Event
{
 public Event(object eventSource, EventInfo eventInfo)
 {
  eventInfo.AddEventHandler(eventSource, Delegate.CreateDelegate(eventInfo.EventHandlerType, this, ((EventHandler)OnEvent).Method));
 }
 private void OnEvent(object sender, EventArgs e)
 {
  if (this.Handler != null) this.Handler(sender, e);
 }
 public event EventHandler Handler;
}
The class EventCommandArgs just encapsulates the regular Event Parameters into the one that is supported by a Command.
public class EventCommandArgs
{
 public EventCommandArgs(object sender, EventArgs e)
 {
  this.Sender = sender;
  this.EventArgs = e;
 }
 public object Sender { get; set; }
 public EventArgs EventArgs { get; set; }
}

When starting with this, I thought it's easy to write a TypeConverter. It sure is for primitive Types.
First I needed to find the value for the TargetType Property of the Style class. One day of research and 3 lines of code later I finally had it.
Next I needed the instance of the object this TargetType points to. Another day of research and a little bit more lines of code later I even managed this hurdle.
I'm not sure if the solution is a solid one, but it passed my primitive tests.

public class EventConverter : TypeConverter
{
 private ServiceType GetService<ServiceType>(ITypeDescriptorContext context)
 {
  return (ServiceType)context.GetService(typeof(ServiceType));
 }
 public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
 {
  if (sourceType == typeof(string)) return true;
  return false;
 }
 public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
 {
  return false;
 }
 private void AddCurrentChild(object current, Type targetType, List<object> coll)
 {
  if (targetType.IsAssignableFrom(current.GetType())) coll.Add(current);
  if (!typeof(DependencyObject).IsAssignableFrom(current.GetType())) return;
  foreach (object child in LogicalTreeHelper.GetChildren((DependencyObject)current)) AddCurrentChild(child, targetType, coll);
 }
 public IList FindChildren(IRootObjectProvider root, Type targetType)
 {
  List<object> coll = new List<object>();
  AddCurrentChild(root.RootObject, targetType, coll);
  return coll;
 }
 public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
 {
  if (context == null) return null;
  var schema = GetService<IXamlSchemaContextProvider>(context).SchemaContext;
  var ambient = GetService<IAmbientProvider>(context);
  var root = GetService<IRootObjectProvider>(context);
  var targetType = ambient.GetFirstAmbientValue(null,
   schema.GetXamlType(typeof(Style)).GetMember("TargetType"),
   schema.GetXamlType(typeof(ControlTemplate)).GetMember("TargetType")
   );
  if (targetType == null) throw new Exception("Could not determine TargetType!");
  var eventInfo = ((Type)targetType.Value).GetEvent(value.ToString());
  if (eventInfo == null) throw new ArgumentException(value.ToString() + " is no event on " + targetType.Value.ToString());
  var children = FindChildren(root, eventInfo.DeclaringType);
  if (children.Count == 0) throw new Exception("Could not find instance of " + eventInfo.DeclaringType.ToString() + "!");
  // the last one is the one we currently parse
  return new Event(children[children.Count - 1], eventInfo);
 }
 public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
 {
  throw base.GetConvertToException(value, destinationType);
 }
}

If anyone has suggestions to improve this concept, or may has a better solution, please post a comment!

Greetings!
Zorgoban