CommandBindings with MVVM

A History Lesson

In the Dark Times before the WPF world embraced the Model/View/ViewModel, the way to use commands in your user interface involved a treacherous magic known as Command Bindings.

The idea is that you would define your custom application commands as dumb, static "placeholders" and bind them to your UI by providing behaviour in the code-behind. The framework itself provided a bunch of these "dumb" commands out of the box for you to use. Let's look at an example:

Suppose I want to hook up my "Properties" button to the built-in ApplicationCommands.Properties command. The first thing I'd do is add a binding to that command in my window's XAML, like this:

<Window.CommandBindings>
    <CommandBinding Command="ApplicationCommands.Properties"
                    Executed="PropertiesExecuted"
                    CanExecute="PropertiesCanExecute" />
</Window.CommandBindings>

Then you'd define the "PropertiesExecuted" and "PropertiesCanExecute" event handlers in your code behind, and they'd be called whenever the command needed to do its stuff.

To attach a UI element (like a button) to the command, you'd simply do something like this:

<Button Command="ApplicationCommands.Properties" ... />

Enter MVVM

In most MVVM frameworks (and specifically MVVM Light) we eschew traditional routed commands in favour of ICommand implementations on the ViewModel itself - usually RelayCommand or RelayCommand<T> if you need a parameter. We bind our UI elements' Command property directly to the command on the ViewModel and everything just works.

In Comicster, however, I wanted to use a mixture of both.

Why? Well, I'm trying to make it possible for users to design their own "skins" for Comicster, and the skin's DataContext isn't a ViewModel - it's the actual Model object that's currently selected. A Title, for example, or a Publisher. Since those objecst don't have commands themselves, you'd have to point your UI elements back at the host Window or something. In other words, instead of something as simple as this:

<Button Command="ApplicationCommands.Delete" ... />

... you'd end up having to do this:

<Button Command="{Binding DataContext.DeleteCommand,RelativeSource=...}" ... />

I definitely wanted to make things easy for skin developers (who might just be coming to grips with the basics of XAML) so it made sense for me to use traditional routed commands and somehow tie their execution back to the ViewModel itself.

So I've got a bunch of placeholder RoutedUICommands defined on a static class called ComicsterCommands. For example, there's ComicsterCommands.InsertTitle and ComicsterCommands.SelectPublisher. The skin designer can point to these, and when they're executed they'll call the appropriate ViewModel method. But how?

The Wrong Trousers

First I wanted to define the CommandBindings in my ViewModel rather than the View, so I defined a property like this on my ViewModel:

public CommandBindingCollection CommandBindings { get; private set; }

... and initialized it in the constructor like this:

CommandBindings = new CommandBindingCollection
{
    new CommandBinding(ComicsterCommands.SelectTitle, SelectTitle),
    new CommandBinding(ComicsterCommands.SelectPublisher, SelectPublisher),
    new CommandBinding(ComicsterCommands.SelectCreator, SelectCreator),
    new CommandBinding(ComicsterCommands.SelectCharacter, SelectCharacter),
    // etc
};

Next I figured I'd simlpy bind that collection back to my view by doing something like this:

<Window CommandBindings="{Binding CommandBindings}" />

Bzzzt. No go. The CommandBindings property is not a dependency property - it can't be bound to like this. The only way you can change it is by adding to the existing collection. That's not easy when the ViewModel doesn't know about the view it's being bound to.

The Right Trousers

Eventually I stumbled upon this blog post by Michal Martin: CommandBindings in MVVM. In it, Michal describes an attached property that adds all the command bindings in a ViewModel to the view. I won't reproduce the code here because Michal deserves the credit for it, but I will describe one little bug I needed to fix.

My ViewModel in this case represents a comic collection you've opened or created. You can open a collection, work with it, save or discard it and start (or open) a new one, and you can do that as many times as you like. Unfortunately the attached property as described by Michal has a little glitch in that it doesn't clear the CommandBindings property on the view each time, so every time the user opened a new collection, the bindings were being added to the view all over again, meaning that the CanExecute and Executed methods were being called on ViewModels that were no longer 'active'!

It's a simple fix:

private static void OnCommandBindingsChanged(DependencyObject sender,
    DependencyPropertyChangedEventArgs e)
{
    UIElement element = sender as UIElement;
    if (element == null) return;

    var bindings = (e.NewValue as CommandBindingCollection);
    if (bindings != null)
    {
        // clear the collection first
        element.CommandBindings.Clear();
        element.CommandBindings.AddRange(bindings);
    }
}

So now I can set up my bindings to a set of predefined routed commands back in the ViewModel, and "bind" to them from the view using a simple one liner:

<Window ...
    v:AttachedProperties.CommandBindings="{Binding Content.CommandBindings}">

In the above XAML, "Content" is a property on my DataContext that points to the open collection.

If you have a need to use traditional routed commands to a view but keep their behaviour in ViewModel, this is a nice approach.

.net wpf mvvm commands
Posted by: Matt Hamilton
Last revised: 03 Nov, 2024 12:07 PM History

Trackbacks

Comments

10 Jun, 2013 09:10 PM

Hi. Thanks to the article. Good work, well done. A question:

Is it possible to trigger an attached command -just like what is described in your article- from another view? I mean, here is the logic:

I have a shell view named ShellView (and ShellViewModel of course ). And I have some child views (and view-models) ChildView1, ChildView2, etc. I have a button on ShellView named RefreshButton. I want to bind the RefreshButton to NavigationCommands.Refresh, and handle the commands in children. Is it possible?

See the code example please:

// shell view:

<Button Command="NavigationCommands.Refresh" Content="Refresh" />

// child view:

<UserControl 
    local:AttachedProperties.RegisterCommandBindings="{Binding CommandBindings}" />

// child view-model

var refreshBinding = new CommandBinding(NavigationCommands.Refresh, RefreshExecuted, RefreshCanExecute);

CommandManager.RegisterClassCommandBinding(typeof(ChildViewModel), refreshBinding);

this.CommandBindings.Add(refreshBinding);

I have tried this, but not works. Have you any idea? Is it even possible?

10 Jun, 2013 10:44 PM

Hi Amiry,

The way I would handle that is by having my "child" ViewModels implement a common interface with a Refresh() method, and have ShellViewModel track the "children" and call that method from the refresh command's execute method.

So the child ViewModels themselves don't know about NavigationCommands.Refresh - they just implement IChildViewModel and have a Refresh method that's called from the shell when that command is executed.

Does that make sense?

Cheers, Matt

No new comments are allowed on this post.