WPF : Attached Commands

In my last post I showed you how to execute ViewModel ICommand(s) from FrameworkElement using the new Blend 3 Behaviours Dll. Well this time I want to show you how to run a ViewModel ICommand from any FrameworkElement without the use of the Blend 3 Behaviours Dll.

I should state that this post was inspired by a good friend of mine Marlon Grechs AttachedCommandBehavior V2 aka ACB blog post. I loved and have used Marlons idea, but he uses dynamically created IL, to create the correct type of RoutedEvent handler that matches the event you are trying to use to trigger the ICommand. It is way cool, but I couldn’t help but think there was an easier way. I like simple things, they suit me, as I am a simple man.

So what I thought was why not just use an Attached DP or 2, and a bit of Reflection, and Event hooking/Binding and ala-kazam, job done.

Here is how, the ViewModel looks like this

   1:  /// <summary>
   2:  /// A small demo view model with a single
   3:  /// ICommand exposed, that will be executed
   4:  /// using the new Blend3 Interactivity
   5:  /// functionality, such as TargetedTriggerAction<T>
   6:  /// </summary>
   7:  public class DemoViewModel : ViewModelBase
   8:  {
   9:      #region Data
  10:      //Commands
  11:      private ICommand demoCommand = null;
  12:  
  13:      #endregion
  14:  
  15:      #region Ctor
  16:      public DemoViewModel()
  17:      {
  18:          //wire up command
  19:          demoCommand = new SimpleCommand
  20:          {
  21:              CanExecuteDelegate = x => true,
  22:              ExecuteDelegate = x =>
  23:                  {
  24:                      MessageBox.Show("In the ViewModel");
  25:                  }
  26:          };
  27:      }
  28:      #endregion
  29:  
  30:      #region Public Properties
  31:  
  32:      public ICommand DemoCommand
  33:      {
  34:          get { return demoCommand; }
  35:      }
  36:      #endregion
  37:  }

I am actually using Marlon Grechs  SimpleCommand but if you prefer you can use Prisms DelegateCommand, or Josh Smiths RelayCommand, they all work.

So next all I do is create some attached behaviour using the magic of Attached DPs. Here is the full code.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Windows;
   6:  using System.Windows.Input;
   7:  using System.Reflection;
   8:  using System.Windows.Media;
   9:  
  10:  namespace AttachedCommands
  11:  {
  12:      public static class CommandBehavior
  13:      {
  14:          #region TheCommandToRun
  15:  
  16:          /// <summary>
  17:          /// TheCommandToRun : The actual ICommand to run
  18:          /// </summary>
  19:          public static readonly DependencyProperty TheCommandToRunProperty =
  20:              DependencyProperty.RegisterAttached("TheCommandToRun",
  21:                  typeof(ICommand),
  22:                  typeof(CommandBehavior),
  23:                  new FrameworkPropertyMetadata((ICommand)null));
  24:  
  25:          /// <summary>
  26:          /// Gets the TheCommandToRun property.  
  27:          /// </summary>
  28:          public static ICommand GetTheCommandToRun(DependencyObject d)
  29:          {
  30:              return (ICommand)d.GetValue(TheCommandToRunProperty);
  31:          }
  32:  
  33:          /// <summary>
  34:          /// Sets the TheCommandToRun property.  
  35:          /// </summary>
  36:          public static void SetTheCommandToRun(DependencyObject d, ICommand value)
  37:          {
  38:              d.SetValue(TheCommandToRunProperty, value);
  39:          }
  40:          #endregion
  41:  
  42:          #region RoutedEventName
  43:  
  44:          /// <summary>
  45:          /// RoutedEventName : The event that should actually execute the
  46:          /// ICommand
  47:          /// </summary>
  48:          public static readonly DependencyProperty RoutedEventNameProperty =
  49:              DependencyProperty.RegisterAttached("RoutedEventName", typeof(String),
  50:              typeof(CommandBehavior),
  51:                  new FrameworkPropertyMetadata((String)String.Empty,
  52:                      new PropertyChangedCallback(OnRoutedEventNameChanged)));
  53:  
  54:          /// <summary>
  55:          /// Gets the RoutedEventName property.  
  56:          /// </summary>
  57:          public static String GetRoutedEventName(DependencyObject d)
  58:          {
  59:              return (String)d.GetValue(RoutedEventNameProperty);
  60:          }
  61:  
  62:          /// <summary>
  63:          /// Sets the RoutedEventName property.  
  64:          /// </summary>
  65:          public static void SetRoutedEventName(DependencyObject d, String value)
  66:          {
  67:              d.SetValue(RoutedEventNameProperty, value);
  68:          }
  69:  
  70:          /// <summary>
  71:          /// Hooks up a Dynamically created EventHandler (by using the 
  72:          /// <see cref="EventHooker">EventHooker</see> class) that when
  73:          /// run will run the associated ICommand
  74:          /// </summary>
  75:          private static void OnRoutedEventNameChanged(DependencyObject d,
  76:              DependencyPropertyChangedEventArgs e)
  77:          {
  78:              String routedEvent = (String)e.NewValue;
  79:  
  80:              //If the RoutedEvent string is not null, create a new
  81:              //dynamically created EventHandler that when run will execute
  82:              //the actual bound ICommand instance (usually in the ViewModel)
  83:              if (!String.IsNullOrEmpty(routedEvent))
  84:              {
  85:                  EventHooker eventHooker = new EventHooker();
  86:                  eventHooker.ObjectWithAttachedCommand = d;
  87:  
  88:                  EventInfo eventInfo = d.GetType().GetEvent(routedEvent,
  89:                      BindingFlags.Public | BindingFlags.Instance);
  90:  
  91:                  //Hook up Dynamically created event handler
  92:                  if (eventInfo != null)
  93:                  {
  94:                      eventInfo.AddEventHandler(d,
  95:                          eventHooker.GetNewEventHandlerToRunCommand(eventInfo));
  96:                  }
  97:              }
  98:          }
  99:          #endregion
 100:      }
 101:  
 102:      /// <summary>
 103:      /// Contains the event that is hooked into the source RoutedEvent
 104:      /// that was specified to run the ICommand
 105:      /// </summary>
 106:      sealed class EventHooker
 107:      {
 108:          #region Public Methods/Properties
 109:          /// <summary>
 110:          /// The DependencyObject, that holds a binding to the actual
 111:          /// ICommand to execute
 112:          /// </summary>
 113:          public DependencyObject ObjectWithAttachedCommand { get; set; }
 114:  
 115:          /// <summary>
 116:          /// Creates a Dynamic EventHandler that will be run the ICommand
 117:          /// when the user specified RoutedEvent fires
 118:          /// </summary>
 119:          /// <param name="eventInfo">The specified RoutedEvent EventInfo</param>
 120:          /// <returns>An Delegate that points to a new EventHandler
 121:          /// that will be run the ICommand</returns>
 122:          public Delegate GetNewEventHandlerToRunCommand(EventInfo eventInfo)
 123:          {
 124:              Delegate del = null;
 125:  
 126:              if (eventInfo == null)
 127:                  throw new ArgumentNullException("eventInfo");
 128:  
 129:              if (eventInfo.EventHandlerType == null)
 130:                  throw new ArgumentException("EventHandlerType is null");
 131:  
 132:              if (del == null)
 133:                  del = Delegate.CreateDelegate(eventInfo.EventHandlerType, this,
 134:                        GetType().GetMethod("OnEventRaised",
 135:                          BindingFlags.NonPublic |
 136:                          BindingFlags.Instance));
 137:  
 138:              return del;
 139:          }
 140:          #endregion
 141:  
 142:          #region Private Methods
 143:  
 144:          /// <summary>
 145:          /// Runs the ICommand when the requested RoutedEvent fires
 146:          /// </summary>
 147:          private void OnEventRaised(object sender, EventArgs e)
 148:          {
 149:              ICommand command = (ICommand)(sender as DependencyObject).
 150:                  GetValue(CommandBehavior.TheCommandToRunProperty);
 151:  
 152:              if (command != null)
 153:              {
 154:                  command.Execute(null);
 155:              }
 156:          }
 157:          #endregion
 158:      }
 159:  
 160:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }NOTE :

If you look at the ICommand interface you will notice that the Execute method look like this

void Execute(
    Object parameter
)

I have not catered for the ICommand parameter in my implementation, but if you want to do that it should be simply a case of yet another attached property, which you could then feed into the EventHooker class shown above, and then pass to the command when executing. So instead of command.Execute(null); you could do something like command.Execute(commandParameter);

Basically I do not use the command parameter that often, so did not include it, but you could, it would be trivial.

The last thing to do is to show you how to use this in a View, here is how.

   1:  <Window x:Class="AttachedCommands.Window1"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:local="clr-namespace:AttachedCommands"
   5:      Title="Window1" Height="300" Width="300">
   6:      <Grid>
   7:  
   8:          <Grid.RowDefinitions>
   9:              <RowDefinition Height="*"/>
  10:              <RowDefinition Height="*"/>
  11:          </Grid.RowDefinitions>
  12:  
  13:          <!-- Fires DemoCommand ICommand when MouseDown RoutedEvent occurs -->
  14:          <Grid Grid.Row="0"
  15:                  local:CommandBehavior.RoutedEventName="MouseDown"
  16:                  local:CommandBehavior.TheCommandToRun=
  17:                      "{Binding Path=DemoCommand}"
  18:                  Background="Orange">
  19:              <Label VerticalAlignment="Center" HorizontalAlignment="Center"
  20:                     Content="MouseDown to raise Command"/>
  21:  
  22:          </Grid>
  23:  
  24:          <!-- Fires  DemoCommand ICommand when MouseWheel RoutedEvent occurs -->
  25:          <Grid Grid.Row="1"
  26:                  local:CommandBehavior.RoutedEventName="MouseWheel"
  27:                  local:CommandBehavior.TheCommandToRun=
  28:                      "{Binding Path=DemoCommand}"
  29:                  Background="Pink">
  30:  
  31:              <Label VerticalAlignment="Center" HorizontalAlignment="Center"
  32:                     Content="MouseWheel to raise Command"/>
  33:  
  34:  
  35:          </Grid>
  36:      </Grid>
  37:  
  38:  </Window>

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }And here is a small screen shot of it running:

ABC

And as always here is a small demo project: attachedcommands.zip

Enjoy

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Advertisements

32 thoughts on “WPF : Attached Commands

  1. Marlon Grech says:

    great job dude….. looks great!!! 5 Stars from me!!!!

    BANG IT

  2. sacha says:

    Thanks Marlon. You inspired this one dude, to credit to ya buddy

  3. […] WPF : Attached Commands (Sacha Barber) […]

  4. vladisld says:

    Thanks for a very usable class.

    One question though. It seems that only one event could be hooked to a command for particular dependency object – am I wrong ?

  5. sacha says:

    vladisld

    Yeah what you say is true, it suited my needs. I fyou want multiple events for a single command, perhaps you could implement some sort of collection, and each item within collection could have its own CommandEvent class.

    Actually from memory Mark Jumal did something like this

    Maybe have a look at http://www.julmar.com/blog/mark/PermaLink,guid,8b3e4279-70a5-431e-8fa3-4c1e047df311.aspx

  6. Jeff Brand says:

    So the CanExecute is not called? I set it to false in the demo code and everything still works. Any way to get that enabled?

    Thanks!

  7. sacha says:

    The only way to do this 100% correctly is by using ICommandSource, and as there are so few controls that implement this such as Button, MenuItem, and ListBoxItem, the only thing you could do is check for the command.CanExecut state just before you run it.

    I didn’t do this as it is not what I was after, but I think that should work.

    So just do this

    private void OnEventRaised(object sender, EventArgs e)
    {
    ICommand command = (ICommand)(sender as DependencyObject).
    GetValue(CommandBehavior.TheCommandToRunProperty);

    if (command != null)
    {
    if (command.CanExecute(null))
    command.Execute(null);
    }
    }

    That works, I just tried it.

  8. martin says:

    Hello,

    your Class is great! Many Thanks. I missed a CommandParameter Propertie. Your class is very easy to understand so could implement the CommandParameter on my own.
    🙂

    greetings

  9. sacha says:

    Yeah parameter should be easy to do. Glad you like

  10. David Vennikov says:

    Hi,
    Sacha, great solution!

    One question though – what if I want to apply more than one command to the same control on different events.

    e.g:

    Is it possible?

  11. David Vennikov says:

    the code was omitted for some reason:

  12. David Vennikov says:

    the code was omitted for some reason:

    >>>

    Is this possible?

  13. David Vennikov says:

    Is this possible?

  14. David Vennikov says:
  15. sacha says:

    David

    Sure that would be possible. Take this code and wrap it is some class. Then have a collection of those classes and use that as an attached DP. Where each item in the collection can have whatever command/event you want.

    So basically you would have something like

    <TextBox>
    <TextBox.MyAttachedEventsCommandCollection>
    <local:AttachedCommandCollection>
    <local:AttachedCommand CommandToUse=”{Binding SomeCommand1}” Event=”MouseDown”/>
    <local:AttachedCommand CommandToUse=”{Binding SomeCommand2}” Event=”MouseUp”/>
    </local:AttachedCommandCollection>
    </TextBox.MyAttachedEventsCollection>
    </TextBox>

    See what I mean

  16. Ray Akkanson says:

    Simple is better offcourse. Do you have a sample project Sacha?

    Ray Akkanson

  17. sacha says:

    There is a link at bottom of post

  18. MikeB says:

    Hi, thanks for your article, is there a way to use this class with dynamically created buttons? For example, I have a list box that contains buttons (I call it a command bar) that is created from the database based on a users security. The Database contains the name of the button along with the name of the ICommand. Is there a way to convert the string name of the ICommand to an ICommand?

  19. sacha says:

    MikeB

    You could not use this class in the situation you state. You could use parts of it, but the part to get the ICommand, you would need to change that to a String and use Reflection to get a Property of the DataContext (ViewModel) that has the name of the String supplied to the “CommandToRun” DP

  20. MikeB says:

    Thank you for your response.

  21. ChrisH says:

    Great code! Very helpful indeed. However I’m having trouble implementing the solution you outlined for David:

    How do I implement the MyAttachedEventsCommandCollection?

  22. sacha says:

    Chris

    Go check out my MVVM framework library which I have been writing about lately.

    http://www.codeproject.com/KB/WPF/CinchII.aspx

    That has the answers

  23. Yazid says:

    Hi, excellent article. I have used your sample so that onMouseDown I execute a command on the ViewModel.

    How do I pass the mousePosition to the ViewModel?

    TIA
    Yaz

  24. sacha says:

    Yazid you could do that using a Binding or pass it into an extra DP called Param, which you then use to pass to the command in the ViewModel.

    For a more complete ICommand/Event implemenation that caters for parameters see my new MVVM framework article

    http://www.codeproject.com/KB/WPF/CinchII.aspx

  25. Dan says:

    Sacha, great article!

    What if I wanted to link to an attached event instead of a native one like MouseDown. For instance “Microsoft.Surface.Presentation.Contacts.ContactDownEvent”

    Thanks

  26. sacha says:

    Dan not sure about that. I think you would have to try it

  27. Shree Menon says:

    Thanks for this great Class. As said before it is so simple that it took only few minutes to understand and also add command parameter. I am using this in Prism

    Thanks again.

    • sacha says:

      You should look at my Cinch MVVM framework for a better Commanding idea. Its called EventCommander, and passes the args to the ICommand also.

  28. Arun nair says:

    Hi,
    Thanks for this great class. It is really useful. I made a small modification to include the command parameter. The change I made was to the OnEventRaised method of the EventHooker class. It now looks like this:
    private void OnEventRaised(object sender, EventArgs e)
    {
    ICommand command = (ICommand)(sender as DependencyObject).GetValue(CommandBehavior.TheCommandToRunProperty);
    object parameter = (sender as DependencyObject).GetValue(CommandBehavior.CommandParameterProperty);

    if (command != null)
    {
    command.Execute(parameter);
    }
    }
    I do not know if this is the best way to go about it, but it works.

    Arun

    • sacha says:

      This is already done for what I did after this blog. I did a massive ammount of work on a MVVM Framework called Cinch. Which is at cinch.codeplex.com and there is loads of documentation that describes how to use it all

  29. iyab says:

    hi, I just tried to download the link and it doesn’t work… is there a way you could fix that… pweeease 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: