Reputation: 3664
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
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:
CommandManager.RequerySuggested
eventCanExecuteChanged
(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