Sunday, April 19, 2009

MVVM Infrastructure: DelegateCommand

This post is part of a short series I am doing on MVVM infrastructure. In this series, I will share some thinking and code that has helped to produce cleaner, simpler MVVM code in my applications.

Related posts:

  1. POCOs versus DependencyObjects
  2. ViewModel
  3. DelegateCommand
  4. ActiveAwareCommand

In my last post I discussed my ViewModel base class. In this post I’m going to start showing you some infrastructure around commanding.

WPF's commanding support enables you to hook up UI interactions with code without tightly coupling the two. User actions are abstracted into implementations of WPF’s ICommand interface, which are then associated with controls that implement ICommandSource. Essentially, it's a layer of indirection. The controls in the UI aren't intimately aware of the command logic they are connected with, and the command logic isn't aware of the controls it will be associated with.

When starting out with WPF commanding, you will soon come across the RoutedCommand class. This particular command is yet another layer of indirection, in that its execution logic involves finding something to execute and calling that! Starting with the focused element, it searches up the visual tree for an element that has a matching CommandBinding in its CommandBindings collection. If the RoutedCommand finds a matching CommandBinding, it executes that CommandBinding's Executed delegate.

Routed commands work great in certain scenarios, and are prevalent in WPF. Indeed, the only concrete ICommand implementation that comes with WPF is the RoutedCommand (and its subclass, RoutedUICommand). Unfortunately, this has caused WPF commanding to become somewhat synonymous with RoutedCommands, which is not at all accurate.

image The thing is, routed commands are not always a great fit for MVVM development. Typically the logic for your command execution belongs in the view model. For example, suppose you have a CustomersViewModel that maintains a list of CustomerViewModels, and you want to expose a command that deletes the selected customer. The logic for that command should reside in the CustomersViewModel, since it knows best what to do when a deletion is requested.

If you were to use a RoutedCommand, you would then need a CommandBinding somewhere in the UI in order to connect a visual element to the DeleteCustomerCommand. In our example, we would likely stick a CommandBinding in the CustomersView and associate it with the deletion command and methods on the view model. Without the CommandBinding, the RoutedCommand won't find a handler for the command and the Delete button will be forever disabled.

Using routed commands with MVVM ends up being messy, sub-optimal, and requires the view be more tightly coupled to the view model. And in some scenarios, it’s not even possible (I’ll discuss such a scenario in my next post). But the good news is, we don't have to use routed commands. All we need is an appropriate implementation of ICommand – one that is more conducive to MVVM development.

imageIf you think about it, all we really want to do is have a command that – when executed – invokes a method in our view model. And when the command is queried for its executable status, it executes a different method in the view model. To continue our customer example, that command might be associated with a Button in our CustomersView. The availability of the command would be dependent upon the user first selecting a customer (you can't delete a customer if you don't know which customer to delete). The execution of the command might execute a DeleteCustomer method on the CustomersViewModel.

Sound simple? That's because it is! Far simpler than attempting to use routed commands for this scenario.

The command implementation I use is called DelegateCommand, simply because it invokes delegates when executing and querying executable status. Other people have different names for this type of command, the most common of which seems to be RelayCommand. That's fine - call it what you will.

Prism has a DelegateCommand too, which is almost exactly the same as this one. However, the reason I have my own is simply because we don't use Prism in our project, and it would be a large overhead to pull it in just for the sake of a class or two. Besides, if you're doing a non-composite WPF app, Prism may not be a good fit.

To create a DelegateCommand, you simply give it one or two delegates to use. If you give it only one delegate, the command is assumed to be always available. That is, ICommand.CanExecute() will always return true.

In our CustomersViewModel, we create the DelegateCommand as follows:

_deleteCustomerCommand = new DelegateCommand(DeleteCustomer, CanDeleteCustomer);

We then expose the command from a property in our view model:

public ICommand DeleteCustomersCommand
{
    get { return _deleteCustomerCommand; }
}

And bind to it in the view:

<Button Command="{Binding DeleteCustomersCommand}">Delete</Button>

It’s as simple as that. The delegates supplied to the DelegateCommand will be invoked at the appropriate times and our Delete button will only be enabled when a customer is selected. You can see all this and more in the download.

Incidentally, DelegateCommand inherits from a base Command class. That’s just to keep the code DRY with respect to other command classes I have, such as the ActiveAwareCommand. I’ll talk about that one in the next post.

† The application in question was built before Prism shipped, elsewise we would be.

9 comments:

James Chaldecott said...

I love (Prism's) DelegateCommand it makes everything so much easier.

One problem I've found (with commands in general) is that CanExecute doesn't get called again when the CommandParameter changes. I've created an Attached Behaviour to fix it which I've posted to the Prism forums & StackOverflow.

Sorry for hijacking your blog, but I've not got my own!

Kent Boogaart said...

@James: I tend not to use command parameters when doing MVVM because everything the command needs to execute is generally embodied in the view model in some fashion.

That said, I would consider that a bug. Have you reported this on Connect?

Daniil Harik said...

Hello Kent,

This is Daniil from StackOverflow, regarding http://stackoverflow.com/questions/798579/wpf-listbox-highlight-part-of-listboxitem-element this thread

I've tried Your solution, it works nice.

I've added one twist to it, if You are interested in discussing it please write me an email.

Thank You for Your effort.

P.S. Nice blog :)

binu said...

Hi
Is it possible to know the which command called the execute method of the delegate command. I want to use the same execute method for 2 different commands. But there would be some difference in the logic of the execute method based on which command was called.
Thanks

Kent Boogaart said...

Hi binu: there is no sender argument, so it's not possible without examining the call stack, which is obviously not a great idea. There are several workarounds though.

Firstly, you could simply abstract out the common functionality into a separate method and call that method from both command handlers.

Another option is to use the state of your view model to discriminate. That is, you might have a state property that is used to choose the code path.

Or you can use the command parameter to distinguish between the two callers.

HTH,
Kent

neil martin said...

used your framework it was very helpfull , thnak you have put credit on product blog and in the code :
http://www.blushpackages.com/Blog/post/Work-Log-18-11-09.aspx

Anonymous said...

Hi,

Does DelegateCommand work only with Commands? I am trying to use it with my Drop event in tree view. Can this be done?

Kent Boogaart said...

@anon: there are various solutions out there to enable you to execute a command in response to an event. Marlon Grech's attached command behaviour comes to mind: http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/. And I believe Blend has some attached behaviours that are similar.

adeptus said...

Wow!
Thank you very much for the class DelegateCommand. It was exactly what I was looking for.

Your little MVVM-Exampleproject was very helpful too...

THANK YOU!!! :-)