MVVM’s command binding has probably been the same for more than a decade. Hence you should take what you read with a grain of salt.
The Problem
Patterns – WPF Apps With The Model-View-ViewModel Design Pattern | Microsoft Docs has a detailed account of the pattern. It also explains the code it suggests to a greater extend. I am onboard with the pattern and the implementation and it is something I have used too many times. However, the usage of CommandManager has always been unsettling to me.
If you do not already know, associating “CommandManager.RequerySuggested” to “CanExecuteChanged” triggers the event for every single data change in the view. Yes, I know I am supposed to keep the “CanExecute” method simple. However, I could not ignore the fact that my methods are executed hundreds of time, while the actual data that drives the change hasn’t changed yet. I wanted the method to fire only when the properties that will change the state has changed. I went on a internet search for hours and yet I wasn’t able to find anything that was simple and effective.
The Solution
So, I wrote my own implementation of ICommand keeping the below as my goals.
- Trigger “CanExecute” only if the data that impacts the change of state changes.
- Have the ability to trigger “CanExecute” if any property in the view-model changes.
- Have the ability to define the state “CanExecute” as singular.
I need two properties to accomplish the above goals.
- The instance of view-model or the object that holds the data will determine the change state for “CanExecute”.
- The properties of the object that determine the change state for “CanExecute”
/// <summary> /// Initializes a new instance of the <see cref="Command{T}"/> class. /// </summary> /// <param name="execute">The execute method.</param> /// <param name="canExecute">The can execute method.</param> /// <param name="objectToListen">The object to listen.</param> /// <param name="propertiesToListen">The properties to listen.</param> public Command(Action<T> execute, Predicate<T> canExecute, INotifyPropertyChanged objectToListen, IEnumerable<string> propertiesToListen) { this.execute = execute; this.canExecute = canExecute; this.objectToListen = objectToListen; if (this.objectToListen != null) { this.objectToListen.PropertyChanged += this.ObjectPropertyChanged; this.PropertiesToListen = propertiesToListen; } }
The above constructor will ensure that I have the ability to get both the object and the properties.
In the below method, I will make the decision to raise the event based on the properties that I get in the constructor. If I didn’t receive any properties, I will listen to all the properties.
/// <summary> /// Listens to any property changed of the object that is passed. /// </summary> /// <param name="sender">The sender.</param> /// <param name="e"> /// The <see cref="PropertyChangedEventArgs"/> instance containing the event data. /// </param> private void ObjectPropertyChanged(object sender, PropertyChangedEventArgs e) { if (this.PropertiesToListen == null || ((this.PropertiesToListen?.Any() ?? false) && this.PropertiesToListen.Contains(e.PropertyName))) { this.RaiseCanExecuteChanged(); } }
The Code
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Windows.Input; using Microsoft.VisualStudio.PlatformUI; /// <summary> /// Class Command. Implements the <see cref="System.Windows.Input.ICommand"/> Implements the /// <see cref="System.IDisposable"/> /// </summary> /// <typeparam name="T">The type of command parameter.</typeparam> /// <seealso cref="System.Windows.Input.ICommand"/> /// <seealso cref="System.IDisposable"/> public class Command<T> : ICommand, IDisposable { /// <summary> /// The can execute /// </summary> private readonly Predicate<T> canExecute; /// <summary> /// The execute /// </summary> private readonly Action<T> execute; /// <summary> /// The is disposed /// </summary> private bool isDisposed; /// <summary> /// The object to listen /// </summary> private INotifyPropertyChanged objectToListen; /// <summary> /// Initializes a new instance of the <see cref="Command{T}"/> class. /// </summary> /// <param name="execute">The execute method.</param> public Command(Action<T> execute) : this(execute, (Predicate<T>)null) { } /// <summary> /// Initializes a new instance of the <see cref="Command{T}"/> class. /// </summary> /// <param name="execute">The execute method.</param> /// <param name="canExecute">The can execute method.</param> public Command(Action<T> execute, Predicate<T> canExecute) : this(execute, canExecute, null, null) { } /// <summary> /// Initializes a new instance of the <see cref="Command{T}"/> class. /// </summary> /// <param name="execute">The execute method.</param> /// <param name="canExecute">The can execute method.</param> /// <param name="objectToListen">The object to listen.</param> /// <param name="propertiesToListen"> /// The properties to listen. Null if all the properties are to be listened. /// </param> public Command(Action<T> execute, Predicate<T> canExecute, INotifyPropertyChanged objectToListen, IEnumerable<string> propertiesToListen = null) { this.execute = execute; this.canExecute = canExecute; this.objectToListen = objectToListen; if (this.objectToListen != null) { this.objectToListen.PropertyChanged += this.ObjectPropertyChanged; this.PropertiesToListen = propertiesToListen; } } /// <summary> /// Occurs when changes occur that affect whether or not the command should execute. /// </summary> public event EventHandler CanExecuteChanged { add { CanExecuteChangeValue += value; } remove { CanExecuteChangeValue -= value; } } /// <summary> /// Occurs when [can execute change value]. /// </summary> private event EventHandler CanExecuteChangeValue; /// <summary> /// Gets the properties to listen. /// </summary> /// <value>The properties to listen.</value> public IEnumerable<string> PropertiesToListen { get; private set; } /// <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 <see langword="null"/>. /// </param> /// <returns> /// <see langword="true"/> if this command can be executed; otherwise, <see langword="false"/>. /// </returns> public bool CanExecute(object parameter) => canExecute?.Invoke((T)parameter) ?? true; /// <summary> /// Performs application-defined tasks associated with freeing, releasing, or resetting /// unmanaged resources. /// </summary> public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// <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 <see langword="null"/>. /// </param> public void Execute(object parameter) => execute((T)parameter); /// <summary> /// Raises the can execute changed. /// </summary> public void RaiseCanExecuteChanged() { this.CanExecuteChangeValue.RaiseEvent(this); } /// <summary> /// Releases unmanaged and - optionally - managed resources. /// </summary> /// <param name="disposing"> /// <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release /// only unmanaged resources. /// </param> protected virtual void Dispose(bool disposing) { if (this.isDisposed) { return; } if (disposing) { if (this.objectToListen != null) { this.objectToListen.PropertyChanged -= this.ObjectPropertyChanged; } this.objectToListen = null; } isDisposed = true; } /// <summary> /// Listens to any property changed of the object that is passed. /// </summary> /// <param name="sender">The sender.</param> /// <param name="e"> /// The <see cref="PropertyChangedEventArgs"/> instance containing the event data. /// </param> private void ObjectPropertyChanged(object sender, PropertyChangedEventArgs e) { if (this.PropertiesToListen == null || ((this.PropertiesToListen?.Any() ?? false) && this.PropertiesToListen.Contains(e.PropertyName))) { // Raise on all the property change this.RaiseCanExecuteChanged(); } } }
Usage
The usage is not far different from what we are used to. The only change here is the additional object to listen and-or properties of it to listen.
// The below will ensure that the command's CanExecute is validated only when Property1 and Property2 are changed. this.MyCommand = new Command<SomeObj>( execute: someObj => this.commands.ExecuteMyCommand(someObj), canExecute: someObj => this.commands.CanExecuteMyCommand(someObj), objectToListen: this, propertiesToListen: new List<string> { nameof(this.Property1), nameof(this.Property2) }); // The below will ensure that the command's CanExecute is validated when any property in this object is changed. this.MyCommand = new Command<SomeObj>( execute: someObj => this.commands.ExecuteMyCommand(someObj), canExecute: someObj => this.commands.CanExecuteMyCommand(someObj), objectToListen: this });
After implementing this solution to my WPF application, I probably saved a zillion unnecessary calls.
I was following you up until this.CanExecuteChangeValue.RaiseEvent(this);
For the simple reason that as my visual studio might tell you: “‘EventHandler’ does not contain a definition for ‘RaiseEvent’ and no accessible extension method ‘RaiseEvent’ accepting a first argument of type ‘EventHandler’ could be found (are you missing a using directive or an assembly reference?)”