Commands provide a means for encapsulating some logic, enabling that logic to be reused. One of the key benefits of commands, however, is that some controls allow you to bind to and execute them in XAML. In other words, not only can you bind to data in Silverlight but you can also bind to operations! For example, a ViewModel might expose a Save
command, that a button in the View can bind to and execute when the button is clicked.
Note Commands are commonly used in conjunction with the MVVM design pattern to allow a ViewModel to expose operations to a View.
To create a command class, it must implement the ICommand
interface, as shown in Figure 13-6.
The CanExecute
property specifies whether or not the command can be executed. When the command is bound to a button, the button will be enabled or disabled accordingly. In order for the button's enabled status to update when the property updates, the CanExecuteChanged
event on the command should be raised. Any logic to be executed when the command is executed will go in the Execute
method.
Note It's a common problem to forget to raise the CanExecuteChanged
event after changing the value of the CanExecute
property. If you find that your button is disabled despite having updated the value of the CanExecute
property, this is most likely the source of the issue.
To create a command, create a new class and implement the ICommand
interface. The following code demonstrates a simple command that will display a message box when it is executed:
using System;
using System.Windows;
using System.Windows.Input;
namespace Chapter11Sample
{
public class TestCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show("Command executed!");
}
}
}
A number of controls in Silverlight have a pair of properties named Command
and CommandParameter
—namely the Button, HyperlinkButton, ToggleButton, and RepeatButton controls. Each of these controls can bind its Command
property to a command and will execute the bound command when it is clicked.
Note The ContextMenu control from the Silverlight Toolkit can also have its menu items bound to commands. Unfortunately, no other controls have support for commands in Silverlight, but you can still use the InvokeCommandAction
action from the Expression Blend Interactivity library to invoke a command in response to any event that a control raises (such as in response to an item in a ListBox control being selected).
The following code demonstrates creating the TestCommand
command shown in the previous section as a resource and binding to it:
<UserControl x:Class="Chapter11Sample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Chapter11Sample">
<UserControl.Resources>
<local:TestCommand x:Key="testCommand" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<Button Content="Test Command"
Command="{Binding Source={StaticResource testCommand}}" />
</Grid>
</UserControl>
You may have noticed that the Execute
method of the command has a parameter of type object
. Any value that you assign or bind to the control's CommandParameter
property will be passed into both the CanExecute
method and the Execute
method when the command is executed.
For example, you can bind this property to a property on another control (such as the SelectedItem
property on a ListBox control), and the value of that control's property will be passed to the Execute
method of the command as its parameter. The following XAML demonstrates this scenario:
<UserControl x:Class="Chapter11Sample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Chapter11Sample">
<UserControl.Resources>
<local:TestCommand x:Key="testCommand" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" Width="200" Height="300">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<ListBox Name="ProductList" />
<Button Content="Test Command" Grid.Row="1"
Command="{Binding Source={StaticResource testCommand}}"
CommandParameter="{Binding SelectedItem, ElementName=ProductList}" />
</Grid>
</UserControl>
You've seen how you can encapsulate a piece of logic within a command, but you may not always want to have to put commands in their own class. For example, you want to expose some logic from a ViewModel class to a View as a command that can be bound to but retain the logic within the ViewModel class since it won't be used anywhere else in the application. In this scenario, you should consider implementing the command as a DelegateCommand
. A DelegateCommand
is an ICommand
implementation that allows you to delegate the implementation of the command to a method in your ViewModel class. You can then expose the DelegateCommand
object from your ViewModel class as a property, allowing you to easily expose operations from ViewModels as commands when using the MVVM design pattern. The following code is the source for the DelegateCommand
class:
using System;
using System.Windows.Input;
namespace SimpleMVVM
{
public class DelegateCommand : ICommand
{
private Func<object, bool> canExecute;
private Action<object> executeAction;
private bool canExecuteCache;
public DelegateCommand(Action<object> executeAction, Func<object, bool> canExecute)
{
this.executeAction = executeAction;
this.canExecute = canExecute;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
bool temp = canExecute(parameter);
if (canExecuteCache != temp)
{
canExecuteCache = temp;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, new EventArgs());
}
}
return canExecuteCache;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
executeAction(parameter);
}
#endregion
}
}
In your ViewModel class, you will need to define two methods: one returning a Boolean value indicating whether the command can be executed, and the other that will be called when the command is actually executed. If we were to implement the TestCommand
command demonstrated earlier as a DelegateCommand
, you'd start by adding the following two methods to your ViewModel class:
private bool CanTest(object param)
{
return true;
}
public void Test(object param)
{
MessageBox.Show("Command executed!");
}
Note When you implement methods in a ViewModel class to be wrapped in a DelegateCommand
like this, the methods must conform to the same signature demonstrated here (i.e., have the same parameters and return types).
The next step is to create an instance of the DelegateCommand
class, passing the two methods to its constructor, and returning the resulting object as a property from your ViewModel class:
public ICommand TestCommand
{
get { return new DelegateCommand(Test, CanTest); }
}
A Button control in the View can have its Command
property bound to the TestCommand
property on the ViewModel. When the button is clicked, the command will be executed, resulting in it calling the Test
method in the ViewModel.
Note Another popular alternative to the DelegateCommand
is the RelayCommand
. An implementation of the RelayCommand
command can be obtained from the MVVM Light Toolkit (one of the popular MVVM frameworks for Silverlight, which you will find listed in the “Frameworks section” in Chapter 13).