Event to Command in WPF MVVM application

I needed a simple way to obtain this simple result: whenever a certain component in the UI (a WebBrowser control) raises some specific event, I want a command in the VM to be executed, without the need to specify any command parameter. The only requirement I want is avoiding a single line of code in the UI :) because, having no code in the UI is one of the main benefit of the MVVM model.

image

To keep everything simple I want a simple syntax that permits me to specify that when an event of type X is raised, a command of name Y should be called.

   1: <Controls:xxxWebBrowserManagerFlexible x:Name="wbBrowser" Margin="0,0,0,0"

   2:                BrowserType="InternetExplorer"   

   3:                Links="{Binding LinksOfThePage}"

   4:                Behaviours:EventToCommandBehavior.Bind="DocumentCompleted-SignalDocumentComplete"

   5:                RawHtmlContent="{Binding FullHtmlContent, Mode=TwoWay}"

   6:                SelectedText="{Binding BrowserSelectedText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

   7:                CurrentUrl="{Binding UrlToNavigate, Mode=TwoWay}" />

As you can see I created a simple behavior called EventToCommandBehavior that accepts a very simple syntax: a string of format EvenName-CommandName. This solution probably is not so elegant, it has no intellisense or designer support, but it works and it is supersimple. My Behavior has an Attached property called Bind

   1: public static readonly DependencyProperty BindProperty =

   2:    DependencyProperty.RegisterAttached

   3:    (

   4:        "Bind",

   5:        typeof(String),

   6:        typeof(EventToCommandBehavior),

   7:        new UIPropertyMetadata(String.Empty, OnBindChanged)

   8:    );

All the dirty work is done inside the OnBindChanged.

   1: private static Dictionary<Object, String> _routes

   2:      = new Dictionary<object, String>();

   3:  

   4:  private static void OnBindChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)

   5:  {

   6:      if (String.IsNullOrWhiteSpace((String) args.NewValue)) return;

   7:  

   8:      String[] bind = ((String) args.NewValue).Split('-');

   9:  

  10:      EventInfo ev = dpo.GetType().GetEvent(bind[0]);

  11:      MethodInfo handler = typeof(EventToCommandBehavior)

  12:          .GetMethod("Handler", BindingFlags.NonPublic | BindingFlags.Static);

  13:      var eh = Delegate.CreateDelegate(ev.EventHandlerType, null, handler);

  14:      var minfo = ev.GetAddMethod();

  15:      minfo.Invoke(dpo, new object[] { eh });

  16:      

  17:      //now go for the command.

  18:      _routes.Add(dpo, bind[1]);

  19:  }

The core part is finding the EventInfo for choosen event, and create dynamically an handler capable to handle every event. The association between EventName and CommandName is stored inside a dictionary object, and the handler is really simple, just get a reference to the command with reflection and call the Execute method.

   1: private static void Handler(Object sender, EventArgs e)

   2:  {

   3:      String commandName;

   4:      if (_routes.TryGetValue(sender, out commandName))

   5:      {

   6:          FrameworkElement fe = sender as FrameworkElement;

   7:          if (fe.DataContext != null)

   8:          {

   9:              PropertyInfo pinfo = fe.DataContext.GetType().GetProperty(commandName);

  10:              if (pinfo != null)

  11:              {

  12:                  ICommand command = (ICommand) pinfo.GetValue(fe.DataContext, null);

  13:                  if (command.CanExecute(null))

  14:                  {

  15:                      command.Execute(null);

  16:                  }

  17:              }

  18:          }

  19:      }

  20:  }

Since the View Model that contains the ICommand is the DataContext of the element that raised the event, I simply use the GetProperty() method to find the right ICommand, and finally call the Execute method to actually call the command. Since one of the prerequisites states that we need to pass no command parameters, I simply pass null to Execute method.

Alk.

Published by

Ricci Gian Maria

.Net programmer, User group and community enthusiast, programmer - aspiring architect - and guitar player :). Visual Studio ALM MVP