Diving Back into WPF: Taking Command

So far I’ve focused on how viewmodels define what a UI element looks like through property setting. But any viewmodel worth its salt generally also does stuff. And that’s where commands come in.

The MVVM toolkit handles in a straightforward manner any command which is implemented using the ICommand interface. That’s not every possible command, as we’ll get to in the next section, but it is every command from a UI element which has a Command property. The prototypical example is the Button, which you often see defined in an XAML file like this:

<Button DockPanel.Dock="Right"
    Margin="5, 5, 10,10"
    HorizontalAlignment="Right"
    Content="Close"
    Command="{Binding CloseCommand}"
    CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" />

Line 5 links the Button‘s need for a command handler to a RelayCommand property defined in the viewmodel (more on this in a moment).

Line 6 is worth mentioning because what it does is pass along the window within which this UI element is declared. That’s necessary in this case because the command handler sometimes needs to pop up a dialog box and a viewmodel, by design, shouldn’t be “aware” of things like “the window in which my associated UI element is running”. In fact, the viewmodel generally shouldn’t even be aware of the UI element it’s associated with1.

RelayCommand is a class defined in the MVVM toolkit. You generally declare instances of them in a viewmodel’s constructor, like this (details omitted for clarity):

public OptionsViewModel()
{
    SaveCommand = new RelayCommand( SaveCommandHandlerAsync );
    ReloadCommand = new RelayCommand( ReloadCommandHandler );
    CloseCommand = new RelayCommand<OptionsWindow>( CloseCommandHandler);
}

When you create an instance of a RelayCommand you have to associate it with a handler method. You can, optionally, declare a single argument type that it takes. The argument type needs to be compatible with whatever object you assign to CommandParameter in the XAML file.

The RelayCommand properties themselves are generally just simple read-only ones:

 public ICommand CloseCommand { get; }

 private void CloseCommandHandler( OptionsWindow optionWin )
 {
     if( SettingsChanged )
     {
         var dlgResult = optionWin.ShowModalMessageExternal( "Unsaved Changes", "There are unsaved changes. Are you sure you want to close?", MessageDialogStyle.AffirmativeAndNegative );

         if( dlgResult != MessageDialogResult.Affirmative )
             return;
     }

     Messenger.Send( new CloseModalWindowMessage( DialogWindow.Options ), "primary" );
}

Line 7 in the handler shows why I needed to pass the window object the UI element is running in to the RelayCommand: I needed to be able to display a dialog box asking the user to confirm they didn’t want to save configuration changes.

Line 13 is an example of communicating between viewmodels so the containing viewmodel — which handles things like saving configuration files — knows that the UI’s state has changed in a way it needs to be aware of.

This is all simple and elegant…but it won’t handle a large class of actions the UI can call for because not every possible action an element can request provides an ICommand interface2.

Fortunately there’s a somewhat-less-elegant workaround to this situation which enables you to map most, if not all, UI actions to RelayCommands.


  1. that’s always been possible for my projects, although sometimes it takes a little head-scratching to figure out how to adhere to 

  2. I believe UI elements can only offer a single ICommand, basically the one thing you’d intuitively expect them to do — like triggering a method when you click a Button

Archives
Categories