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!
Trackbacks
- Windows Client Developer Roundup 053 for 1/6/2011 · All About Computer | http://ispey.com/computer/computer/windows-client-developer-roundup-053-for-162011/
- MVVM Light ActivityCommand « Mas-Tool's Favorites | http://mas-tool.com/?p=2774
Comments
Rob Eisenberg
Because of the way that iterators work, I don't think you actually don't need the RelayActivity. You can probably just write this:
IEnumerable ShowMessage() { yield return new MessageBoxActivity("Hello from an activity!", "Hello World"); Status = "Just showed a message!"; }
Matt Hamilton
Hi Rob,
I thought that myself, but it seems that the lines after the "yield return" are executed even if the yielded activity was cancelled. This code, for example:
Will still change my "Status" TextBlock to "Testing..." even if I cancel the MessageBox. The subsequent RelayActivity only gets called, though, if I hit OK on the MessageBox.
So it seems the iterator is executing everything right up to the very next "yield return". I will investigate further - it could be a problem with my code. Thanks for your comment!
Matt Hamilton
... and indeed it is a problem with my code. I will update the post with a revised version of the
Executemethod. Thanks Rob!