How can I handle WPF routed commands in my ViewModel without code-behind?

Viewed 6121

According to my understanding of MVVM, it is a good practice to handle routed commands directly in the ViewModel.

When a routed command is defined in a ViewModel as a RelayCommand (or DelegateCommand), it is easy to bind directly to the command like this: Command={Binding MyViewModelDefinedCommand}.

Actually, for routed command that are defined outside of my ViewModel, I handle those commands in the Code behind of the View and forward calls to the ViewModel. But I find it awkward that I have to do so. It goes against recommended MVVM good practices. I think that there should be a more elegant way to achieve the job.

How can I handle a "System.Windows.Input.ApplicationCommands" or any routed command defined outside of the Viewmodel directly in the ViewModel. In other words, for command defined outside of the ViewModel, how can I handle CommandBinding callback "CommandExecute" and/or "CommandCanExecute" to the ViewModel directly? Is that possible or not? If yes how? If no, why?

3 Answers

The accepted answer is very nice, but it seems the OP didn't quite understand how RoutedCommands work and that caused some confusion. Quoting from the question:

When a routed command is defined in a ViewModel as a RelayCommand (or DelegateCommand), it is easy to bind directly to the command like this: Command={Binding MyViewModelDefinedCommand}.

This is ambigous, but either way it's incorrect:

  1. Either - one can't define a RoutedCommand as a Relay/DelegateCommand because RoutedCommand is a different implementation of ICommand interface.
  2. Or - if a VM exposes an actual RoutedCommand, one will still face the same issue as with those RoutedCommands that are defined outside of VM (because of the way RoutedCommands work).

RoutedCommand is a specific implementation of ICommand

RoutedCommand's Execute/CanExecute methods do not contain our application logic (when you instantiate a RoutedCommand, you don't pass Execute/CanExecute delegates). They raise routed events which, like other routed events, traverse the element tree. These events (PreviewCanExecute, CanExecute, PreviewExecuted, Executed) are looking for element that has CommandBinding for that RoutedCommand. CommandBinding object has event handlers for those events, and that is where our application logic goes (now it's clear why exposing a RoutedCommand from your VM doesn't solve the issue).

// The command could be declared as a resource in xaml, or it could be one 
// of predefined ApplicationCommands
public static class MyCommands {
    public static readonly RoutedCommand FooTheBar = new RoutedCommand();
}

xaml:

<Window x:Class...
        xmlns:cmd="clr-namespace:MyCommands.Namespace">
    <Window.CommandBindings>
        <CommandBinding Command="{x:Static cmd:MyCommands.FooTheBar}"
                        Executed="BarFooing_Executed"/>
    </Window.CommandBindings>

<Grid>
...
// When command is executed, event goes up the element tree, and when
// it finds CommandBinding on the Window, attached handler is executed
<Button Command="{x:Static cmd:MyCommands.FooTheBar}"
        Content="MyButton"/>
...
</Grid>
</Window>

CommandBinding object

CommandBinding class does not inherit from DependencyObject (it's Command property can't be bound to a command exposed on VM). You can use event handlers attached to a CommandBinding to forward the call (in code-behind) to the VM - there is nothing important there, no logic (nothing to test). If you want no code-behind, then the accepted answer has nice solution (does that forwarding for you).

Related