Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / WPF

Selector DoubleClick Behaviour calling ViewModel ICommand

Rate me:
Please Sign up or sign in to vote.
4.83/5 (7 votes)
8 Sep 2009CPOL 55.9K   15   17
The other day I was at work and needed to use a WPF ListView (Selector) to call a ICommand in a ViewModel. Here is what I did.

The other day I was at work and needed to use a WPF ListView (Selector) to call an ICommand in a ViewModel. Nown we want to be good and use nice design approaches, so I thought about using the attached command capabilities of my Cinch MVVM framework. But then I thought, ah, I only want to call the ICommand when the user actually double clicks an Item in the ListView (Selector) and not when the double click occurs anywhere else, like say a header, which the ListView certainly has.

So without further ado, I set to work and came up with the following attached Behaviour DP:

C#
  1:  using System;
  2:  using System.Collections.Generic;
  3:  using System.Windows;
  4:  using System.Windows.Controls.Primitives;
  5:  using System.Windows.Input;
  6:  using System.Windows.Controls;
  7:
  8:  namespace ListViewDoubleCLick
  9:  {
 10:      /// <summary>
 11:      /// Selector MouseDoubleClick calling ViewModel ICommand
 12:      /// </summary>
 13:      public static class SelectorDoubleClickCommandBehavior
 14:      {
 15:          #region Attached DPs
 16:          #region HandleDoubleClick
 17:
 18:          /// <summary>
 19:          /// HandleDoubleClick Attached Dependency Property
 20:          /// </summary>
 21:          public static readonly DependencyProperty
 22:              HandleDoubleClickProperty =
 23:              DependencyProperty.RegisterAttached(
 24:              "HandleDoubleClick",
 25:              typeof(bool),
 26:              typeof(SelectorDoubleClickCommandBehavior),
 27:                  new FrameworkPropertyMetadata(false,
 28:                      new PropertyChangedCallback(
 29:                          OnHandleDoubleClickChanged)));
 30:
 31:          /// <summary>
 32:          /// Gets the HandleDoubleClick property.
 33:          /// </summary>
 34:          public static bool GetHandleDoubleClick(DependencyObject d)
 35:          {
 36:              return (bool)d.GetValue(HandleDoubleClickProperty);
 37:          }
 38:
 39:          /// <summary>
 40:          /// Sets the HandleDoubleClick property.
 41:          /// </summary>
 42:          public static void SetHandleDoubleClick(DependencyObject d,
 43:              bool value)
 44:          {
 45:              d.SetValue(HandleDoubleClickProperty, value);
 46:          }
 47:
 48:          /// <summary>
 49:          /// Hooks up a weak event against the source Selectors
 50:          /// MouseDoubleClick if the Selector has asked for
 51:          /// the HandleDoubleClick to be handled
 52:          ///
 53:          /// If the source Selector has expressed an interest
 54:          /// in not having its MouseDoubleClick handled
 55:          /// the internal reference
 56:          /// </summary>
 57:          private static void OnHandleDoubleClickChanged(
 58:              DependencyObject d,
 59:              DependencyPropertyChangedEventArgs e)
 60:          {
 61:              Selector selector = d as Selector;
 62:
 63:
 64:              if (selector != null)
 65:              {
 66:                  if ((bool)e.NewValue)
 67:                  {
 68:                      selector.MouseDoubleClick -= OnMouseDoubleClick;
 69:
 70:                      //This will cause the MouseButtonEventHandler.Target
 71:                      //to keep a strong reference to the source of the
 72:                      //event, which will stop it from being GCd
 73:                      selector.MouseDoubleClick += OnMouseDoubleClick;
 74:                  }
 75:              }
 76:          }
 77:          #endregion
 78:
 79:          #region TheCommandToRun
 80:
 81:          /// <summary>
 82:          /// TheCommandToRun : The actual ICommand to run
 83:          /// </summary>
 84:          public static readonly DependencyProperty TheCommandToRunProperty =
 85:              DependencyProperty.RegisterAttached(
 86:                  "TheCommandToRun",
 87:                  typeof(ICommand),
 88:                  typeof(SelectorDoubleClickCommandBehavior),
 89:                  new FrameworkPropertyMetadata((ICommand)null));
 90:
 91:          /// <summary>
 92:          /// Gets the TheCommandToRun property.
 93:          /// </summary>
 94:          public static ICommand GetTheCommandToRun(DependencyObject d)
 95:          {
 96:              return (ICommand)d.GetValue(TheCommandToRunProperty);
 97:          }
 98:
 99:          /// <summary>
100:          /// Sets the TheCommandToRun property.
101:          /// </summary>
102:          public static void SetTheCommandToRun(DependencyObject d,
103:              ICommand value)
104:          {
105:              d.SetValue(TheCommandToRunProperty, value);
106:          }
107:          #endregion
108:          #endregion
109:
110:          #region Private Methods
111:
112:
113:          /// <summary>
114:          /// Handle Selector.MouseDoubleClick but will
115:          /// only fire the associated ViewModel command
116:          /// if the MouseDoubleClick occurred over an actual
117:          /// ItemsControl item. This is nessecary as if we
118:          /// are using a ListView we may have clicked the
119:          /// headers which are not items, so do not want the
120:          /// associated ViewModel command to be run
121:          /// </summary>
122:          private static void OnMouseDoubleClick(object sender,
123:              MouseButtonEventArgs e)
124:          {
125:              //Get the ItemsControl and then get the item, and
126:              //check there is an actual item, as if we are using
127:              //a ListView we may have clicked the
128:              //headers which are not items
129:              ItemsControl listView = sender as ItemsControl;
130:              DependencyObject originalSender =
131:                  e.OriginalSource as DependencyObject;
132:              if (listView == null || originalSender == null) return;
133:
134:              DependencyObject container =
135:                  ItemsControl.ContainerFromElement
136:                  (sender as ItemsControl,
137:                  e.OriginalSource as DependencyObject);
138:
139:              if (container == null ||
140:                  container == DependencyProperty.UnsetValue) return;
141:
142:              // found a container, now find the item.
143:              object activatedItem =
144:                  listView.ItemContainerGenerator.
145:                      ItemFromContainer(container);
146:
147:              if (activatedItem != null)
148:              {
149:                  ICommand command =
150:                      (ICommand)(sender as DependencyObject).
151:                      GetValue(TheCommandToRunProperty);
152:
153:                  if (command != null)
154:                  {
155:                      if (command.CanExecute(null))
156:                          command.Execute(null);
157:                  }
158:              }
159:          }
160:          #endregion
161:      }
162:
163:  }

Where we would use this in XAML:

XML
 1:  <Window x:Class="ListViewDoubleCLick.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:ListViewDoubleCLick"
 5:      xmlns:interactivity="clr-       namespace:Microsoft.Expression.Interactivity;
 6:         assembly=Microsoft.Expression.Interactivity"
 7:      Title="Window1" Height="600" Width="800"
 8:      WindowStartupLocation="CenterScreen">
 9:      <Grid>
10:
11:          <TabControl>
12:              <TabItem Header="Attached DP Approach">
13:                  <ListView ItemsSource="{Binding People}"
14:                    IsSynchronizedWithCurrentItem="True"
15:                    local:NaiveSelectorDoubleClickCommandBehavior.HandleDoubleClick="true"
16:                    local:NaiveSelectorDoubleClickCommandBehavior.TheCommandToRun=
17:                            "{Binding Path=DoItCommand}" >
18:
19:                      <ListView.View>
20:                          <GridView>
21:                              <GridViewColumn Header="FirstName"
22:                                      DisplayMemberBinding="{Binding FirstName}"
23:                                      Width="80" />
24:                              <GridViewColumn Header="LastName"
25:                                      DisplayMemberBinding="{Binding LastName}"
26:                                      Width="80"/>
27:                          </GridView>
28:                      </ListView.View>
29:                  </ListView>
30:              </TabItem>
31:          </TabControl>
32:      </Grid>
33:
34:  </Window>

Or, we could get really fancy and use the Blend 3 Microsoft.Expression.Interactivity.Dll, which would look something like this:

C#
  1:  using System;
  2:  using System.Collections.Generic;
  3:  using System.Windows;
  4:  using System.Windows.Controls.Primitives;
  5:  using System.Windows.Input;
  6:  using System.Windows.Controls;
  7:  using Microsoft.Expression.Interactivity;
  8:  using System.ComponentModel;
  9:
 10:  namespace ListViewDoubleCLick
 11:  {
 12:      /// <summary>
 13:      /// Selector MouseDoubleClick calling ViewModel ICommand Behavior
 14:      /// using Blend3 Microsoft.Expression.Interactivity Dll
 15:      /// </summary>
 16:      public class InteractionsSelectorDoubleClickCommandAction :
 17:          TargetedTriggerAction<FrameworkElement>,
 18:          ICommandSource
 19:      {
 20:          #region DPs
 21:
 22:          #region Command DP
 23:          /// <summary>
 24:          /// The actual Command to fire when the
 25:          /// EventTrigger occurs, thus firing this
 26:          /// InteractionsSelectorDoubleClickCommandAction
 27:          /// </summary>
 28:          [Category("Command Properties")]
 29:          public ICommand Command
 30:          {
 31:              get { return (ICommand)GetValue(CommandProperty); }
 32:              set { SetValue(CommandProperty, value); }
 33:          }
 34:
 35:          public static readonly DependencyProperty CommandProperty =
 36:              DependencyProperty.Register(
 37:                  "Command", typeof(ICommand),
 38:                      typeof(InteractionsSelectorDoubleClickCommandAction),
 39:                      new PropertyMetadata(
 40:                          (ICommand)null, OnCommandChanged));
 41:
 42:          private static void OnCommandChanged(DependencyObject d,
 43:              DependencyPropertyChangedEventArgs e)
 44:          {
 45:              var action =
 46:                  (InteractionsSelectorDoubleClickCommandAction)d;
 47:              action.OnCommandChanged((ICommand)e.OldValue,
 48:                  (ICommand)e.NewValue);
 49:          }
 50:
 51:          #region Command implementation
 52:
 53:          /// <summary>
 54:          /// This is a strong reference to the Command.
 55:          /// CanExecuteChanged event handler.
 56:          /// The commanding system uses a weak
 57:          /// reference and if we don’t enforce a
 58:          /// strong reference then the event
 59:          /// handler will be gc’ed.
 60:          /// </summary>
 61:          private EventHandler CanExecuteChangedHandler;
 62:
 63:
 64:
 65:          private void OnCommandChanged(ICommand oldCommand,
 66:              ICommand newCommand)
 67:          {
 68:              if (oldCommand != null)
 69:                  UnhookCommand(oldCommand);
 70:              if (newCommand != null)
 71:                  HookCommand(newCommand);
 72:          }
 73:
 74:          private void UnhookCommand(ICommand command)
 75:          {
 76:              command.CanExecuteChanged -=
 77:                  CanExecuteChangedHandler;
 78:              UpdateCanExecute();
 79:          }
 80:
 81:          private void HookCommand(ICommand command)
 82:          {
 83:              // Save a strong reference to the
 84:              // Command.CanExecuteChanged event handler.
 85:              // The commanding system uses a weak
 86:              // reference and if we don’t save a strong
 87:              // reference then the event handler will be gc’ed.
 88:              CanExecuteChangedHandler =
 89:                  new EventHandler(OnCanExecuteChanged);
 90:              command.CanExecuteChanged
 91:                  += CanExecuteChangedHandler;
 92:              UpdateCanExecute();
 93:          }
 94:
 95:          private void OnCanExecuteChanged(object sender,
 96:              EventArgs e)
 97:          {
 98:              UpdateCanExecute();
 99:          }
100:
101:          private void UpdateCanExecute()
102:          {
103:              if (Command != null)
104:              {
105:                  RoutedCommand command =
106:                       Command as RoutedCommand;
107:                  if (command != null)
108:                      IsEnabled =
109:                          command.CanExecute(
110:                           CommandParameter, CommandTarget);
111:                  else
112:                      IsEnabled =
113:                          Command.CanExecute(CommandParameter);
114:                  if (Target != null && SyncOwnerIsEnabled)
115:                      Target.IsEnabled = IsEnabled;
116:              }
117:          }
118:
119:          #endregion
120:
121:
122:          #endregion
123:
124:          #region CommandParameter DP
125:          /// <summary>
126:          /// For consistency with the Wpf Command pattern
127:          /// </summary>
128:          [Category("Command Properties")]
129:          public object CommandParameter
130:          {
131:              get { return (object)GetValue(
132:                  CommandParameterProperty); }
133:              set { SetValue(CommandParameterProperty, value); }
134:          }
135:
136:          public static readonly DependencyProperty
137:              CommandParameterProperty =
138:              DependencyProperty.Register(
139:                  "CommandParameter", typeof(object),
140:                      typeof(InteractionsSelectorDoubleClickCommandAction),
141:                          new PropertyMetadata());
142:          #endregion
143:
144:          #region CommandTarget DP
145:          /// <summary>
146:          /// For consistency with the Wpf Command pattern
147:          /// </summary>
148:          [Category("Command Properties")]
149:          public IInputElement CommandTarget
150:          {
151:              get { return (IInputElement)GetValue(
152:                  CommandTargetProperty); }
153:              set { SetValue(CommandTargetProperty, value); }
154:          }
155:
156:          public static readonly DependencyProperty
157:              CommandTargetProperty =
158:              DependencyProperty.Register(
159:                  "CommandTarget", typeof(IInputElement),
160:                      typeof(InteractionsSelectorDoubleClickCommandAction),
161:                          new PropertyMetadata());
162:          #endregion
163:
164:          #region SyncOwnerIsEnabled DP
165:          /// <summary>
166:          /// Allows the user to specify that the
167:          /// owner element should be
168:          /// enabled/disabled whenever the
169:          /// action is enabled/disabled.
170:          /// </summary>
171:          [Category("Command Properties")]
172:          public bool SyncOwnerIsEnabled
173:          {
174:              get { return (bool)GetValue(SyncOwnerIsEnabledProperty); }
175:              set { SetValue(SyncOwnerIsEnabledProperty, value); }
176:          }
177:
178:          /// <summary>
179:          /// When SyncOwnerIsEnabled is true
180:          /// then changing
181:          /// InteractionsSelectorDoubleClickCommandAction.
182:          /// IsEnabled
183:          /// will automatically update the owner
184:          /// (Target) IsEnabled property.
185:          /// </summary>
186:          public static readonly DependencyProperty
187:              SyncOwnerIsEnabledProperty =
188:              DependencyProperty.Register(
189:                  "SyncOwnerIsEnabled", typeof(bool),
190:                      typeof(InteractionsSelectorDoubleClickCommandAction),
191:                      new PropertyMetadata());
192:          #endregion
193:
194:          #endregion
195:
196:          #region Overrides
197:          /// <summary>
198:          /// On attached hook up our own MouseDoubleClick so we
199:          /// can check we actually double click an item
200:          /// </summary>
201:          protected override void OnAttached()
202:          {
203:              base.OnAttached();
204:              Selector s = this.AssociatedObject as Selector;
205:              if (s != null)
206:              {
207:                  s.MouseDoubleClick += OnMouseDoubleClick;
208:              }
209:          }
210:
211:          /// <summary>
212:          /// On attached unhook the previously
213:          /// hooked MouseDoubleClick handler
214:          /// </summary>
215:          protected override void OnDetaching()
216:          {
217:              base.OnDetaching();
218:              Selector s = this.AssociatedObject as Selector;
219:              if (s != null)
220:              {
221:                  s.MouseDoubleClick -= OnMouseDoubleClick;
222:              }
223:          }
224:
225:          //Must at least implement abstract member invoke
226:          protected override void Invoke(object parameter)
227:          {
228:              //The logic for this is done in the OnMouseDoubleClick
229:              //as we only wanto fire command if we are actually on an
230:              //Item in the Selector. If the Selector is a ListView we
231:              //may have headers so will not want to fire associated
232:              //Command when a header is double clicked
233:          }
234:          #endregion
235:
236:          #region Private Methods
237:
238:          /// <summary>
239:          /// Handle Selector.MouseDoubleClick but will
240:          /// only fire the associated ViewModel command
241:          /// if the MouseDoubleClick occurred over an actual
242:          /// ItemsControl item. This is nessecary as if we
243:          /// are using a ListView we may have clicked the
244:          /// headers which are not items, so do not want the
245:          /// associated ViewModel command to be run
246:          /// </summary>
247:          private static void OnMouseDoubleClick(object sender,
248:              MouseButtonEventArgs e)
249:          {
250:              //Get the ItemsControl and then get the item, and
251:              //check there is an actual item, as if we are using
252:              //a ListView we may have clicked the
253:              //headers which are not items
254:              ItemsControl listView = sender as ItemsControl;
255:              DependencyObject originalSender =
256:                  e.OriginalSource as DependencyObject;
257:              if (listView == null || originalSender == null) return;
258:
259:              DependencyObject container =
260:                  ItemsControl.ContainerFromElement
261:                  (sender as ItemsControl,
262:                  e.OriginalSource as DependencyObject);
263:
264:              if (container == null ||
265:                  container == DependencyProperty.UnsetValue) return;
266:
267:              // found a container, now find the item.
268:              object activatedItem =
269:                  listView.ItemContainerGenerator.
270:                      ItemFromContainer(container);
271:
272:              if (activatedItem != null)
273:              {
274:                  ICommand command =
275:                      (ICommand)(sender as DependencyObject).
276:                      GetValue(TheCommandToRunProperty);
277:
278:                  if (command != null)
279:                  {
280:                      if (command.CanExecute(null))
281:                          command.Execute(null);
282:                  }
283:              }
284:          }
285:
286:          #endregion
287:      }
288:
289:  }

Which we could use from XAML as follows:

XML
 1:  <Window x:Class="ListViewDoubleCLick.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:ListViewDoubleCLick"
 5:      xmlns:interactivity="clr-namespace:Microsoft.Expression.Interactivity;
                              assembly=Microsoft.Expression.Interactivity"
 6:      Title="Window1" Height="600" Width="800"
 7:      WindowStartupLocation="CenterScreen">
 8:      <Grid>
 9:
10:          <TabControl>
11:               <TabItem Header="Using Blend3 Interactivity Dll" >
12:                  <ListView ItemsSource="{Binding People}"
13:                    IsSynchronizedWithCurrentItem="True">
14:
15:                      <interactivity:Interaction.Triggers>
16:                          <interactivity:EventTrigger EventName="MouseDoubleClick">
17:                              <local:InteractionsSelectorDoubleClickCommandAction
18:                                  Command="{Binding DoItCommand}"
19:                                  SyncOwnerIsEnabled="True" />
20:                          </interactivity:EventTrigger>
21:                      </interactivity:Interaction.Triggers>
22:
23:                      <ListView.View>
24:                          <GridView>
25:                              <GridViewColumn Header="FirstName"
26:                                      DisplayMemberBinding="{Binding FirstName}"
27:                                      Width="80" />
28:                              <GridViewColumn Header="LastName"
29:                                      DisplayMemberBinding="{Binding LastName}"
30:                                      Width="80"/>
31:                          </GridView>
32:                      </ListView.View>
33:                  </ListView>
34:              </TabItem>
35:          </TabControl>
36:
37:
38:
39:      </Grid>
40:
41:  </Window>

As usual, here is a small demo project:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
GeneralArticle Pin
zdebyman25-Oct-12 2:09
zdebyman25-Oct-12 2:09 
QuestionWhat do you mean by 'DoItCommand'? Pin
Bob Brady12-Sep-12 10:24
Bob Brady12-Sep-12 10:24 
AnswerRe: What do you mean by 'DoItCommand'? Pin
Sacha Barber12-Sep-12 19:27
Sacha Barber12-Sep-12 19:27 
AnswerRe: What do you mean by 'DoItCommand'? Pin
Sacha Barber12-Sep-12 19:30
Sacha Barber12-Sep-12 19:30 
GeneralRe: What do you mean by 'DoItCommand'? Pin
Bob Brady13-Sep-12 4:12
Bob Brady13-Sep-12 4:12 
QuestionMicrosoft.Expression.Interactivity Pin
brettles2-Sep-12 21:46
brettles2-Sep-12 21:46 
BugPotential Memory Leak Pin
Clifford Nelson7-Jun-12 8:18
Clifford Nelson7-Jun-12 8:18 
GeneralRe: Potential Memory Leak Pin
Sacha Barber7-Jun-12 9:33
Sacha Barber7-Jun-12 9:33 
When would it get to false. Far better to use weak events see my cinch mvvm framework for example of that
Sacha Barber
  • Microsoft Visual C# MVP 2008-2012
  • Codeproject MVP 2008-2012
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

QuestionImplemented in Cinch? Pin
aaj2318-Apr-12 3:01
aaj2318-Apr-12 3:01 
AnswerRe: Implemented in Cinch? Pin
Sacha Barber18-Apr-12 3:09
Sacha Barber18-Apr-12 3:09 
GeneralDon't see the benefit anymore with so much code for just a doubleclick... Pin
gvdpeer24-Mar-10 10:32
gvdpeer24-Mar-10 10:32 
GeneralRe: Don't see the benefit anymore with so much code for just a doubleclick... Pin
Sacha Barber24-Mar-10 12:00
Sacha Barber24-Mar-10 12:00 
GeneralReally complex for "just" an DoubleClick Pin
ChrDressler13-Dec-09 8:12
ChrDressler13-Dec-09 8:12 
GeneralRe: Really complex for "just" an DoubleClick Pin
Sacha Barber13-Dec-09 10:16
Sacha Barber13-Dec-09 10:16 
GeneralPassing the double clicked item Pin
Matthew Searles29-Oct-09 16:08
Matthew Searles29-Oct-09 16:08 
AnswerRe: Passing the double clicked item Pin
Matthew Searles29-Oct-09 17:17
Matthew Searles29-Oct-09 17:17 
GeneralRe: Passing the double clicked item Pin
Sacha Barber29-Oct-09 20:42
Sacha Barber29-Oct-09 20:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.