Building advanced MVVM commands, Part 1

Posted on Nov 24, 2010 in and tagged , ,

If you follow the MVVM pattern while developing your WPF/Silverlight applications, then you are probably familiar with DelegateCommand (or RelayCommand, as it is called sometimes) model. In brief: it is an implementation of WPF/Silverlight ICommand that allows you to specify what the command should do by passing some delegate that performs execution logic (instead of using CommandBindings). Pretty simple, but powerful pattern.

Dealing with MVVM a lot, I’ve build my own implementation of this pattern that lets you simplify its usage.

Here are some common features, that you have to handle on your own by using classic DelegateCommand:

  • Support for asynchronous execution (usually solved by generating additional code in ViewModel that handles async calls and free/busy indicator).
  • Different command states (Free/Busy/Failed with exception) + UI reactions for those states.
  • Support for execution cancellation and progress tracking (required for asynchronous commands)

Before we start, I offer you to imagine a common command execution timeline. In my opinion it looks like:

  1. Initialize some data (generally, UI settings – clear collections, set some indicators,…)
  2. Perform main execution logic (can be synchronous or asynchronous)
  3. Perform some actions after execution (update UI based on execution result)

And don’t forget about exception handling and correct cross-thread calls for asynchronous commands (all UI changes have to be performed through Dispatcher).

Let’s start with a little helper class, that I called PropertyChangedNotifier. It’s just a wrapper around IPropertyChangedNotifier that has OnPropertyChanged(string propertyName) method. This method raises underlying IPropertyChangedNotifier.PropertyChanged event. Also this class has VerifyPropertyName method that verifies if specified string corresponds to the actual property (this method is marked with [Conditional(“DEBUG”)] attribute and is only used while debugging your applications to find out possible bugs with invalid property names)

public abstract class PropertyChangedNotifier : INotifyPropertyChanged
{
    protected virtual bool ThrowOnInvalidPropertyName { get; private set; }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        VerifyPropertyName(propertyName);

        var handler = PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }

    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    private void VerifyPropertyName(string propertyName)
    {
        if (String.IsNullOrEmpty(propertyName))
        {
            return;
        }

        if (TypeDescriptor.GetProperties(this)[propertyName] == null)
        {
            string msg = "Invalid property name: " + propertyName;
            if (ThrowOnInvalidPropertyName)
            {
                throw new Exception(msg);
            }
            Debug.Fail(msg);
        }
    }
}

Let’s start our DelegateCommand with this classic imlementation:

public sealed class DelegateCommand<T>: PropertyChangedNotifier, ICommand where T: class
{
    public Action<DelegateCommand<T>, T> ExecuteAction
    {
        get;
        set;
    }

    public Predicate<T> CanExecutePredicate
    {
        get;
        set;
    }

    public DelegateCommand(Action<DelegateCommand<T>, T> execute, Predicate<T> canExecute)
    {
        ExecuteAction = execute;
        CanExecutePredicate = canExecute;
    }

    public void Execute(object parameter)
    {
        //blank
    }

    public bool CanExecute(object parameter)
    {
        //blank
    }

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

As I’ve mentioned, we may need to execute this command asynchronously, so I added property IsAsync that lets you specify whether the call will be asynchronous or not. Also as have been discussed earlier, we need delegates for pre-execute, post-execute and error actions.

public Action<T> PreExecute
{
    get;
    set;
}

public event Action<DelegateCommand<T>> Executed;

public Action<Exception> OnException
{
    get;
    set;
}

public bool IsAsync
{
    get;
    set;
}

public bool DisableOnExecution
{
    get;
    set;
}

The last property, DisableOnExecution, tells command to report False to CanExecute calls when it is executed asynchronously.

Remember the command state I’ve told about? Create an enum named CommandState

public enum CommandState
{
    Succeeded,
    Failed,
    IsExecuting
}

and add State property of type CommandState to DelegateCommand.

private CommandState _state;
public CommandState State
{
    get { return _state; }
    set
    {
        if (_state == value)
        {
            return;
        }

        _state = value;

        //if command was executed asynchronously, there may be some issues with correct CanExecute checks,
        //so we force CommandManager to perform that check.
        lock (this)
        {
            if (IsAsync && DisableOnExecution)
            {
                InvokeOnUIThread(CommandManager.InvalidateRequerySuggested);
            }
        }

        OnPropertyChanged("State");
    }
}

This one is a bit tricky. For now, there are some issues with WPF that prevent CanExecute checks when command is executed asynchronously. So when async execution is completed, we tell CommandManager to update all the bindings and re-evaluate CanExecute by calling CommandManager.InvalidateRequerySuggested. Note that is has to be called on UI thread. InvokeOnUIThread adds specified action to the UI dispatcher queue.


public void InvokeOnUIThread(Action action) { Dispatcher.Invoke(action); }

Here Dispatcher is a local variable that is initialized in the constructor.

#if SILVERLIGHT
    Dispatcher = Deployment.Current.Dispatcher;
#else
    Dispatcher = Dispatcher.CurrentDispatcher;
#endif

And if we speak about async calls, let’s add a method that will invoke specified delegate asynchronously:

private static void InvokeAsync(Action task, AsyncCallback executed)
{
    task.BeginInvoke(executed, null);
}

private static void InvokeAsync(Action task)
{
    InvokeAsync(task, x => { });
}

Also you may want to store some exception information if the command execution failed.

private Exception _commandException;
public Exception CommandException
{
    get { return _commandException; }
    set
    {
        if (_commandException == value)
        {
            return;
        }

        _commandException = value;
        OnPropertyChanged("CommandException");
    }
}

Command cancellation? Yes, add IsCancellationPending property. With all this things set up, we are ready to implement Execute and CanExecute methods.

public void Execute(object parameter = null)
{
    var typedParameter = parameter as T;

    Action action = () =>
    {
        try
        {
            CallExecute(typedParameter);
            if (State != CommandState.Failed)
            {
                State = CommandState.Succeeded;
            }
        }
        catch (Exception ex)
        {
            State = CommandState.Failed;
            CommandException = ex;
            CallOnException(ex);
        }
    };

    IsCancellationPending = false;
    CommandException = null;
    State = CommandState.IsExecuting;

    CallPreExecute(typedParameter);

    if (IsAsync)
    {
        var handler = Executed;
        if (handler == null)
        {
            InvokeAsync(action);
        }
        else
        {
            InvokeAsync(action, x => handler(this));
        }
    }
    else
    {
        action();
        CallExecuted();
    }
}

public bool CanExecute(object parameter)
{
    if (CanExecutePredicate != null)
    {
        try
        {
            return !GetIsBusy() && CanExecutePredicate(parameter as T);
        }
        catch
        {
            return false;
        }
    }
    return !GetIsBusy();
}

Methods that are named like CallXXX are just wrappers around corresponding delegates that check if they can be called.

private void CallPreExecute(T arg)
{
    if (PreExecute != null)
    {
        PreExecute(arg);
    }
}

private void CallExecute(T arg)
{
    if (ExecuteAction != null)
    {
        ExecuteAction(this, arg);
    }
}

private void CallExecuted()
{
    var handler = Executed;
    if (handler != null)
    {
        handler(this);
    }
}

private void CallOnException(Exception ex)
{
    if (OnException != null)
    {
        InvokeOnUIThread(() => OnException(ex));
    }
}

GetIsBusy method is pretty simple.

private bool GetIsBusy()
{
    return DisableOnExecution && (State == CommandState.IsExecuting);
}

Note that Execute method can perform both synchronous and asynchronous calls based on IsAsync value. Pre-execution, post-execution and error handling delegates are called on UI thread, so when you use the command, you don’t have to think about.

Building test application

It will be pretty simple. User selects a directory, application lists all the files in this directory and its sub-directories. I’ll start with FileModel class that will represent one list entry

public class FileModel
{
    public String Name
    {
        get;
        set;
    }

    public String Extension
    {
        get;
        set;
    }

    public String Size
    {
        get;
        set;
    }

    public String FullPath
    {
        get;
        set;
    }
}

Next to the ViewModel. It will have Files property (the collection of FileModel) and LoadFilesCommand (implemented as our DelegateCommand)

public sealed class MainWindowViewModel: PropertyChangedNotifier
{
    public MainWindowViewModel()
    {
        Files = new ObservableCollection();
    }
        
    public ObservableCollection Files
    {
        get;
        private set;
    }

    private DelegateCommand _loadFilesCommand;
    public DelegateCommand LoadFilesCommand
    {
        get
        {
            return _loadFilesCommand ?? (_loadFilesCommand = new DelegateCommand(LoadFiles)
            {
                DisableOnExecution = true,
                IsAsync = true
            });
        }
    }

    private void LoadFiles(DelegateCommand cmd, String loadFrom)
    {
        cmd.InvokeOnUIThread(() => Files.Clear());

        Action addFile = file =>
        {
            var model = new FileModel
            {
                Name = Path.GetFileNameWithoutExtension(file),
                FullPath = Path.GetFullPath(file),
                Extension = Path.GetExtension(file),
                Size = GetFileSize(file)
            };
            cmd.InvokeOnUIThread(() => Files.Add(model));
        };

        Action fileLoader = null;
        fileLoader = path =>
        {
            if (cmd.IsCancellationPending)
            {
                return;
            }

            foreach (var file in Directory.GetFiles(path))
            {
                addFile(file);
            }

            foreach (var dir in Directory.GetDirectories(path))
            {
                if (cmd.IsCancellationPending)
                {
                    return;
                }

                try
                {
                    fileLoader(dir);
                }
                catch {}
            }
        };

        fileLoader(loadFrom);
    }

    private static String GetFileSize(String fileName)
    {
        try
        {
            using (var stream = File.OpenRead(fileName))
            {
                return (stream.Length/1024).ToString();
            }
        }
        catch
        {
            return "?";
        }
    }
}

Some comments:

  • We use recursive lambda-function to get all the files in the specified folder.
  • At each loop step we have to check if the user decided to cancel operation.
  • Empty catch are blocks are not good! I used them only to keep things simple. Normally you will have to log all the errors.
  • All actions that change UI state must be performed on UI thread (I use cmd.InvokeOnUIThread(…))
  • Pre-execute, post-execute and error handling are not covered in this sample. I will show how they can be used in the next part.

The application window markup

private void StopButton_Click(object sender, RoutedEventArgs e)
{
    (DataContext as MainWindowViewModel).LoadFilesCommand.IsCancellationPending = true;
}

Event handler in the code-behind file (we will get rid of this in the following parts, so that cancellation will be available from markup)

private void StopButton_Click(object sender, RoutedEventArgs e)
{
    (DataContext as MainWindowViewModel).LoadFilesCommand.IsCancellationPending = true;
}

Run the application. Enter any path in the text box and click Start button. It will be disabled and application will start to fill the list with file information. Clicking Stop will cancel this operation.

Sample application that uses async commands

That’s all for now. With this implementation we can keep ViewModels clean and perform async operations practically the same way that we would perform the usual ones.

Download sources

In the next parts I will show how the progress tracking can be added, how we can improve command initialization with fluent extensions and how it all can be combined with visual states via DataTemplates techniques I’ve described earlier. Good luck =)

Leave a Reply

Your email address will not be published. Required fields are marked *