关联 Command 对象的 按钮的 启用和禁用状态 是由什么来决定的?
通过查看 wpf 的 源码得到以下结论:
ICommand 中定义的 CanExecuteChanged 由命令源 ButtonBase 使用(ButtonBase定义了这个事件处理方法 OnCanExecuteChanged)
在OnCanExecuteChanged 这个方法中,经过一系列调用 会调用 ICommand 定义的 CanExecute方法,
获取到的返回值 去设置 控件的启用还是禁用的状态。
最终结论:
要想 通过 命令 改变 命令关联的按钮的 启用禁用状态,就需要 有人 去调用 CanExecuteChanged 这个事件,谁去调用它?
在 [Windows Community Toolkit] RelayCommand 中 提供了 一个 NotifyCanExecuteChanged 方法,要自己调用这个方法,来触发按钮状态的改变
RoutedCommand 中 的 CanExecuteChanged 是 转交给 CommandManager.RequerySuggested 执行的
即 :由 CommandManager 自动调用 RoutedCommand 中 的 CanExecuteChanged 事件,来调整按钮的禁用和启用状态
// routedCommand 中的 CanExecuteChanged 事件
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
详细分析:
wpf 命令模型 最重要的 一个组件 ICommand 接口,定义如下
namespace System.Windows.Input
{
// Token: 0x02000008 RID: 8
[NullableContext(2)]
[TypeForwardedFrom("PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
[TypeConverter("System.Windows.Input.CommandConverter, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
[ValueSerializer("System.Windows.Input.CommandValueSerializer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
public interface ICommand
{
// Token: 0x14000001 RID: 1
// (add) Token: 0x0600001B RID: 27
// (remove) Token: 0x0600001C RID: 28
event EventHandler CanExecuteChanged; (在命令中定义,谁用它来关联事件程序)
// Token: 0x0600001D RID: 29
bool CanExecute(object parameter); (由谁调用)
// Token: 0x0600001E RID: 30
void Execute(object parameter);
}
}
Button的基类 ButtonBase 类中实现的 ICommandSource 中的 Command,Command 是 一个依赖属性
[Bindable(true)]
[Category("Action")]
[Localizability(LocalizationCategory.NeverLocalize)]
public ICommand Command
{
get
{
return (ICommand)base.GetValue(ButtonBase.CommandProperty);
}
set
{
base.SetValue(ButtonBase.CommandProperty, value);
}
}
这个Command 依赖属性 注册了 一个 属性改变时的回调函数 OnCommandChanged,当设置按钮的Command属性时,会调用这个函数
ButtonBase.CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(ButtonBase), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(ButtonBase.OnCommandChanged)));
static ButtonBase()
{
ButtonBase.ClickEvent = EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ButtonBase));
ButtonBase.CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(ButtonBase), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(ButtonBase.OnCommandChanged)));
ButtonBase.CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(ButtonBase), new FrameworkPropertyMetadata(null));
ButtonBase.CommandTargetProperty = DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(ButtonBase), new FrameworkPropertyMetadata(null));
ButtonBase.IsPressedPropertyKey = DependencyProperty.RegisterReadOnly("IsPressed", typeof(bool), typeof(ButtonBase), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, new PropertyChangedCallback(ButtonBase.OnIsPressedChanged)));
ButtonBase.IsPressedProperty = ButtonBase.IsPressedPropertyKey.DependencyProperty;
ButtonBase.ClickModeProperty = DependencyProperty.Register("ClickMode", typeof(ClickMode), typeof(ButtonBase), new FrameworkPropertyMetadata(ClickMode.Release), new ValidateValueCallback(ButtonBase.IsValidClickMode));
EventManager.RegisterClassHandler(typeof(ButtonBase), AccessKeyManager.AccessKeyPressedEvent, new AccessKeyPressedEventHandler(ButtonBase.OnAccessKeyPressed));
KeyboardNavigation.AcceptsReturnProperty.OverrideMetadata(typeof(ButtonBase), new FrameworkPropertyMetadata(BooleanBoxes.TrueBox));
InputMethod.IsInputMethodEnabledProperty.OverrideMetadata(typeof(ButtonBase), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.Inherits));
UIElement.IsMouseOverPropertyKey.OverrideMetadata(typeof(ButtonBase), new UIPropertyMetadata(new PropertyChangedCallback(Control.OnVisualStatePropertyChanged)));
UIElement.IsEnabledProperty.OverrideMetadata(typeof(ButtonBase), new UIPropertyMetadata(new PropertyChangedCallback(Control.OnVisualStatePropertyChanged)));
}
静态 OnCommandChanged 方法 会 调用 当前按钮对象的实列方法 OnCommandChanged
// Token: 0x060079AA RID: 31146 RVA: 0x009DAA78 File Offset: 0x009D9C78
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ButtonBase)d).OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue);
}
// Token: 0x060079AB RID: 31147 RVA: 0x009DAAA0 File Offset: 0x009D9CA0
private void OnCommandChanged(ICommand oldCommand, ICommand newCommand)
{
if (oldCommand != null)
{
this.UnhookCommand(oldCommand);
}
if (newCommand != null)
{
this.HookCommand(newCommand);
}
}
实列的OnCommandChanged方法 会进一步 调用 UnhookCommand 和 HookCommand,从原先的Command的CanExecuteChanged事件与按钮的事件处理方法 OnCanExecuteChanged取消关联,在将新Command的CanExecuteChanged事件与按钮的事件处理方法OnCanExecuteChanged关联。
这里 按钮对象 注册了 OnCanExecuteChanged 事件函数,就是 当 调用 命令的 CanExecuteChanged 事件时,就会执行 按钮的 OnCanExecuteChanged 事件函数
// Token: 0x060079AC RID: 31148 RVA: 0x009DAAB8 File Offset: 0x009D9CB8
private void UnhookCommand(ICommand command)
{
CanExecuteChangedEventManager.RemoveHandler(command, new EventHandler<EventArgs>(this.OnCanExecuteChanged));
this.UpdateCanExecute();
}
// Token: 0x060079AD RID: 31149 RVA: 0x009DAAD4 File Offset: 0x009D9CD4
private void HookCommand(ICommand command)
{
CanExecuteChangedEventManager.AddHandler(command, new EventHandler<EventArgs>(this.OnCanExecuteChanged));
this.UpdateCanExecute();
}
OnCanExecuteChanged 方法接着会 调用 UpdateCanExecute 方法,这个方法会调用 CommandHelpers.CanExecuteCommandSource(this)
// Token: 0x060079AE RID: 31150 RVA: 0x009DAAF0 File Offset: 0x009D9CF0
private void OnCanExecuteChanged(object sender, EventArgs e)
{
this.UpdateCanExecute();
}
// Token: 0x060079AF RID: 31151 RVA: 0x009DAAF8 File Offset: 0x009D9CF8
private void UpdateCanExecute()
{
if (this.Command != null)
{
this.CanExecute = CommandHelpers.CanExecuteCommandSource(this);
return;
}
this.CanExecute = true;
}
CanExecuteCommandSource 内部调用了 Command的 CanExecute 方法
internal static bool CanExecuteCommandSource(ICommandSource commandSource)
{
ICommand command = commandSource.Command;
if (command == null)
{
return false;
}
object commandParameter = commandSource.CommandParameter;
IInputElement inputElement = commandSource.CommandTarget;
RoutedCommand routedCommand = command as RoutedCommand;
if (routedCommand != null)
{
if (inputElement == null)
{
inputElement = (commandSource as IInputElement);
}
return routedCommand.CanExecute(commandParameter, inputElement);
}
return command.CanExecute(commandParameter);
}
从CommandHelpers.CanExecuteCommandSource(this) 方法返回的值 设置了 CanExecute 属性,设置CanExecute 属性时 设置了 按钮是 禁用 还是启用
private bool CanExecute
{
get
{
return !base.ReadControlFlag(Control.ControlBoolFlags.CommandDisabled);
}
set
{
if (value != this.CanExecute)
{
base.WriteControlFlag(Control.ControlBoolFlags.CommandDisabled, !value);
base.CoerceValue(UIElement.IsEnabledProperty);
}
}
}