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:
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: 11: 12: 13: public static class SelectorDoubleClickCommandBehavior
14: {
15: #region Attached DPs
16: #region HandleDoubleClick
17:
18: 19: 20: 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: 32: 33: 34: public static bool GetHandleDoubleClick(DependencyObject d)
35: {
36: return (bool)d.GetValue(HandleDoubleClickProperty);
37: }
38:
39: 40: 41: 42: public static void SetHandleDoubleClick(DependencyObject d,
43: bool value)
44: {
45: d.SetValue(HandleDoubleClickProperty, value);
46: }
47:
48: 49: 50: 51: 52: 53: 54: 55: 56: 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: 71: 72: 73: selector.MouseDoubleClick += OnMouseDoubleClick;
74: }
75: }
76: }
77: #endregion
78:
79: #region TheCommandToRun
80:
81: 82: 83: 84: public static readonly DependencyProperty TheCommandToRunProperty =
85: DependencyProperty.RegisterAttached(
86: "TheCommandToRun",
87: typeof(ICommand),
88: typeof(SelectorDoubleClickCommandBehavior),
89: new FrameworkPropertyMetadata((ICommand)null));
90:
91: 92: 93: 94: public static ICommand GetTheCommandToRun(DependencyObject d)
95: {
96: return (ICommand)d.GetValue(TheCommandToRunProperty);
97: }
98:
99: 100: 101: 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: 114: 115: 116: 117: 118: 119: 120: 121: 122: private static void OnMouseDoubleClick(object sender,
123: MouseButtonEventArgs e)
124: {
125: 126: 127: 128: 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: 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:
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:
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: 13: 14: 15: 16: public class InteractionsSelectorDoubleClickCommandAction :
17: TargetedTriggerAction<FrameworkElement>,
18: ICommandSource
19: {
20: #region DPs
21:
22: #region Command DP
23: 24: 25: 26: 27: 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: 54: 55: 56: 57: 58: 59: 60: 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: 84: 85: 86: 87: 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: 126: 127: 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: 146: 147: 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: 166: 167: 168: 169: 170: 171: [Category("Command Properties")]
172: public bool SyncOwnerIsEnabled
173: {
174: get { return (bool)GetValue(SyncOwnerIsEnabledProperty); }
175: set { SetValue(SyncOwnerIsEnabledProperty, value); }
176: }
177:
178: 179: 180: 181: 182: 183: 184: 185: 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: 198: 199: 200: 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: 212: 213: 214: 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: 226: protected override void Invoke(object parameter)
227: {
228: 229: 230: 231: 232: 233: }
234: #endregion
235:
236: #region Private Methods
237:
238: 239: 240: 241: 242: 243: 244: 245: 246: 247: private static void OnMouseDoubleClick(object sender,
248: MouseButtonEventArgs e)
249: {
250: 251: 252: 253: 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: 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:
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: