// Copyright (c) 2012-2016, Yves Goergen, http://unclassified.software/source/delegatecommand // // Copying and distribution of this file, with or without modification, are permitted provided the // copyright notice and this notice are preserved. This file is offered as-is, without any warranty. using System; using System.Collections.Generic; using System.Diagnostics; using System.Windows; using System.Windows.Input; using System.Windows.Threading; namespace Unclassified.UI { /// /// Provides an implementation which relays the Execute and CanExecute /// method to the specified delegates. /// public class DelegateCommand : ICommand { #region Static disabled command /// /// A instance that does nothing and can never be executed. /// public static readonly DelegateCommand Disabled = new DelegateCommand(() => { }) { IsEnabled = false }; #endregion Static disabled command #region Private data private readonly Action execute; private readonly Func canExecute; private List weakHandlers; private bool? isEnabled; private bool raiseCanExecuteChangedPending; #endregion Private data #region Constructors /// /// Initializes a new instance of the class. /// /// Delegate to execute when Execute is called on the command. /// The execute argument must not be null. public DelegateCommand(Action execute) : this(execute != null ? p => execute() : (Action)null, (Func)null) { } /// /// Initializes a new instance of the class. /// /// Delegate to execute when Execute is called on the command. /// Delegate to execute when CanExecute is called on the command. /// The execute argument must not be null. public DelegateCommand(Action execute, Func canExecute) : this(execute != null ? p => execute() : (Action)null, canExecute != null ? p => canExecute() : (Func)null) { } /// /// Initializes a new instance of the class. /// /// Delegate to execute when Execute is called on the command. /// Delegate to execute when CanExecute is called on the command. /// The execute argument must not be null. public DelegateCommand(Action execute, Func canExecute) : this(execute != null ? p => execute() : (Action)null, canExecute) { } /// /// Initializes a new instance of the class. /// /// Delegate to execute when Execute is called on the command. /// The execute argument must not be null. public DelegateCommand(Action execute) : this(execute, (Func)null) { } /// /// Initializes a new instance of the class. /// /// Delegate to execute when Execute is called on the command. /// Delegate to execute when CanExecute is called on the command. /// The execute argument must not be null. public DelegateCommand(Action execute, Func canExecute) : this(execute, canExecute != null ? p => canExecute() : (Func)null) { } /// /// Initializes a new instance of the class. /// /// Delegate to execute when Execute is called on the command. /// Delegate to execute when CanExecute is called on the command. /// The execute argument must not be null. public DelegateCommand(Action execute, Func canExecute) { if (execute == null) throw new ArgumentNullException(nameof(execute)); this.execute = execute; this.canExecute = canExecute; } #endregion Constructors #region CanExecuteChanged event /// /// Occurs when changes occur that affect whether or not the command should execute. /// public event EventHandler CanExecuteChanged { add { if (weakHandlers == null) { weakHandlers = new List(new[] { new WeakReference(value) }); } else { weakHandlers.Add(new WeakReference(value)); } } remove { if (weakHandlers == null) return; for (int i = weakHandlers.Count - 1; i >= 0; i--) { WeakReference weakReference = weakHandlers[i]; EventHandler handler = weakReference.Target as EventHandler; if (handler == null || handler == value) { weakHandlers.RemoveAt(i); } } } } /// /// Raises the event. /// [DebuggerStepThrough] public void RaiseCanExecuteChanged() => OnCanExecuteChanged(); /// /// Raises the event after all other processing has /// finished. Multiple calls to this function before the asynchronous action has been /// started are ignored. /// [DebuggerStepThrough] public void RaiseCanExecuteChangedAsync() { if (!raiseCanExecuteChangedPending) { // Don't do anything if not on the UI thread. The dispatcher event will never be // fired there and probably there's nobody interested in changed properties anyway // on that thread. if (Dispatcher.CurrentDispatcher == Application.Current.Dispatcher) { Dispatcher.CurrentDispatcher.BeginInvoke((Action)OnCanExecuteChanged, DispatcherPriority.Loaded); raiseCanExecuteChangedPending = true; } } } /// /// Raises the event. /// [DebuggerStepThrough] protected virtual void OnCanExecuteChanged() { raiseCanExecuteChangedPending = false; PurgeWeakHandlers(); if (weakHandlers == null) return; WeakReference[] handlers = weakHandlers.ToArray(); foreach (WeakReference reference in handlers) { EventHandler handler = reference.Target as EventHandler; handler?.Invoke(this, EventArgs.Empty); } } [DebuggerStepThrough] private void PurgeWeakHandlers() { if (weakHandlers == null) return; for (int i = weakHandlers.Count - 1; i >= 0; i--) { if (!weakHandlers[i].IsAlive) { weakHandlers.RemoveAt(i); } } if (weakHandlers.Count == 0) weakHandlers = null; } #endregion CanExecuteChanged event #region ICommand methods /// /// Defines the method that determines whether the command can execute in its current state. /// /// Data used by the command. If the command does not require data to be passed, this object can be set to null. /// true if this command can be executed; otherwise, false. [DebuggerStepThrough] public bool CanExecute(object parameter) => isEnabled ?? canExecute?.Invoke(parameter) ?? true; /// /// Convenience method that invokes CanExecute without parameters. /// /// true if this command can be executed; otherwise, false. [DebuggerStepThrough] public bool CanExecute() => CanExecute(null); /// /// Defines the method to be called when the command is invoked. /// /// Data used by the command. If the command does not require data to be passed, this object can be set to null. /// The method returns false. [DebuggerStepThrough] public void Execute(object parameter) { if (!CanExecute(parameter)) { throw new InvalidOperationException("The command cannot be executed because CanExecute returned false."); } execute(parameter); } /// /// Convenience method that invokes the command without parameters. /// /// The method returns false. [DebuggerStepThrough] public void Execute() => Execute(null); /// /// Invokes the command if the method returns true. /// /// Data used by the command. If the command does not require data to be passed, this object can be set to null. /// true if this command was executed; otherwise, false. public bool TryExecute(object parameter) { if (CanExecute(parameter)) { Execute(parameter); return true; } return false; } /// /// Convenience method that invokes the command without parameters if the /// method returns true. /// /// true if this command was executed; otherwise, false. [DebuggerStepThrough] public bool TryExecute() => TryExecute(null); #endregion ICommand methods #region Enabled state /// /// Gets or sets a value indicating whether the current DelegateCommand is enabled. If this /// value is not null, it takes precedence over the canExecute function was passed in the /// constructor. If no function was passed and this value is null the command is enabled. /// public bool? IsEnabled { [DebuggerStepThrough] get { return isEnabled; } [DebuggerStepThrough] set { if (value != isEnabled) { isEnabled = value; RaiseCanExecuteChanged(); } } } #endregion Enabled state } }