Skip to content

Instantly share code, notes, and snippets.

@ReedCopsey
Created February 12, 2015 18:37
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ReedCopsey/ba11d8d691f27ca2710f to your computer and use it in GitHub Desktop.
Save ReedCopsey/ba11d8d691f27ca2710f to your computer and use it in GitHub Desktop.
/// <summary>
/// An <see cref="ICommand"/> whose delegates do not take any parameters for <see cref="Execute"/> and <see cref="CanExecute"/>.
/// </summary>
public class ActionCommand : DelegateCommandBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ActionCommand"/> class with the <see cref="Action"/> to invoke on execution.
/// </summary>
/// <param name="executeMethod">The execute method.</param>
/// <param name="useCommandManager">if set to <c>true</c> use the command manager instead of our own event process for CanExecuteChanged Tracking.</param>
public ActionCommand(Action executeMethod, bool useCommandManager = false)
: this(executeMethod, () => true, useCommandManager)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ActionCommand"/> class with the <see cref="Action"/> to invoke on execution
/// and a <see langword="Func" /> to query for determining if the command can execute.
/// </summary>
/// <param name="executeMethod">The <see cref="Action"/> to invoke when <see cref="ICommand.Execute"/> is called.</param>
/// <param name="canExecuteMethod">The <see cref="Func{TResult}"/> to invoke when <see cref="ICommand.CanExecute"/> is called</param>
/// <param name="useCommandManager">if set to <c>true</c> use the command manager instead of our own event process for CanExecuteChanged Tracking.</param>
public ActionCommand(Action executeMethod, Func<bool> canExecuteMethod, bool useCommandManager = false)
: base(o => executeMethod(), o => canExecuteMethod(), useCommandManager)
{
if (executeMethod == null || canExecuteMethod == null)
{
throw new ArgumentNullException("executeMethod", "Execute method cannot be null");
}
}
/// <summary>
/// Executes the command.
/// </summary>
public void Execute()
{
this.Execute(null);
}
/// <summary>
/// Determines if the command can be executed.
/// </summary>
/// <returns>Returns <see langword="true"/> if the command can execute,otherwise returns <see langword="false"/>.</returns>
public bool CanExecute()
{
return this.CanExecute(null);
}
}
/// <summary>
/// An <see cref="ICommand"/> whose delegates can be attached for <see cref="Execute"/> and <see cref="CanExecute"/>.
/// </summary>
/// <typeparam name="T">Parameter type.</typeparam>
/// <remarks>
/// The constructor deliberately prevent the use of value types.
/// Because ICommand takes an object, having a value type for T would cause unexpected behavior when CanExecute(null) is called during XAML initialization for command bindings.
/// Using default(T) was considered and rejected as a solution because the implementor would not be able to distinguish between a valid and defaulted values.
/// <para/>
/// Instead, callers should support a value type by using a nullable value type and checking the HasValue property before using the Value property.
/// <example>
/// <code>
/// public MyClass()
/// {
/// this.submitCommand = new DelegateCommand&lt;int?&gt;(this.Submit, this.CanSubmit);
/// }
///
/// private bool CanSubmit(int? customerId)
/// {
/// return (customerId.HasValue &amp;&amp; customers.Contains(customerId.Value));
/// }
/// </code>
/// </example>
/// </remarks>
public class DelegateCommand<T> : DelegateCommandBase
{
/// <summary>
/// Initializes a new instance of the <see cref="DelegateCommand{T}"/> class.
/// </summary>
/// <param name="executeMethod">Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.</param>
/// <param name="useCommandManager">if set to <c>true</c> use the command manager instead of our own event process for CanExecuteChanged Tracking.</param>
/// <remarks><seealso cref="CanExecute"/> will always return true.</remarks>
public DelegateCommand(Action<T> executeMethod, bool useCommandManager = false)
: this(executeMethod, o => true, useCommandManager)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DelegateCommand{T}"/> class.
/// </summary>
/// <param name="executeMethod">Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.</param>
/// <param name="canExecuteMethod">Delegate to execute when CanExecute is called on the command. This can be null.</param>
/// <param name="useCommandManager">if set to <c>true</c> use the command manager instead of our own event process for CanExecuteChanged Tracking.</param>
/// <exception cref="ArgumentNullException">When both <paramref name="executeMethod"/> and <paramref name="canExecuteMethod"/> ar <see langword="null" />.</exception>
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod, bool useCommandManager = false)
: base((o) => executeMethod((T)o), o => canExecuteMethod((T)o), useCommandManager)
{
if (executeMethod == null || canExecuteMethod == null)
{
throw new ArgumentNullException("executeMethod", "Execute method cannot be null");
}
Type genericType = typeof(T);
// DelegateCommand allows object or Nullable<>.
// note: Nullable<> is a struct so we cannot use a class constraint.
if (genericType.IsValueType)
{
if ((!genericType.IsGenericType) || (!typeof(Nullable<>).IsAssignableFrom(genericType.GetGenericTypeDefinition())))
{
throw new InvalidCastException("Only reference or nullable types are supported.");
}
}
}
/// <summary>
/// Determines if the command can execute by invoked the <see cref="Func{T,Bool}"/> provided during construction.
/// </summary>
/// <param name="parameter">Data used by the command to determine if it can execute.</param>
/// <returns>
/// <see langword="true" /> if this command can be executed; otherwise, <see langword="false" />.
/// </returns>
public bool CanExecute(T parameter)
{
return this.CanExecute(parameter);
}
/// <summary>
/// Executes the command and invokes the <see cref="Action{T}"/> provided during construction.
/// </summary>
/// <param name="parameter">Data used by the command.</param>
public void Execute(T parameter)
{
this.Execute(parameter);
}
}
/// <summary>
/// An <see cref="ICommand"/> whose delegates can be attached for <see cref="Execute"/> and <see cref="CanExecute"/>.
/// </summary>
public abstract class DelegateCommandBase : IDelegateCommand
{
/// <summary>
/// The execute method
/// </summary>
private readonly Action<object> executeMethod;
/// <summary>
/// The can execute method
/// </summary>
private readonly Func<object, bool> canExecuteMethod;
/// <summary>
/// Flag whether to use the command manager, or our own event handling
/// </summary>
private readonly bool useCommandManager;
/// <summary>
/// Initializes a new instance of the DelegateCommandBase class, specifying both the execute action and the can execute function.
/// </summary>
/// <param name="executeMethod">The <see cref="Action"/> to execute when <see cref="ICommand.Execute"/> is invoked.</param>
/// <param name="canExecuteMethod">The <see cref="Func{Object,Bool}"/> to invoked when <see cref="ICommand.CanExecute"/> is invoked.</param>
/// <param name="useCommandManager">if set to <c>true</c> [use command manager].</param>
protected DelegateCommandBase(Action<object> executeMethod, Func<object, bool> canExecuteMethod, bool useCommandManager)
{
if (executeMethod == null || canExecuteMethod == null)
{
throw new ArgumentNullException("executeMethod", "Execute method cannot be null");
}
this.executeMethod = executeMethod;
this.canExecuteMethod = canExecuteMethod;
this.useCommandManager = useCommandManager;
}
/// <summary>
/// Occurs when changes occur that affect whether or not the command should execute. You must keep a hard
/// reference to the handler to avoid garbage collection and unexpected results.
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
if (this.useCommandManager)
{
CommandManager.RequerySuggested += value;
}
else
{
CanExecuteChangedWeakEventManager.AddHandler(this, value);
}
}
remove
{
if (this.useCommandManager)
{
CommandManager.RequerySuggested -= value;
}
else
{
CanExecuteChangedWeakEventManager.RemoveHandler(this, value);
}
}
}
private event EventHandler CanExecuteChangedInternal;
/// <summary>
/// Raises <see cref="DelegateCommandBase.CanExecuteChanged"/> on the UI thread so every command invoker
/// can requery to check if the command can execute.
/// <remarks>Note that this will trigger the execution of <see cref="DelegateCommandBase.CanExecute"/> once for each invoker.</remarks>
/// </summary>
public void RaiseCanExecuteChanged()
{
this.OnCanExecuteChanged();
}
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
void ICommand.Execute(object parameter)
{
this.Execute(parameter);
}
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
/// <returns>
/// true if this command can be executed; otherwise, false.
/// </returns>
bool ICommand.CanExecute(object parameter)
{
return this.CanExecute(parameter);
}
/// <summary>
/// Raises <see cref="ICommand.CanExecuteChanged"/> on the UI thread so every
/// command invoker can requery <see cref="ICommand.CanExecute"/> to check if the
/// ICommand can execute.
/// </summary>
protected virtual void OnCanExecuteChanged()
{
if (this.useCommandManager)
{
CommandManager.InvalidateRequerySuggested();
}
else
{
var handler = this.CanExecuteChangedInternal;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
/// <summary>
/// Executes the command with the provided parameter by invoking the <see cref="Action{Object}"/> supplied during construction.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
protected void Execute(object parameter)
{
this.executeMethod(parameter);
}
/// <summary>
/// Determines if the command can execute with the provided parameter by invoing the <see cref="Func{Object,Bool}"/> supplied during construction.
/// </summary>
/// <param name="parameter">The parameter to use when determining if this command can execute.</param>
/// <returns>Returns <see langword="true"/> if the command can execute. <see langword="False"/> otherwise.</returns>
protected bool CanExecute(object parameter)
{
return this.canExecuteMethod == null || this.canExecuteMethod(parameter);
}
/// <summary>
/// Internal WeakEventManager to handle the events in a weak way
/// </summary>
private class CanExecuteChangedWeakEventManager : WeakEventManager
{
private CanExecuteChangedWeakEventManager()
{
}
/// <summary>
/// Get the event manager for the current thread.
/// </summary>
private static CanExecuteChangedWeakEventManager CurrentManager
{
get
{
Type managerType = typeof(CanExecuteChangedWeakEventManager);
CanExecuteChangedWeakEventManager manager =
(CanExecuteChangedWeakEventManager)GetCurrentManager(managerType);
// at first use, create and register a new manager
if (manager == null)
{
manager = new CanExecuteChangedWeakEventManager();
WeakEventManager.SetCurrentManager(managerType, manager);
}
return manager;
}
}
/// <summary>
/// Add a handler for the given source's event.
/// </summary>
public static void AddHandler(DelegateCommandBase source, EventHandler handler)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (handler == null)
{
throw new ArgumentNullException("handler");
}
CurrentManager.ProtectedAddHandler(source, handler);
}
/// <summary>
/// Remove a handler for the given source's event.
/// </summary>
public static void RemoveHandler(DelegateCommandBase source, EventHandler handler)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (handler == null)
{
throw new ArgumentNullException("handler");
}
CurrentManager.ProtectedRemoveHandler(source, handler);
}
/// <summary>
/// Return a new list to hold listeners to the event.
/// </summary>
protected override ListenerList NewListenerList()
{
return new ListenerList();
}
/// <summary>
/// Listen to the given source for the event.
/// </summary>
protected override void StartListening(object source)
{
DelegateCommandBase typedSource = (DelegateCommandBase)source;
typedSource.CanExecuteChangedInternal += this.OnCanExecuteChangedInternal;
}
/// <summary>
/// Stop listening to the given source for the event.
/// </summary>
protected override void StopListening(object source)
{
DelegateCommandBase typedSource = (DelegateCommandBase)source;
typedSource.CanExecuteChangedInternal -= this.OnCanExecuteChangedInternal;
}
/// <summary>
/// Event handler for the SomeEvent event.
/// </summary>
private void OnCanExecuteChangedInternal(object sender, EventArgs e)
{
this.DeliverEvent(sender, e);
}
}
}
/// <summary>
/// Interface to extend a command and allow you to handle raising CanExecuteChanged
/// </summary>
public interface IDelegateCommand : ICommand
{
/// <summary>
/// Raises <see cref="ICommand.CanExecuteChanged"/>
/// <remarks>Note that this will trigger the execution of <see cref="ICommand.CanExecute"/> once for each invoker.</remarks>
/// </summary>
void RaiseCanExecuteChanged();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment