Control
命令绑定
和数据绑定类似,但是定义相反,命令源是控件,命令目标是实现了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
}