WPF (6) Análisis de código fuente del modelo de comando de comando

1. Análisis del código fuente de ICommand

En el comando WPF anterior de WPF (3), hemos analizado el sistema de comandos de WPF, incluido el comando RoutedCommand predeterminado de WPF y nuestra implementación de comando ICommand personalizada. Pero el último artículo se centró principalmente en el uso de comandos, y todavía hay algunas preguntas sobre el principio de funcionamiento y los detalles del proceso de algunos comandos, por ejemplo, ¿cómo suscribirse al evento CanExecuteChanged de ICommand? ¿Qué afecta el estado habilitado/deshabilitado del control de objeto ICommand asociado? ¿Cómo se ve afectado y así sucesivamente. Antes de explicar y analizar, analicemos el código fuente relevante del comando ICommand.

namespace System {
    
    
    //1.WPF 默认声明的委托类型 EventHandler
    //	- Object sender: 委托调用对象/源
    //	- EventArgs e: 事件参数对象
    public delegate void EventHandler(Object sender, EventArgs e);
 	//2.带泛型<TEventArgs>的委托类型 EventHandler
    public delegate void EventHandler<TEventArgs>(Object sender, TEventArgs e); // Removed TEventArgs constraint post-.NET 4
}

namespace System.Windows.Input
{
    
    
    ///<summary>
    ///     An interface that allows an application author to define a method to be invoked.
    ///</summary>
    public interface ICommand
    {
    
    
        //3.Raised when the ability of the command to execute has changed.
        //(1)说明:包装委托EventHandler的事件对象CanExecuteChanged
        //(2)作用:既然ICommand包含了event事件属性,则说明ICommand就成为了事件发布者。由绑定Command的控件订阅CanExecuteChanged事件,在特定属性改变时,来触发该CanExecuteChanged事件,从而进一步调用 CanExecute 方法刷新绑定控件的可用状态。
        event EventHandler CanExecuteChanged;
 
        //4.Returns whether the command can be executed.
        //	- <param name="parameter">A parameter that may be used in executing the command. This parameter may be ignored by some implementations.</param>
        //	- <returns>true if the command can be executed with the given parameter and current state. false otherwise.</returns>
        //(1)说明:该方法用于判断命令的可执行状态
        //(2)作用:常与绑定控件的可用状态 UIElement.IsEnabledProperty 相关联,配合CanExecuteChanged事件来刷新控件状态。若不需要判断控件的可用状态,则可以直接返回true 
        bool CanExecute(object parameter);
 
        //5.Defines the method that should be executed when the command is executed.
        //	- <param name="parameter">A parameter that may be used in executing the command. This parameter may be ignored by some implementations.</param>
        //(1)说明:该方法用于编写命令的执行逻辑,是命令的关键
        //(2)作用: 该方法用于封装命令的执行逻辑,是命令执行的主体
        void Execute(object parameter);
    }
}

2. Análisis del modelo de comando (tomando el control Button como ejemplo)

2.1 Proceso de suscripción vinculante

El comando en ICommandSource implementado en la clase ButtonBase, que es la clase base de Button, es una propiedad de dependencia. Registra una función de devolución de llamada OnCommandChanged cuando cambia la propiedad. Cuando el botón vincula/establece el comando, la función de devolución de llamada se llamará automáticamente. El código fuente lógico es el siguiente:

namespace System.Windows.Controls.Primitives
{
    
    
    /// <summary>
    ///     The base class for all buttons
    /// </summary>
    public abstract class ButtonBase : ContentControl, ICommandSource
    {
    
    
    
    	/// <summary>
        ///     The DependencyProperty for RoutedCommand
        /// </summary>
        [CommonDependencyProperty]
        public static readonly DependencyProperty CommandProperty =
                DependencyProperty.Register(
                        "Command",
                        typeof(ICommand),
                        typeof(ButtonBase),
                        new FrameworkPropertyMetadata((ICommand)null,
                            new PropertyChangedCallback(OnCommandChanged)));//Command依赖属性注册了回调函数 OnCommandChanged
                            
        /// <summary>
        /// Get or set the Command property
        /// </summary>
        [Bindable(true), Category("Action")]
        [Localizability(LocalizationCategory.NeverLocalize)]
        public ICommand Command
        {
    
    
            get
            {
    
    
                return (ICommand) GetValue(CommandProperty);
            }
            set
            {
    
    
                SetValue(CommandProperty, value);
            }
        }
 
        //1.静态回调函数 OnCommandChanged:最终调用OnCommandChanged(ICommand oldCommand, ICommand newCommand)方法
        private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
    
    
            ButtonBase b = (ButtonBase)d;
            b.OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue);
        }
 		//2.实例回调函数 OnCommandChanged:在绑定新命令时,调用HookCommand方法进行关联处理
        private void OnCommandChanged(ICommand oldCommand, ICommand newCommand)
        {
    
    
            if (oldCommand != null)
            {
    
    
                UnhookCommand(oldCommand);
            }
            if (newCommand != null)
            {
    
    
                HookCommand(newCommand);
            }
        }
    
    }
    
}

Como se puede ver en el código fuente anterior, el método OnCommandChanged de la función de devolución de llamada de la instancia llamará a los métodos UnhookCommand y HookCommand para desasociar primero el comando original del control y luego asociar el nuevo comando con el control. Aquí nos centramos principalmente en HookCommand, y la lógica de procesamiento de asociación específica es la siguiente:

namespace System.Windows.Controls.Primitives
{
    
    

    public abstract class ButtonBase : ContentControl, ICommandSource
    {
    
    
    	private void UnhookCommand(ICommand command)
        {
    
    
            CanExecuteChangedEventManager.RemoveHandler(command, OnCanExecuteChanged);
            UpdateCanExecute();
        }
 
        //1.命令关联函数:用于将命令与控件绑定,实质上是让控件订阅Command的事件发布
        //	- CanExecuteChangedEventManager.AddHandler: 使用控件的OnCanExecuteChanged方法订阅command的发布事件
        //	- UpdateCanExecute: 执行调用一次CanExecuteCommandSource方法,更新CanExecute状态(这里首次调用是初始化状态)
        private void HookCommand(ICommand command)
        {
    
    
            CanExecuteChangedEventManager.AddHandler(command, OnCanExecuteChanged);
            UpdateCanExecute();
        }
 
        //2.订阅函数:ICommand EventHandler的委托类型,用于控件订阅Command Changed事件,刷新CanExecute状态
        private void OnCanExecuteChanged(object sender, EventArgs e)
        {
    
    
            UpdateCanExecute();
        }
 
        //3.刷新状态函数:判断命令的可执行状态,刷新一次CanExecute
        private void UpdateCanExecute()
        {
    
    
            if (Command != null)
            {
    
    
                CanExecute = MS.Internal.Commands.CommandHelpers.CanExecuteCommandSource(this);
            }
            else
            {
    
    
                CanExecute = true;
            }
        }
    }   
}

​ El método HookCommand tiene dos funciones principales, una es llamar al método CanExecuteChangedEventManager.AddHandler y confiar su propio método OnCanExecuteChanged como controlador de eventos para suscribirse al evento modificado CanExecuteChanged del comando, de modo que cuando se active el evento CanExecuteChanged del comando , se liberará automáticamente para llamar al método OnCanExecuteChanged del control para actualizar el estado de CanExecute. Su código fuente es el siguiente:

namespace System.Windows.Input
{
    
    
    /// <summary>
    /// Manager for the ICommand.CanExecuteChanged event.
    /// </summary>
    public class CanExecuteChangedEventManager : WeakEventManager
    {
    
    
    	/// <summary>
        /// Add a handler for the given source's event.
        /// </summary>
        public static void AddHandler(ICommand source, EventHandler<EventArgs> handler)
        {
    
    
            if (source == null)
                throw new ArgumentNullException("source");
            if (handler == null)
                throw new ArgumentNullException("handler");
 			//1.单例模式:调用CurrentManager.PrivateAddHandler方法来处理(Command,Handler)
            CurrentManager.PrivateAddHandler(source, handler);
        }
        
        private void PrivateAddHandler(ICommand source, EventHandler<EventArgs> handler)
        {
    
    
            // get the list of sinks for this source, creating if necessary
            // 2.获取Sink链表,用于维护全局的(Command,Handler)关系
            List<HandlerSink> list = (List<HandlerSink>)this[source];
            if (list == null)
            {
    
    
                list = new List<HandlerSink>();
                this[source] = list;
            }
 
            // add a new sink to the list
            // 3.将当前的(Command,Handler)关系加入维护链表,并注册订阅事件
            HandlerSink sink = new HandlerSink(this, source, handler);
            list.Add(sink);
 
            // keep the handler alive
            AddHandlerToCWT(handler, _cwt);
        }
        
        //4.关键:Sink对象,维护(Command,Handler)关系对,并在初始化时注册订阅事件
        private class HandlerSink
        {
    
    
            public HandlerSink(CanExecuteChangedEventManager manager, ICommand source, EventHandler<EventArgs> originalHandler)
            {
    
    
                _manager = manager;
                _source = new WeakReference(source);
                _originalHandler = new WeakReference(originalHandler);
 
                _onCanExecuteChangedHandler = new EventHandler(OnCanExecuteChanged);
 
                // BTW, the reason commands used weak-references was to avoid leaking
                // the Button - see Dev11 267916.   This is fixed in 4.5, precisely
                // by using the weak-event pattern.   Commands can now implement
                // the CanExecuteChanged event the default way - no need for any
                // fancy weak-reference tricks (which people usually get wrong in
                // general, as in the case of DelegateCommand<T>).
 
                // register the local listener
                //5.将当前Button的 Handler 委托订阅Command的 CanExecuteChanged 事件
                source.CanExecuteChanged += _onCanExecuteChangedHandler;
            }
        }
    }
}

​ La segunda función del método HookCommand es llamar al método UpdateCanExecute para inicializar el estado CanExecute. Y el método UpdateCanExecute también es la lógica principal en el delegado OnCanExecuteChanged. Se usa para juzgar el estado ejecutable del comando y actualizar CanExecute una vez. La esencia es llamar al método bool CanExecute dentro del comando una vez. El análisis del código fuente es como sigue:

namespace MS.Internal.Commands
{
    
    
    internal static class CommandHelpers
    {
    
    
    	internal static bool CanExecuteCommandSource(ICommandSource commandSource)
        {
    
    
            //1.获取绑定命令对象
            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);
            }
            //2.调用 command.CanExecute 方法判断/刷新一次状态
            return command.CanExecute方法(commandParameter);
        }
    }  
}

2.2 Asociación estatal

2.1 analizó y explicó cómo el comando está vinculado al control Button y establece una relación de suscripción de eventos, entonces, ¿cómo se asocia el estado disponible del control Button con el método CanExecute del comando? De hecho, en el análisis anterior, el método UpdateCanExecute() establece el valor de su propia propiedad CanExecute a partir del valor devuelto por CommandHelpers.CanExecuteCommandSource(this), y al establecer la propiedad CanExecute, se asocia automáticamente con la variable de estado IsEnabledProperty que el botón está deshabilitado/habilitado, y su análisis de código fuente de la siguiente manera:

namespace System.Windows.Controls.Primitives
{
    
    
    public abstract class ButtonBase : ContentControl, ICommandSource
    {
    
    
    	
    	//ButtonBase 的 CanExecute属性
    	private bool CanExecute
        {
    
    
            get {
    
     return !ReadControlFlag(ControlBoolFlags.CommandDisabled); }
            set
            {
    
    
                if (value != CanExecute)
                {
    
    
                    WriteControlFlag(ControlBoolFlags.CommandDisabled, !value);
                    //关联到UIElement.IsEnabledProperty,是否可用状态
                    CoerceValue(IsEnabledProperty);
                }
            }
        }
    }
}

2.3 Mecanismo de activación de eventos

Después del análisis anterior, encontramos que para afectar el estado de activación/desactivación del botón Button asociado con el comando a través del comando, alguien necesita activar activamente el evento CanExecuteChanged en el comando cuando los datos cambian, para despertar una serie de suscripciones posteriores al delegado de actualización del estado del evento, ¿quién lo llamará?

(1) Comando enrutado

​ Para el RoutedCommand integrado de WPF, cualquier cliente que se suscriba al evento ICommand.CanExecuteChanged es en realidad un evento CommandManager.RequerySuggested suscrito. RoutedCommand delega la lógica de actualizar el estado disponible/deshabilitado del comando al evento CommandManager.RequerySuggested, y este evento El CommandManager detecta automáticamente el disparador y su código fuente es el siguiente:

namespace System.Windows.Input
{
    
    
    /// <summary>
    ///     A command that causes handlers associated with it to be called.
    /// </summary>
    public class RoutedCommand : ICommand
    {
    
    
    	//1.对于CanExecuteChanged事件的任何订阅行为都代理给了CommandManager.RequerySuggested事件,由CommandManager自动检测/更新状态
    	public event EventHandler CanExecuteChanged
        {
    
    
            add {
    
     CommandManager.RequerySuggested += value; }
            remove {
    
     CommandManager.RequerySuggested -= value; }
        }
    }
}

Por ejemplo, cuando cambia el enfoque espacial en la interfaz de la interfaz de usuario, se activará RequerySuggested. Esta implementación es un método de activación diferida, que no necesita ser invocado por el propio desarrollador, pero el sistema WPF lo detecta automáticamente. El problema con este método de activación perezoso es que el método CanExecute puede ejecutarse varias veces, lo que puede tener un cierto impacto en el rendimiento. Por supuesto, también podemos llamar manualmente a CommandManager.InvalidateRequerySuggested() para actualizar el estado del comando, que realizará la misma operación que la activación de ICommand.CanExecuteChanged, pero realizará esta operación en todos los RoutedCommands en un subproceso en segundo plano al mismo tiempo. De forma predeterminada, la condición de activación del evento WPF RequerySuggested está integrado en WPF, lo que solo actualizará la disponibilidad en los siguientes momentos:

KeyUp
MouseUp
GotKeyboardFocus
LostKeyboardFocus

Su código fuente se puede ver en CommandDevice.PostProcessInput, las partes clave son las siguientes:

// 省略前面。
if (e.StagingItem.Input.RoutedEvent == Keyboard.KeyUpEvent ||
    e.StagingItem.Input.RoutedEvent == Mouse.MouseUpEvent ||
    e.StagingItem.Input.RoutedEvent == Keyboard.GotKeyboardFocusEvent ||
    e.StagingItem.Input.RoutedEvent == Keyboard.LostKeyboardFocusEvent)
{
    
    
    CommandManager.InvalidateRequerySuggested(); //触发事件->刷新状态
}

(2) Comando personalizado

Para un comando personalizado, el método CanExecute solo se actualizará una vez cuando comience la inicialización del enlace y luego, sin importar cómo cambien los datos, el estado de actualización del evento no se activará, porque nadie activa activamente el evento ICommand.CanExecuteChanged para continuar. activar el delegado de suscripción. Sin embargo, podemos implementar manualmente el mecanismo de activación de actualización de eventos en el Comando personalizado, incluyendo principalmente los siguientes dos métodos (implementados en la Sección 3):

  • Actualizar manualmente el estado: cuando cambia el valor de la propiedad que afecta el estado ejecutable del comando, llame manualmente al método para activar el evento CanExecuteChanged
  • Use el proxy CommandManager: imite RoutedCommand, delegue el evento CanExecuteChanged al evento CommandManager.RequerySuggested

3. Comando personalizado avanzado

(1) Esquema de actualización manual

public class CommandBase : ICommand
{
    
    
    //1.命令可执行状态改变事件 CanExecuteChanged
    public event EventHandler CanExecuteChanged; 
    //2.命令具体执行逻辑委托 Action
    public Action<object> DoExecute {
    
     get; set; }
    //3.命令是否可执行判断逻辑委托(这里给个默认的值,不实现就默认返回true)
    public Func<object, bool> DoCanExecute {
    
     get; set; } = new Func<object, bool>(obj => true);
 
    public bool CanExecute(object parameter)
    {
    
    
        // 让实例去实现这个委托
        return DoCanExecute?.Invoke(parameter) == true;// 绑定的对象 可用
    }
 
    public void Execute(object parameter)
    {
    
    
        // 让实例去实现这个委托
        DoExecute?.Invoke(parameter);
    }
 
 
    //4.手动触发事件方法:手动触发一次CanExecuteChanged事件,刷新状态   
    public void DoCanExecuteChanged()
    {
    
    
        // 触发事件的目的就是重新调用CanExecute方法
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

(2) Utilice la solución de proxy CommandManager

namespace Login.ViewModels
{
    
    

    public class CommandBase : ICommand
    {
    
    
        //fileds
        private Action<object> _executeAction;
        private Func<object, bool> _canExecuteFunc;
        
        /Constructors
        public CommandBase(Action<object> executeAction)
        {
    
    
            _executeAction = executeAction;
            _canExecuteFunc = null;
        }

        public CommandBase(Action<object> executeAction, Func<object, bool> canExecuteFunc)
        {
    
    
            _executeAction = executeAction;
            _canExecuteFunc = canExecuteFunc;
        }
        
        //event: 由 CommandManager.RequerySuggested 代理事件
        public event EventHandler CanExecuteChanged
        {
    
    
            add {
    
     CommandManager.RequerySuggested += value; }
            remove {
    
     CommandManager.RequerySuggested -= value; }
        }

		//Methods
		public bool CanExecute(object parameter)
    	{
    
    
        	return _canExecuteFunc == null?true:_canExecuteFunc(parameter);
    	}
 
    	public void Execute(object parameter)
    	{
    
    
        	_executeAction(parameter);
    	}
 
    }
}

Supongo que te gusta

Origin blog.csdn.net/qq_40772692/article/details/127287839
Recomendado
Clasificación