// 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
}
}