Home > C#, MVVM, Windows Phone, WinRT > How to automatically call the CanExecute method when properties change with MVVM Light in Windows and Phone apps

How to automatically call the CanExecute method when properties change with MVVM Light in Windows and Phone apps

04/06/2014

When using commands, usually in an MVVM scenario, we can define a method, CanExecute, that determines whether a command can execute in its current state.

In a such situation, if the CanExecute handler returns false, the associated command can’t be invoked: if the command is bound, for example, to a Button, the latter will be automatically disabled.

When working with MVVM, the state of a command tipically depends on one or more properties of the ViewModel. For example, suppose we are using MVVM Light and we have defined the following class:

public class MainViewModel : ViewModelBase
{
    private string mail;
    public string Mail
    {
        get { return mail; }
        set
        {
            if (this.Set(ref mail, value))
                ConfirmCommand.RaiseCanExecuteChanged();
        }
    }

    private bool privacyConfirmation;
    public bool PrivacyConfirmation
    {
        get { return privacyConfirmation; }
        set
        {
            if (this.Set(ref privacyConfirmation, value))
                ConfirmCommand.RaiseCanExecuteChanged();
        }
    }

    public RelayCommand ConfirmCommand { get; set; }

    public MainViewModel()
    {
        ConfirmCommand = new RelayCommand(() =>
        {
            // ...
        },
        () =>
        {
            return !string.IsNullOrWhiteSpace(mail) && privacyConfirmation;
        });
    }
}

And the corresponding XAML page:

<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
    <TextBox Text="{Binding Mail, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
             Width="300" PlaceholderText="Mail address" />
    <CheckBox IsChecked="{Binding PrivacyConfirmation, Mode=TwoWay}"
              Content="I accept privacy terms"/>
    <Button Content="Confirm" Command="{Binding ConfirmCommand}" />
</StackPanel>

At lines 33-36 of MainViewModel, we have defined the CanExecute method as lambda: the command can be executed only if Mail is inserted and Privacy Confirmation is checked. To make thing works, we need to invoke the RelayCommand.RaiseCanExecuteChanged method when each interested property changes (lines 9-10 and 20-21), so that the system will call the CanExecute handler and determine the new execution state of the command.

Even in this simple example, we can see that, if a command depends on several properties, it is necessary to call the RaiseCanExecuteChanged method every time one of them chages. This can lead to a lot of repetitive code.

To simplify our apps, we can leverage the Messenger object that comes with MVVM Light. The ViewModelBase.Set method has an overload that allows to broadcast a PropertyChangedMessage notification using the Messenger if the corresponding property actually changes. So we can define a class that extends the standard RelayCommand and contains a list of properties on which it depends:

public class AutoRelayCommand : RelayCommand, IDisposable
{
    private ISet<string> properties;

    public AutoRelayCommand(Action execute) 
        : base (execute)
    {
        this.Initialize();
    }

    public AutoRelayCommand(Action execute, Func<bool> canExecute) 
        : base(execute, canExecute)
    {
        this.Initialize();
    }

    private void Initialize()
    {
        Messenger.Default.Register<PropertyChangedMessageBase>(this, true, (property) =>
        {
            if (properties != null && properties.Contains(property.PropertyName))
                this.RaiseCanExecuteChanged();
        });
    }

    public void DependsOn<T>(Expression<Func<T>> propertyExpression)
    {
        if (properties == null)
            properties = new HashSet<string>();

        properties.Add(this.GetPropertyName(propertyExpression));
    }

    private string GetPropertyName<T>(Expression<Func<T>> propertyExpression)
    {
        if (propertyExpression == null)
            throw new ArgumentNullException("propertyExpression");

        var body = propertyExpression.Body as MemberExpression;
        if (body == null)
            throw new ArgumentException("Invalid argument", "propertyExpression");

        var property = body.Member as PropertyInfo;
        if (property == null)
            throw new ArgumentException("Argument is not a property", 
                "propertyExpression");

        return property.Name;
    }

    public void Dispose()
    {
        Messenger.Default.Unregister(this);
    }
}

The key of this class is the DependsOn method, that allows to specify on which properties the command depends. It takes an expression representing a property and adds it to an internal collection. In the Initialize method (lines 17-24), we use the Messenger to register for the PropertyChangedMessageBase notification. When such a message is received, we determine whether the changed property is contained in the list of the monitored ones. If so, we call the RaiseCanExecuteChanged method, which in turns forces the system to invoke the CanExecute handler.

Now we can update the MainViewModel to use this new command type:

public class MainViewModel : ViewModelBase
{
    private string mail;
    public string Mail
    {
        get { return mail; }
        set
        {
            this.Set(ref mail, value, broadcast: true);
        }
    }

    private bool privacyConfirmation;
    public bool PrivacyConfirmation
    {
        get { return privacyConfirmation; }
        set
        {
            this.Set(ref privacyConfirmation, value, broadcast: true);
        }
    }

    public AutoRelayCommand ConfirmCommand { get; set; }

    public MainViewModel()
    {
        ConfirmCommand = new AutoRelayCommand(() =>
        {
            // ...
        },
        () =>
        {
            return !string.IsNullOrWhiteSpace(mail) && privacyConfirmation;
        });

        ConfirmCommand.DependsOn(() => Mail);
        ConfirmCommand.DependsOn(() => PrivacyConfirmation);
    }
}

With the code at lines 36-37, we tell that ConfirmCommand (that now is an AutoRelayCommand) depends on Mail and PrivacyConfirmation properties.

At lines 9 and 19 we have specified true for the brodcast argument of the Set method. In this way, a PropertyChangedMessage is automatically sent whenever these properties change. As said before, the AutoRelayCommand receives the notification and, because of its dependencies, it automatically calls the RaiseCanExecuteChanged method, causing the CanExecute handler to be invoked.

Note that we can follow the same approach also with generic RelayCommand<T> command (and so define an AutoRelayCommand<T> class).

Categories: C#, MVVM, Windows Phone, WinRT
%d bloggers like this: