2022-01-15 wpf Button命令模型

关联 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(this.OnCanExecuteChanged));
            this.UpdateCanExecute();
        }

        // Token: 0x060079AD RID: 31149 RVA: 0x009DAAD4 File Offset: 0x009D9CD4
        private void HookCommand(ICommand command)
        {
            CanExecuteChangedEventManager.AddHandler(command, new EventHandler(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);
                }
            }
        }

你可能感兴趣的:(2022-01-15 wpf Button命令模型)