MVVM Light ActivityCommand

In my last post I introduced the idea of Caliburn-style coroutines with MVVM Light. I touched on the idea of an ActivityCommand class that could be used in place of RelayCommand to allow for tighter integration with activities. Let's take a look at how that could work.

The ActivityCommand Class

First, ActivityCommand is effectively a direct copy of RelayCommand. The only things that change are the type of the "execute" lambda and the actual Execute method. Here's the gist:

private readonly IMessenger _messenger;
private readonly Func<IEnumerable<IActivity>> _execute;
private readonly Func<bool> _canExecute;

public ActivityCommand(IMessenger messenger, Func<IEnumerable<IActivity>> execute, Func<bool> canExecute)
{
    _messenger = messenger;
    _execute = execute;
    _canExecute = canExecute;
}

Since we need an instance of IMessenger later on, we have no choice but to pass one into the constructor. That's a shame, but I'd rather be passing an instance around than relying on the Messenger.Default singleton.

Here's the Execute method, which should look reasonably familiar if you've read the last post:

public void Execute(object parameter)
{
    var context = new ActivityContext(_messenger);

    var enumerator = _execute().GetEnumerator();

    bool done = !enumerator.MoveNext();
    if (done) return;

    do 
    {
        var activity = enumerator.Current;
        activity.Completed += (sender, e) =>
        {
            context.Cancel = e.Cancel;
            context.Error = e.Error;

            done = e.Cancel || e.Error != null || !enumerator.MoveNext();
        };
        activity.Execute(context);
    } while (!done);
}

So when the command executes, we loop through all the activities returned from the _execute lambda and kick them off one by one until the series is cancelled or an exception is thrown.

Now it becomes really easy to wire up our ICommand properties to a series of activities:

ShowMessageCommand = new ActivityCommand(MessengerInstance, ShowMessage);

IEnumerable<IActivity> ShowMessage()
{
    yield return new MessageBoxActivity("Hello from an activity!", "Hello World");
}

Obviously there's also a generic ActivityCommand<T> that lets you pass a parameter to the "execute" lambda, just like RelayCommand<T>.

There's one more helper class that I've introduced to simplify things slightly. There are times when, as part of a series of activities, you want to do something that's simple enough not to need its own IActivity implementation. That's where RelayActivity comes in:

The RelayActivity Class

RelayActivity is a class that implements IActivity but does nothing except executes the Action it's passed. Here's the implementation:

public class RelayActivity : IActivity
{
    public RelayActivity(Action execute)
    {
        _execute = execute;
    }

    Action _execute;

    public void Execute(ActivityContext context)
    {
        try 
        {
            _execute();
            Completed(this, new ActivityCompletedEventArgs());
        }
        catch (Exception ex)
        {
            Completed(this, new ActivityCompletedEventArgs(ex));
        }
    }

    public event EventHandler<ActivityCompletedEventArgs> Completed = delegate { };
}

Pretty straight forward. Here's a way we might use it:

IEnumerable<IActivity> ShowMessage()
{
    yield return new MessageBoxActivity("Hello from an activity!", "Hello World");
    yield return new RelayActivity(() => this.Status = "Just showed a message!");
}

So we show our message, and then immediately after we set a local property somewhere. The setting of that property is such a simple task that there's no reason to define a dedicated SetStatusActivity class for it.

So Where Are We?

Right now we have a way to define "asynchronous" tasks and execute them sequentially, and we have a way to tie that execution to an ICommand really simply.

Questions? Comments? Criticisms? Please leave me a comment!

.net mvvm wpf coroutines
Posted by: Matt Hamilton
Last revised: 15 Oct, 2024 09:46 PM History

Trackbacks