1 using System;
2 using System.Collections.Generic;
3 using System.Windows.Input;
4
5 namespace WPF.Commands
6 {
7 /// <summary>
8 /// This class allows delegating the commanding logic to methods passed as parameters,
9 /// and enables a View to bind commands to objects that are not part of the element tree.
10 /// </summary>
11 public class DelegateCommand : ICommand
12 {
13 #region Constructors
14
15 /// <summary>
16 /// Constructor
17 /// </summary>
18 public DelegateCommand(Action executeMethod)
19 : this(executeMethod, null, false)
20 {
21 }
22
23 /// <summary>
24 /// Constructor
25 /// </summary>
26 public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
27 : this(executeMethod, canExecuteMethod, false)
28 {
29 }
30
31 /// <summary>
32 /// Constructor
33 /// </summary>
34 public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
35 {
36 if (executeMethod == null)
37 {
38 throw new ArgumentNullException("executeMethod");
39 }
40
41 _executeMethod = executeMethod;
42 _canExecuteMethod = canExecuteMethod;
43 _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
44 }
45
46 #endregion
47
48 #region Public Methods
49
50 /// <summary>
51 /// Method to determine if the command can be executed
52 /// </summary>
53 public bool CanExecute()
54 {
55 if (_canExecuteMethod != null)
56 {
57 return _canExecuteMethod();
58 }
59 return true;
60 }
61
62 /// <summary>
63 /// Execution of the command
64 /// </summary>
65 public void Execute()
66 {
67 if (_executeMethod != null)
68 {
69 _executeMethod();
70 }
71 }
72
73 /// <summary>
74 /// Property to enable or disable CommandManager's automatic requery on this command
75 /// </summary>
76 public bool IsAutomaticRequeryDisabled
77 {
78 get
79 {
80 return _isAutomaticRequeryDisabled;
81 }
82 set
83 {
84 if (_isAutomaticRequeryDisabled != value)
85 {
86 if (value)
87 {
88 CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
89 }
90 else
91 {
92 CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
93 }
94 _isAutomaticRequeryDisabled = value;
95 }
96 }
97 }
98
99 /// <summary>
100 /// Raises the CanExecuteChaged event
101 /// </summary>
102 public void RaiseCanExecuteChanged()
103 {
104 OnCanExecuteChanged();
105 }
106
107 /// <summary>
108 /// Protected virtual method to raise CanExecuteChanged event
109 /// </summary>
110 protected virtual void OnCanExecuteChanged()
111 {
112 CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
113 }
114
115 #endregion
116
117 #region ICommand Members
118
119 /// <summary>
120 /// ICommand.CanExecuteChanged implementation
121 /// </summary>
122 public event EventHandler CanExecuteChanged
123 {
124 add
125 {
126 if (!_isAutomaticRequeryDisabled)
127 {
128 CommandManager.RequerySuggested += value;
129 }
130 CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2);
131 }
132 remove
133 {
134 if (!_isAutomaticRequeryDisabled)
135 {
136 CommandManager.RequerySuggested -= value;
137 }
138 CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value);
139 }
140 }
141
142 /// <summary>
143 ///
144 /// </summary>
145 /// <param name="parameter"></param>
146 /// <returns></returns>
147 bool ICommand.CanExecute(object parameter)
148 {
149 return CanExecute();
150 }
151
152 /// <summary>
153 ///
154 /// </summary>
155 /// <param name="parameter"></param>
156 void ICommand.Execute(object parameter)
157 {
158 Execute();
159 }
160
161 #endregion
162
163 #region Data
164 /// <summary>
165 ///
166 /// </summary>
167 private readonly Action _executeMethod = null;
168 /// <summary>
169 ///
170 /// </summary>
171 private readonly Func<bool> _canExecuteMethod = null;
172 /// <summary>
173 ///
174 /// </summary>
175 private bool _isAutomaticRequeryDisabled = false;
176 /// <summary>
177 ///
178 /// </summary>
179 private List<WeakReference> _canExecuteChangedHandlers;
180
181 #endregion
182 }
183
184 /// <summary>
185 /// This class allows delegating the commanding logic to methods passed as parameters,
186 /// and enables a View to bind commands to objects that are not part of the element tree.
187 /// </summary>
188 /// <typeparam name="TExecuteParameter">Type of the parameter passed to the delegates</typeparam>
189 public class DelegateCommand<TExecuteParameter> : ICommand
190 {
191 #region Constructors
192
193 /// <summary>
194 /// Constructor
195 /// </summary>
196 public DelegateCommand(Action<TExecuteParameter> onExecute)
197 : this(onExecute, null, false)
198 {
199 }
200
201 /// <summary>
202 /// Constructor
203 /// </summary>
204 public DelegateCommand(Action<TExecuteParameter> onExecute, Func<TExecuteParameter, bool> canExecute)
205 : this(onExecute, canExecute, false)
206 {
207 }
208
209 /// <summary>
210 /// Constructor
211 /// </summary>
212 public DelegateCommand(Action<TExecuteParameter> onExecute, Func<TExecuteParameter, bool> canExecute, bool isAutomaticRequeryDisabled)
213 {
214 if (onExecute == null)
215 {
216 throw new ArgumentNullException("executeMethod");
217 }
218 _onExecute = onExecute;
219 _canExecute = canExecute;
220 _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
221 }
222
223 #endregion
224
225 #region Public Methods
226
227 /// <summary>
228 /// Method to determine if the command can be executed
229 /// </summary>
230 public bool CanExecute(TExecuteParameter parameter)
231 {
232 if (_canExecute != null)
233 {
234 return _canExecute(parameter);
235 }
236 return true;
237 }
238
239 /// <summary>
240 /// Execution of the command
241 /// </summary>
242 public void Execute(TExecuteParameter parameter)
243 {
244 if (_onExecute != null)
245 {
246 _onExecute(parameter);
247 }
248 }
249
250 /// <summary>
251 /// Raises the CanExecuteChaged event
252 /// </summary>
253 public void RaiseCanExecuteChanged()
254 {
255 OnCanExecuteChanged();
256 }
257
258 /// <summary>
259 /// Protected virtual method to raise CanExecuteChanged event
260 /// </summary>
261 protected virtual void OnCanExecuteChanged()
262 {
263 CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
264 }
265
266 /// <summary>
267 /// Property to enable or disable CommandManager's automatic requery on this command
268 /// </summary>
269 public bool IsAutomaticRequeryDisabled
270 {
271 get
272 {
273 return _isAutomaticRequeryDisabled;
274 }
275 set
276 {
277 if (_isAutomaticRequeryDisabled != value)
278 {
279 if (value)
280 {
281 CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
282 }
283 else
284 {
285 CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
286 }
287 _isAutomaticRequeryDisabled = value;
288 }
289 }
290 }
291
292 #endregion
293
294 #region ICommand Members
295
296 /// <summary>
297 /// ICommand.CanExecuteChanged implementation
298 /// </summary>
299 public event EventHandler CanExecuteChanged
300 {
301 add
302 {
303 if (!_isAutomaticRequeryDisabled)
304 {
305 CommandManager.RequerySuggested += value;
306 }
307 CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2);
308 }
309 remove
310 {
311 if (!_isAutomaticRequeryDisabled)
312 {
313 CommandManager.RequerySuggested -= value;
314 }
315 CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value);
316 }
317 }
318
319 /// <summary>
320 ///
321 /// </summary>
322 /// <param name="parameter"></param>
323 /// <returns></returns>
324 bool ICommand.CanExecute(object parameter)
325 {
326 // if T is of value type and the parameter is not
327 // set yet, then return false if CanExecute delegate
328 // exists, else return true
329 var type = typeof(TExecuteParameter);
330 if (type.IsValueType && type.IsPrimitive)
331 {
332 if (int.TryParse(parameter.ToString(), out int intResult))
333 {
334 return CanExecute((TExecuteParameter)(object)intResult);
335 }
336 }
337
338 if (parameter == null && type.IsValueType)
339 {
340 return (_canExecute == null);
341 }
342 return CanExecute((TExecuteParameter)parameter);
343 }
344
345 /// <summary>
346 ///
347 /// </summary>
348 /// <param name="parameter"></param>
349 void ICommand.Execute(object parameter)
350 {
351 Execute((TExecuteParameter)parameter);
352 }
353
354 #endregion
355
356 #region Data
357 /// <summary>
358 ///
359 /// </summary>
360 private readonly Action<TExecuteParameter> _onExecute = null;
361 /// <summary>
362 ///
363 /// </summary>
364 private readonly Func<TExecuteParameter, bool> _canExecute = null;
365 /// <summary>
366 ///
367 /// </summary>
368 private bool _isAutomaticRequeryDisabled = false;
369 /// <summary>
370 ///
371 /// </summary>
372 private List<WeakReference> _canExecuteChangedHandlers;
373
374 #endregion
375 }
376
377 /// <summary>
378 /// This class contains methods for the CommandManager that help avoid memory leaks by
379 /// using weak references.
380 /// </summary>
381 internal class CommandManagerHelper
382 {
383 internal static void CallWeakReferenceHandlers(List<WeakReference> handlers)
384 {
385 if (handlers != null)
386 {
387 // Take a snapshot of the handlers before we call out to them since the handlers
388 // could cause the array to me modified while we are reading it.
389
390 EventHandler[] callees = new EventHandler[handlers.Count];
391 int count = 0;
392
393 for (int i = handlers.Count - 1; i >= 0; i--)
394 {
395 WeakReference reference = handlers[i];
396 EventHandler handler = reference.Target as EventHandler;
397 if (handler == null)
398 {
399 // Clean up old handlers that have been collected
400 handlers.RemoveAt(i);
401 }
402 else
403 {
404 callees[count] = handler;
405 count++;
406 }
407 }
408
409 // Call the handlers that we snapshotted
410 for (int i = 0; i < count; i++)
411 {
412 EventHandler handler = callees[i];
413 handler(null, EventArgs.Empty);
414 }
415 }
416 }
417
418 /// <summary>
419 ///
420 /// </summary>
421 /// <param name="handlers"></param>
422 internal static void AddHandlersToRequerySuggested(List<WeakReference> handlers)
423 {
424 if (handlers != null)
425 {
426 foreach (WeakReference handlerRef in handlers)
427 {
428 EventHandler handler = handlerRef.Target as EventHandler;
429 if (handler != null)
430 {
431 CommandManager.RequerySuggested += handler;
432 }
433 }
434 }
435 }
436
437 /// <summary>
438 ///
439 /// </summary>
440 /// <param name="handlers"></param>
441 internal static void RemoveHandlersFromRequerySuggested(List<WeakReference> handlers)
442 {
443 if (handlers != null)
444 {
445 foreach (WeakReference handlerRef in handlers)
446 {
447 EventHandler handler = handlerRef.Target as EventHandler;
448 if (handler != null)
449 {
450 CommandManager.RequerySuggested -= handler;
451 }
452 }
453 }
454 }
455
456 /// <summary>
457 ///
458 /// </summary>
459 /// <param name="handlers"></param>
460 /// <param name="handler"></param>
461 internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler)
462 {
463 AddWeakReferenceHandler(ref handlers, handler, -1);
464 }
465
466 /// <summary>
467 ///
468 /// </summary>
469 /// <param name="handlers"></param>
470 /// <param name="handler"></param>
471 /// <param name="defaultListSize"></param>
472 internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler, int defaultListSize)
473 {
474 if (handlers == null)
475 {
476 handlers = (defaultListSize > 0 ? new List<WeakReference>(defaultListSize) : new List<WeakReference>());
477 }
478
479 handlers.Add(new WeakReference(handler));
480 }
481
482 /// <summary>
483 ///
484 /// </summary>
485 /// <param name="handlers"></param>
486 /// <param name="handler"></param>
487 internal static void RemoveWeakReferenceHandler(List<WeakReference> handlers, EventHandler handler)
488 {
489 if (handlers != null)
490 {
491 for (int i = handlers.Count - 1; i >= 0; i--)
492 {
493 WeakReference reference = handlers[i];
494 EventHandler existingHandler = reference.Target as EventHandler;
495 if ((existingHandler == null) || (existingHandler == handler))
496 {
497 // Clean up old handlers that have been collected
498 // in addition to the handler that is to be removed.
499 handlers.RemoveAt(i);
500 }
501 }
502 }
503 }
504 }
505 }
1 <Button Width="60"
2 Height="30"
3 DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
4 Command="{Binding TestCommand}"
5 CommandParameter="123">Test</Button>