WPF Custom Command And Binding
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>