首先介绍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));
}
那么是谁注册这个事件去做一些事情呢?答案是数据绑定机制内部去注册的,所以我们只管触发这个事件发送一个通知就好了。
示例下载:https://files.cnblogs.com/bear831204/RelayAndDelegateCommand.zip