C# Disciples

my life in Avalon ….

How to get the EventArgs as a CommandParameter using the AttachedCommandBehaviour

I have been asked many times how can I get the event args as a command parameter when using the AttachedCommandBehaviour.

Well the library does not support such a feature. I can do that yet I feel like the EventArgs should not flow down to the ViewModel because as such that is a UI thing… Yet in some case (such as for example if you want to set the Handled property to true of the Event args) this is needed.

I decided not to add this in the library yet I am gonna show how one can get this to work with some Attached Behavior.

The idea is to have a Attached Dependency property that handles the event and stores the last Event Args in another Attached Dependency Property. Here is the code to do so

   1: public class MouseEventArgsHandler

   2: {

   3:     #region LastMouseEventArgs

   4:  

   5:     /// <summary>

   6:     /// LastMouseEventArgs Attached Dependency Property

   7:     /// </summary>

   8:     public static readonly DependencyProperty LastMouseEventArgsProperty =

   9:         DependencyProperty.RegisterAttached("LastMouseEventArgs", typeof(MouseButtonEventArgs), typeof(MouseEventArgsHandler),

  10:             new FrameworkPropertyMetadata((MouseButtonEventArgs)null));

  11:  

  12:     /// <summary>

  13:     /// Gets the LastMouseEventArgs property.  This dependency property 

  14:     /// indicates ....

  15:     /// </summary>

  16:     public static MouseButtonEventArgs GetLastMouseEventArgs(DependencyObject d)

  17:     {

  18:         return (MouseButtonEventArgs)d.GetValue(LastMouseEventArgsProperty);

  19:     }

  20:  

  21:     /// <summary>

  22:     /// Sets the LastMouseEventArgs property.  This dependency property 

  23:     /// indicates ....

  24:     /// </summary>

  25:     public static void SetLastMouseEventArgs(DependencyObject d, MouseButtonEventArgs value)

  26:     {

  27:         d.SetValue(LastMouseEventArgsProperty, value);

  28:     }

  29:  

  30:     #endregion

  31:  

  32:     #region HandleMouseDoubleClick

  33:  

  34:     /// <summary>

  35:     /// HandleMouseDoubleClick Attached Dependency Property

  36:     /// </summary>

  37:     public static readonly DependencyProperty HandleMouseDoubleClickProperty =

  38:         DependencyProperty.RegisterAttached("HandleMouseDoubleClick", typeof(bool), typeof(MouseEventArgsHandler),

  39:             new FrameworkPropertyMetadata((bool)false,

  40:                 new PropertyChangedCallback(OnHandleMouseDoubleClickChanged)));

  41:  

  42:     /// <summary>

  43:     /// Gets the HandleMouseDoubleClick property.  This dependency property 

  44:     /// indicates ....

  45:     /// </summary>

  46:     public static bool GetHandleMouseDoubleClick(DependencyObject d)

  47:     {

  48:         return (bool)d.GetValue(HandleMouseDoubleClickProperty);

  49:     }

  50:  

  51:     /// <summary>

  52:     /// Sets the HandleMouseDoubleClick property.  This dependency property 

  53:     /// indicates ....

  54:     /// </summary>

  55:     public static void SetHandleMouseDoubleClick(DependencyObject d, bool value)

  56:     {

  57:         d.SetValue(HandleMouseDoubleClickProperty, value);

  58:     }

  59:  

  60:     /// <summary>

  61:     /// Handles changes to the HandleMouseDoubleClick property.

  62:     /// </summary>

  63:     private static void OnHandleMouseDoubleClickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

  64:     {

  65:         Control control = d as Control;

  66:         if (control != null)

  67:         {

  68:             if ((bool)e.NewValue)

  69:                 control.MouseDoubleClick += ControlMouseDoubleClick;

  70:             else

  71:                 control.MouseDoubleClick -= ControlMouseDoubleClick;

  72:         }

  73:     }

  74:  

  75:     static void ControlMouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)

  76:     {

  77:         SetLastMouseEventArgs((DependencyObject)sender, e);

  78:     }

  79:  

  80:     #endregion

  81: }

then you can use that value as the CommandParameter… Something like this

   1: <ListBox ItemsSource="{Binding Data}" 

   2:         local:MouseEventArgsHandler.HandleMouseDoubleClick="True"

   3:         acb:CommandBehavior.Event="MouseDoubleClick" 

   4:         acb:CommandBehavior.Command="{Binding DoIt}"

   5:         acb:CommandBehavior.CommandParameter="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(local:MouseEventArgsHandler.LastMouseEventArgs)}" />

 

and here is what the command in the ViewModel looks like

   1: DoIt = new SimpleCommand

   2: {

   3:     ExecuteDelegate = x =>

   4:         {

   5:             //set the event as handled

   6:             ((MouseButtonEventArgs)x).Handled = true;

   7:             System.Diagnostics.Debug.WriteLine("Event handled");

   8:         }

   9: };

 

The down side of this is that you have to do an attached property for every different event you want to handle, yet at the same time this should be a rare case and if it is not then you should really re think what you are doing with MVVM.

 

Hope it helps :)

I created a demo project for anyone that wants to have a look.

July 3, 2009 - Posted by marlongrech | WPF | | 10 Comments

10 Comments »

  1. Great stuff! ;)
    As soon as I saw your code on OnHandleMouseDoubleClickChanged I thought “Of course! Silly me!”. But that’s always the way, isn’t it? :D

    Comment by Sergi Díaz | July 6, 2009 | Reply

  2. hehe…. yea it happens to all of us :D

    Comment by marlon grech | July 6, 2009 | Reply

  3. Ouch! I’ve found a nasty problem.

    This only works if the dependency properties are created internally in a specific order. local:MouseEventArgsHandler.HandleMouseDoubleClick=”True” must be executed before any CommandBehaviour dependency property, otherwise the command won’t have access to MouseEventArgsHandler.LastMouseEventArgs, since subscriptions to events are executed in the same order you subscribed to them.
    The problem, of course, is that this is XAML and you can’t control in which order they execute. I thought that the xaml parser would do it in the same order you declare them, but this doesn’t seem to be the case :( .

    Comment by Sergi Díaz | July 6, 2009 | Reply

  4. I’ve fixed it using a Trigger. When MouseEventArgsHandler.HandleMouseDoubleClick is True, then I set the CommandBehaviour properties in setters, this way I’m sure about the order they get defined.

    Comment by Sergi Díaz | July 6, 2009 | Reply

  5. mmm… that is very true….

    A safer way to do it is to forget the LastMouseEventArgs and instead in the HandleMouseDoubleClick get the command and call it directly.

    I created a sample for you. here it is.
    http://cid-96f8d49aa44c79c1.skydrive.live.com/self.aspx/Public/CommandForEventSamplev2.rar

    Comment by marlon grech | July 6, 2009 | Reply

  6. Thanks marlon.

    I see you’ve been working on… something that was needed ;) .

    Comment by Sergi Díaz | July 7, 2009 | Reply

  7. Great stuff indeed !

    The same is really neat BUT for some reason where I use it in own MV-VM CommandBehavior.GetCommand() returns null in ControlMouseDoubleClick(object sender …) as if the sender had lost trace of the command somewhere.

    Any idea of what happens ?

    Comment by Olivier REIX | September 10, 2009 | Reply

  8. Also : I use it in a ListView, not a ListBox, if that can help (only difference I see).

    Comment by Olivier REIX | September 10, 2009 | Reply

  9. send me an email with a sample project that I can try out.. my mail is marlongrech AT gmail DOT com… translate that to an email (done that because of spam readers)

    Comment by marlongrech | September 10, 2009 | Reply

  10. ThX. I found the pb. I was setting a different DataContext for my listview and the command was never properly registred.

    Comment by Olivier REIX | September 15, 2009 | Reply


Leave a comment