首先介紹CommandManager類,它有一個重要的靜態事件:
RequerySuggested: Occurs when the CommandManager detects conditions that might change the ability of a command to execute.
當CommandManager認為當前的某個改變或動作有可能會改變command的能否執行的狀態時,就觸發該事件。例如焦點改變,所以這個事件會多次被觸發。
另外有一個重要的靜態方法:
InvalidateRequerySuggested(): Forces the CommandManager to raise the RequerySuggested event.
手動的調用這個方法強制的觸發RequerySuggested事件。
如下是測試的代碼,xaml:

<Window x:Class="RelayAndDelegateCommand.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" xmlns:local="clr-namespace:RelayAndDelegateCommand"> <StackPanel> <Button Content="Test Routed Command" Command="{x:Static local:MainWindow.TestRoutedCommand}" /> <Button Margin="0,8,0,0" Content="Test Relay Command" Command="{Binding TestRelayCommand}" /> <Button Margin="0,8,0,0" Content="Test Delegate Command" Command="{Binding TestDelegateCommand}" /> <Button Margin="0,20,0,0" Content="Click me" HorizontalAlignment="Center" Name="button1" VerticalAlignment="Top" Width="80" Click="button1_Click" /> </StackPanel> </Window>
code-main window:

public partial class MainWindow : Window { private bool _cansave = false; public MainWindow() { InitializeComponent(); this.CommandBindings.Add(new CommandBinding(TestRoutedCommand, new ExecutedRoutedEventHandler(OnTestRoutedCommandExecuted), new CanExecuteRoutedEventHandler(OnTestRoutedCommandCanExecute))); this.DataContext = this; } private void button1_Click(object sender, RoutedEventArgs e) { _cansave = true; // DelegateCommand needs manually raise can execute changed. (TestDelegateCommand as DelegateCommand).RaiseCanExecuteChanged(); } #region 1. TestRoutedCommand public static readonly RoutedCommand TestRoutedCommand = new RoutedCommand(); public void OnTestRoutedCommandExecuted(object sender, ExecutedRoutedEventArgs e) { MessageBox.Show("Hello world from RoutedCommand"); } public void OnTestRoutedCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = _cansave; Debug.WriteLine("CanExecute from RoutedCommand"); } #endregion #region 2. TestRelayCommand private ICommand _testRelayCommand; public ICommand TestRelayCommand { get { if (_testRelayCommand == null) { _testRelayCommand = new RelayCommand(new Action<object>(OnTestRelayCommandExecuted), new Predicate<object>(OnTestRelayCommandCanExecute)); } return _testRelayCommand; } } public void OnTestRelayCommandExecuted(object para) { MessageBox.Show("Hello world from RelayCommand"); } public bool OnTestRelayCommandCanExecute(object para) { Debug.WriteLine("CanExecute from RelayCommand"); return _cansave; } #endregion #region 3. TestDelegateCommand private ICommand _testDelegateCommand; public ICommand TestDelegateCommand { get { if (_testDelegateCommand == null) { _testDelegateCommand = new DelegateCommand(new Action<object>(OnTestDelegateCommandExecuted), new Predicate<object>(OnTestDelegateCommandCanExecute)); } return _testDelegateCommand; } } public void OnTestDelegateCommandExecuted(object para) { MessageBox.Show("Hello world from DelegateCommand"); } public bool OnTestDelegateCommandCanExecute(object para) { Debug.WriteLine("CanExecute from DelegateCommand"); return _cansave; } #endregion }
code-一個簡單的RelayCommand類:

public class RelayCommand : ICommand { #region Fields readonly Action<object> _execute; readonly Predicate<object> _canExecute; #endregion #region Constructors public RelayCommand(Action<object> execute) : this(execute, null) { } public RelayCommand(Action<object> execute, Predicate<object> canExecute) { if (execute == null) throw new ArgumentNullException("execute"); _execute = execute; _canExecute = canExecute; } #endregion #region ICommand Members public bool CanExecute(object parameter) { return _canExecute == null ? true : _canExecute(parameter); } // When command manager thinks the canexecute might change(e.g. focus changed), it raises RequerySuggested event. // The CanExecuteChanged is automatically registered by command binding, the execution logic of updating the button's // enabled\disabled state(value below) which is usually executed when CanExecuteChanged triggered, now is delegated to // RequerySuggested event, so when RequerySuggested triggered, the execution logic is being executed, and button's state gets updated. public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public void Execute(object parameter) { _execute(parameter); } #endregion }
code-一個簡單的DelegateCommand類:

public class DelegateCommand : ICommand { #region Fields readonly Action<object> _execute; readonly Predicate<object> _canExecute; #endregion #region Constructors public DelegateCommand(Action<object> execute) : this(execute, null) { } public DelegateCommand(Action<object> execute, Predicate<object> canExecute) { if (execute == null) throw new ArgumentNullException("execute"); _execute = execute; _canExecute = canExecute; } #endregion #region ICommand Members public bool CanExecute(object parameter) { return _canExecute == null ? true : _canExecute(parameter); } public event EventHandler CanExecuteChanged; // The CanExecuteChanged is automatically registered by command binding, we can assume that it has some execution logic // to update the button's enabled\disabled state(though we cannot see). So raises this event will cause the button's state be updated. public void RaiseCanExecuteChanged() { if (CanExecuteChanged != null) CanExecuteChanged(this, EventArgs.Empty); } public void Execute(object parameter) { _execute(parameter); } #endregion }
在這個例子中,第一個RoutedCommand是系統定義的,后面兩個是經常看到的兩個自定義的command的實現,它們都要實現
ICommand接口。這個接口有一個重要的事件public event EventHandler CanExecuteChanged,command binding機制內部會
自動的注冊這個事件,當我們觸發這個事件的時候,command binding機制內部會執行相應的邏輯來更新該命令可用不可用的狀態(如上面的DelegateCommand的實現)。
另一種實現方式如上面的RelayCommand所示,
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
它把更新命令可用不可用的狀態的邏輯(上面代碼中的value)代理給了CommandManager.RequerySuggested事件,而這個事件的觸發是由CommandManager自己來檢測的,當RequerySuggested事件被觸發時,執行同樣的邏輯(上面的value),command同樣得到刷新。
該實現與DelegateCommand的不同是DelegateCommand需要自己手動的調用RaiseCanExecuteChanged()方法來刷新,而RelayCommand的實現是一種懶的方式,不需要自己調用,由系統檢測。
這種懶的方式帶來的問題就是導致CanExecute方法多次被執行,例如上面說到的焦點改變時,可能會帶來性能影響。
如果查看RoutedCommand的實現,可以發現它的實現和RelayCommand是一樣的,所以平時我們使用它的時候並不需要手動的
通知這個命令刷新了。
RoutedCommand的內部實現:
INotifyPropertyChanged接口的PropertyChanged事件和ICommand接口的CanExecuteChanged事件類似,
通常在一個數據綁定中,我們並沒有去注冊這個事件,而我們經常調用如下的方法去通知某個地方要去做些更新的動作了。
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
那么是誰注冊這個事件去做一些事情呢?答案是數據綁定機制內部去注冊的,所以我們只管觸發這個事件發送一個通知就好了。
示例下載:http://files.cnblogs.com/bear831204/RelayAndDelegateCommand.zip