Marcel B
Marcel B

Reputation: 3664

How to use the CommandManager and still be able to trigger the ICommand.CanExecuteChanged event manually i.e. explicitely?

I have written myself a SingleExecutionCommand (I’m not sure about that name yet. Feel free to suggest an other, but that’s not my question), which doesn’t allow a button to be pressed again before the first execution is finished. Well, at least that’s the plan.
I want to use the CommandManager to handle my CanExecuteChanged-Event, but I also need to trigger this event on my own whenever I change my ExecutionIsRunning-Flag.

This is the Code of my Command:

public class SingleExecutionCommand<T> : DelegateCommand<T>
{

    protected readonly Func<T, Task> AwaitableExecuteDelegate;

    public bool ExecutionIsRunning { get; protected set; }

    public SingleExecutionCommand(Func<T, Task> awaitableExecute, Predicate<T> canExecute = null) :
        base(null, canExecute)
    {
        AwaitableExecuteDelegate = awaitableExecute;
    }

    public SingleExecutionCommand(Action<T> execute, Predicate<T> canExecute = null)
        : base(execute, canExecute)
    {
        AwaitableExecuteDelegate = null;
    }        

    public async override void Execute(object parameter)
    {
        if (parameter != null && !(parameter is T)) throw new ArgumentException("Command Parameter has the wrong type.");

        if (AwaitableExecuteDelegate == null)
        {
            ExecutionIsRunning = true;
            base.Execute(parameter);
            ExecutionIsRunning = false;
        }
        else
        {
            ExecutionIsRunning = true;
            await AwaitableExecuteDelegate((T)parameter);
            ExecutionIsRunning = false;
        }
    }

    public override bool CanExecute(object parameter)
    {
        return (!ExecutionIsRunning) && base.CanExecute(parameter);
    }
}

If tried to do this in my DelegateCommand<T>-Class:

protected void FireCanExcuteChangedEvent()
{
    if (CanExecuteChanged == null) return;

    CanExecuteChanged(this, EventArgs.Empty);
}

but that doesn’t work, because that event is a rerouted event (to the CommandManager.RequerySuggested) and not a real event. So, how can I use both, the CommandManager and my own event?

Edit:
The CanExecuteChanged-event looks like this:

public event EventHandler CanExecuteChanged
{
    add
    {
        CommandManager.RequerySuggested += value;
    }
    remove
    {
        CommandManager.RequerySuggested -= value;
    }
}

Upvotes: 3

Views: 1875

Answers (1)

BionicCode
BionicCode

Reputation: 28968

Since the handlers are assigned/delegated to the CommandManager.RequerySuggested event, I suggest to call CommandManager.InvalidateRequerySuggested(). This will raise the CanExecuteChanged event to signal that the conditions regarding the validity of a command have changed. All listeners will now reevaluate if they can execute the command by responding with a CanExecute() call. If the result is false the command source usually will be disabled.

Your fixed FireCanExcuteChangedEvent() would look as followed:

protected virtual void FireCanExcuteChangedEvent()
{
  CommandManager.InvalidateRequerySuggested();
}

Note that will raise the CanExecuteCHanged event globally i.e. on every ICommand instance (where the CanExecuteCHanged event invocation is delegated to the CommandManager). If performance is a concern, consider to raise CanExecuteChanged explicitly for each instance (see example below).

An improved implementation would introduce a dedicated internal event delegate. This delegate will accumulate the event handlers of the CanExecuteChanegd subscribers in addition to delegating them to the CommandManager.RequerySuggested event.
This way you have both options:

  • notify event observers based on the global CommandManager.RequerySuggested event
  • notify based on the local i.e. instance event version of CanExecuteChanged (independent of CommandManager.RequerySuggested).

The following example also allows to disable the CommandManager based invocation of the CanExecuteChanged event.
This way the command must be explicitly invalidated by calling the public InvalidateCommand() method:

public class RelayCommand : ICommand
{   
  // Optional feature:
  // Allow to bypass the CommandManager completely 
  // to use explicit command invalidation only (via the public InvalidateCommand method).
  // Set from constructor.
  public bool IsCommandManagerInvalidateRequeryEnabled { get; }

  // Store CanExecuteChanged event handlers 
  // to invoke them independent from the CommandManager
  private EventHandler canExecuteChangedDelegate;

  public RelayCommand(bool isCommandManagerInvalidateRequeryEnabled)
  {
    this.IsCommandManagerInvalidateRequeryEnabled = isCommandManagerInvalidateRequeryEnabled ;
  }

  // Invalidate this command explicitly.
  // Raises the ICommand.CanExecuteChanged event of this instance.
  public void InvalidateCommand() => OnCanExecuteChanged();

  /// Raises the ICommand.CanExecuteChanged event.
  protected virtual void OnCanExecuteChanged() 
    => this.canExecuteChangedDelegate?.Invoke(this, EventArgs.Empty);

  // Explicit event implementation
  public event EventHandler CanExecuteChanged
  {
    add
    {
      if (this.IsCommandManagerInvalidateRequeryEnabled)
      {
        // Delegate the CanExecuteChanged event invocation to the CommandManager
        CommandManager.RequerySuggested += value;
      }

      // Accumulate handlers on a local delegate 
      // to allow raising the CanExecuteChanged event independent of the CommandManager
      this.canExecuteChangedDelegate += value;
    }
    remove
    {
      CommandManager.RequerySuggested -= value;
      this.canExecuteChangedDelegate -= value;
    }
  }
}

Upvotes: 4

Related Questions