命令绑定

和数据绑定类似,但是定义相反,命令源是控件,命令目标是实现了ICommand或子类接口的Command对象。

ICommand接口

  • CanExecute, 判断命令是否可用
  • CanExecuteChanged, 当命令状态改变时引发CanExecuteChanged事件,通知控件调用CanExecute()方法检查命令的状态。command binding机制内部会自动的注册这个事件,当我们触发这个事件的时候,command binding机制内部会执行相应的逻辑来更新该命令可用不可用的状态。
  • Execute,

    public interface ICommand { event EventHandler CanExecuteChanged;

      bool CanExecute(object parameter);
    
      void Execute(object parameter);   }
    

RoutedComand, RelayCommand, DelegateCommand

RelayCommand把更新命令可用不可用的状态的逻辑(上面代码中的value)代理给了CommandManager.RequerySuggested事件,而这个事件的触发是由CommandManager自己来检测的,当RequerySuggested事件被触发时,执行同样的逻辑(上面的value),command同样得到刷新。该实现与DelegateCommand的不同是DelegateCommand需要自己手动的调用RaiseCanExecuteChanged()方法来刷新,而RelayCommand的实现是一种懒的方式,不需要自己调用,由系统检测。这种懒的方式带来的问题就是导致CanExecute方法多次被执行,例如上面说到的焦点改变时,可能会带来性能影响。如果查看RoutedCommand的实现,可以发现它的实现和RelayCommand是一样的,所以平时我们使用它的时候并不需要手动的通知这个命令刷新了。

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
}

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
}

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事件。

Example of RelayCommand and DelegateCommand

<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>

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
}