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:

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; }