Using Commands and MVVM

To separate business action logic from presentation in a Silverlight app, you should be using commands. The logic for "Save Document" or "Post Invoices" really has no place in the page code behind, and certainly doesn't need to show up twice when invoked from both a button and a menu. Commands encapsulate logic for some business action, e.g. "Save Document".  that can be bound declaratively in XAML. Commands also fit neatly into the MVVM pattern where the command itself lives in the ViewModel.

To create and use a command:

  • Create a class that implement the ICommand interface. 'Easy-peasy' -- only two methods to override here.
  • Implement a ViewModel that contains the command. Adding a single property of your new command class will do it.
  • Bind the ViewModel object to your presentation. Binding the DataContext of the layout root makes the view model available anywhere on the page.
  • Bind the command to a command source, i.e. a button or menu item.

Implement ICommand

ICommand has only two methods, CanExecute (controls if the button be enabled) and Execute (your command logic goes here).  "Command Source" controls such as Button or Hyperlink have their Enabled properties toggled automatically based on what CanExecute() returns. CanExecute() declutters application code by removing all those "if x, then MenuSaveItem.Enabled = true" blocks of code that litter traditional, non-MVVM, applications. In the example below, the command can execute at any time and the button is always enabled.

If you bind a controls' CommandParameter property, the Command Parameter value will show up in both the CanExecute() and Execute() "parameter" argument.

The other bit of code here, CanExecuteChanged, is an event used to signal the command source (i.e. the button, hyperlink, menu item) to re-read the CanExecute state.

public class MyCommand : ICommand{
    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        MessageBox.Show("Executing command...");
    }
}

Implement ViewModel

The example ViewModel only has a single property: the command object. In a production application, the ViewModel would have other methods and properties to convert model data into something useful for the presentation layer.

public class MyViewModel{
    public MyCommand MessageCommand { get; set; }
    public MyViewModel()
    {
        this.MessageCommand = new MyCommand();
    }
}

Bind the ViewModel and Command

Binding the ViewModel to the "LayoutRoot" of the page makes the command available from anywhere in the page. You'll need to add an XML namespace to reference your project (named "local" in the example below). Create a static resource that references the ViewModel object and bind the ViewModel to the DataContext of "LayoutRoot".  The command itself is hooked up to the Command property of a Button. 

 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:MyProject"

 <!— ... -->
 <UserControl.Resources> 
 <local:MyViewModel x:Key="viewModel" /> 
 </UserControl.Resources> 
 <Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource viewModel}}"> 
 <Button Command="{Binding MessageCommand}" Content="Run MyCommand" /> 
</Grid>

When you run the application and click the button, the command Execute() fires and displays the message box. Notice that there is no click event handling, no code-behind for the application page and no direct application code to handle the button's Enabled state.

Silverlight 4 includes command support for Button, HyperlinkButton and Hyperlink. Telerik also includes command support for many of its RadControls for Silverlight including RadMenuItem, RadComboBox, charting areas, tree view items and any of the button controls.

Commands are first class citizens in Silverlight 4. In another blog we will look at how Expression Blend 4 makes it easy to interactively hook up commands, even to actions on controls that don't yet explicitly support commands.

comments powered by Disqus