Centralising RaiseCanExecuteChanged Logic in an MVVM Light Portable View Model

When working with an MVVM Light sometime we need to “tell” the view that a command’s ability to be executed has changed.

For example, when a user clicks a Start button to start a countdown, the Start button should be disabled and the pause button should then be enabled.

If we’re binding a button to an MVVM Light RelayCommand, the button will automatically disable when then RelayCommand’s CanExecute function returns false.

For this to work, when the user clicks Start, the command will execute, but then we need to tell the Start and Pause commands to raise their CanExecuteChanged events. To do this we can call the RaiseCanExecuteChanged method on each RelayCommand that logically makes sense for our application.

One approach is to put this in each property setter, for example:

public const string IsPlayingPropertyName = "IsPlaying";
protected  bool _isplaying;

public  bool IsPlaying
{
    get
    {
        return _isplaying;
    }

    set
    {
        if (_isplaying == value)
        {
            return;
        }

        RaisePropertyChanging(IsPlayingPropertyName);
        _isplaying = value;
        RaisePropertyChanged(IsPlayingPropertyName);
              
        // RAISE CANEXECUTECHANGED HERE IN PROP SETTER
        PauseCommand.RaiseCanExecuteChanged();
        StartCommand.RaiseCanExecuteChanged();
    }
}

Now when this IsPlaying property changes, the Pause and Start buttons commands can execute will be updated.

A problem with this approach is that the logic to decide which RelayCommands CanExecuteChanged event gets raised in response to which properties are changing is distributed through all the property setters in the ViewModel. This can make it hard to reason about the changes in state of the ViewModel.

One alternative approach is to centralise this logic in a single method.

First we wire-up the MVVM Light PropertyChanged event, for example in the View Model constructor:

PropertyChanged += PlayViewModel_PropertyChanged;

Now whenever a property on the ViewModel changes, the PlayViewModel_PropertyChanged handler will be called. This is the central place where we can put all our can execute change logic.

In this method, we want to describe when a source property changes, what commands need their CanExecuteChanged to be invoked.

We want to be able to associate a given property changing with one or more RelayCommands. The code below uses Tuples but you could go and creates classes to model this if you wanted to.

The listing below shows us wiring up a single property  “IsPlayingPropertyName” to 2 commands: “StartCommand” and “PauseCommand”.

We could go and perform more canExecuteMap.Add for any other source properties we want to associated with changing command can execute.

Then we just loop through all the commands in the “map” and if the source property matches, loop through and invoke RaiseCanExecuteChanged on all the mapped commands for that property change.

Here’s the entire method:

void PlayViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    var canExecuteMap = new List<Tuple<string, List<Action>>>();

    canExecuteMap.Add(new Tuple<string, List<Action>>(IsPlayingPropertyName, new List<Action>
                                                                             {
                                                                                 StartCommand.RaiseCanExecuteChanged,
                                                                                 PauseCommand.RaiseCanExecuteChanged
                                                                             }));


    foreach (var propertyChanged in canExecuteMap)
    {
        if (propertyChanged.Item1 == e.PropertyName)
        {
            foreach (var canExecuteChangedMethod in propertyChanged.Item2)
            {
                canExecuteChangedMethod.Invoke();
            }
        }
    }

}

What could be be even cooler is a fluent style approach to this, imagine: IsPlayingPropertyName.CausesChangeUpdate.To(StartCommand).And(PauseCommand);

SHARE:

Add comment

Loading