• Prism框架中的DelagateCommand(上)


    背景

      在很多时候在WPF中我们都会使用到ICommand接口来定义我们的命令,然后将这个命令绑定到前台的控件比如Button上面,这个是一个很常规的操作,在后台的ViewModel中我们通常会使用一个实现了ICommand接口的DelegateCommand类来实例化我们定义的ICommand命令,我们这篇文章来重点分析一下Prism中这个DelegateCommand会写出什么不同的东西。

    常规实现

      在看常规的实现之前我们首先来看看ICommand这个接口中到底定义了一些什么东西?

    namespace System.Windows.Input
    {
        //
        // 摘要:
        //     Defines a command.
        public interface ICommand
        {
            //
            // 摘要:
            //     Occurs when changes occur that affect whether or not the command should execute.
            event EventHandler CanExecuteChanged;
    
            //
            // 摘要:
            //     Defines the method that determines whether the command can execute in its current
            //     state.
            //
            // 参数:
            //   parameter:
            //     Data used by the command. If the command does not require data to be passed,
            //     this object can be set to null.
            //
            // 返回结果:
            //     true if this command can be executed; otherwise, false.
            bool CanExecute(object parameter);
            //
            // 摘要:
            //     Defines the method to be called when the command is invoked.
            //
            // 参数:
            //   parameter:
            //     Data used by the command. If the command does not require data to be passed,
            //     this object can be set to null.
            void Execute(object parameter);
        }
    }
    

      这个接口是定义在System.Windows.Input命令空间下面的,主要包括两个方法和一个事件,我们的继承类就是要实现这个接口中的这些方法和事件

    /// <summary>
       /// 实现DelegateCommand
       /// </summary>
     internal  class DelegateCommand : ICommand
       {
           /// <summary>
           /// 命令所需执行的事件
           /// </summary>
           public Action<object> ExecuteAction { get; set; }
           /// <summary>
           /// 命令是否可用所执行的事件
           /// </summary>
           public Func<object, bool> CanExecuteFunc { get; set; }
          
     
           public DelegateCommand(Action<object> execute, Func<object, bool> canexecute)
           {
               ExecuteAction = execute;
               CanExecuteFunc = canexecute;
           }
     
           /// <summary>
           /// 命令可用性获取
           /// </summary>
           /// <param name="parameter"></param>
           /// <returns></returns>
           public bool CanExecute(object parameter)
           {
               return CanExecuteFunc(parameter);
           }
     
           public event EventHandler CanExecuteChanged
           {
               add { CommandManager.RequerySuggested += value; }
               remove { CommandManager.RequerySuggested -= value; }
           }
     
           /// <summary>
           /// 命令具体执行
           /// </summary>
           /// <param name="parameter"></param>
           public void Execute(object parameter)
           {
               ExecuteAction(parameter);
           }
       }
    

      这个里面这两个方法都比较好理解,关键是这个CanExecuteChanged这个事件订阅了CommandManager的RequerySuggested这个事件,对于这个实现请参阅MSDN的详细解释。

    Prism中的实现

      Prism框架中的DelegateCommand首先是继承自一个DelegateCommandBase的抽象类,我们先来看看这个抽象类的具体实现

    namespace Prism.Commands
    {
        /// <summary>
        /// An <see cref="ICommand"/> whose delegates can be attached for <see cref="Execute"/> and <see cref="CanExecute"/>.
        /// </summary>
        public abstract class DelegateCommandBase : ICommand, IActiveAware
        {
            private bool _isActive;
    
            private SynchronizationContext _synchronizationContext;
            private readonly HashSet<string> _observedPropertiesExpressions = new HashSet<string>();
    
            /// <summary>
            /// Creates a new instance of a <see cref="DelegateCommandBase"/>, specifying both the execute action and the can execute function.
            /// </summary>
            protected DelegateCommandBase()
            {
                _synchronizationContext = SynchronizationContext.Current;
            }
    
            /// <summary>
            /// Occurs when changes occur that affect whether or not the command should execute.
            /// </summary>
            public virtual event EventHandler CanExecuteChanged;
    
            /// <summary>
            /// Raises <see cref="ICommand.CanExecuteChanged"/> so every 
            /// command invoker can requery <see cref="ICommand.CanExecute"/>.
            /// </summary>
            protected virtual void OnCanExecuteChanged()
            {
                var handler = CanExecuteChanged;
                if (handler != null)
                {
                    if (_synchronizationContext != null && _synchronizationContext != SynchronizationContext.Current)
                        _synchronizationContext.Post((o) => handler.Invoke(this, EventArgs.Empty), null);
                    else
                        handler.Invoke(this, EventArgs.Empty);
                }
            }
    
            /// <summary>
            /// Raises <see cref="CanExecuteChanged"/> so every command invoker
            /// can requery to check if the command can execute.
            /// </summary>
            /// <remarks>Note that this will trigger the execution of <see cref="CanExecuteChanged"/> once for each invoker.</remarks>
            [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
            public void RaiseCanExecuteChanged()
            {
                OnCanExecuteChanged();
            }
    
            void ICommand.Execute(object parameter)
            {
                Execute(parameter);
            }
    
            bool ICommand.CanExecute(object parameter)
            {
                return CanExecute(parameter);
            }
    
            /// <summary>
            /// Handle the internal invocation of <see cref="ICommand.Execute(object)"/>
            /// </summary>
            /// <param name="parameter">Command Parameter</param>
            protected abstract void Execute(object parameter);
    
            /// <summary>
            /// Handle the internal invocation of <see cref="ICommand.CanExecute(object)"/>
            /// </summary>
            /// <param name="parameter"></param>
            /// <returns><see langword="true"/> if the Command Can Execute, otherwise <see langword="false" /></returns>
            protected abstract bool CanExecute(object parameter);
    
            /// <summary>
            /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
            /// </summary>
            /// <typeparam name="T">The object type containing the property specified in the expression.</typeparam>
            /// <param name="propertyExpression">The property expression. Example: ObservesProperty(() => PropertyName).</param>
            protected internal void ObservesPropertyInternal<T>(Expression<Func<T>> propertyExpression)
            {
                if (_observedPropertiesExpressions.Contains(propertyExpression.ToString()))
                {
                    throw new ArgumentException($"{propertyExpression.ToString()} is already being observed.",
                        nameof(propertyExpression));
                }
                else
                {
                    _observedPropertiesExpressions.Add(propertyExpression.ToString());
                    PropertyObserver.Observes(propertyExpression, RaiseCanExecuteChanged);
                }
            }
    
            #region IsActive
    
            /// <summary>
            /// Gets or sets a value indicating whether the object is active.
            /// </summary>
            /// <value><see langword="true" /> if the object is active; otherwise <see langword="false" />.</value>
            public bool IsActive
            {
                get { return _isActive; }
                set
                {
                    if (_isActive != value)
                    {
                        _isActive = value;
                        OnIsActiveChanged();
                    }
                }
            }
    
            /// <summary>
            /// Fired if the <see cref="IsActive"/> property changes.
            /// </summary>
            public virtual event EventHandler IsActiveChanged;
    
            /// <summary>
            /// This raises the <see cref="DelegateCommandBase.IsActiveChanged"/> event.
            /// </summary>
            protected virtual void OnIsActiveChanged()
            {
                IsActiveChanged?.Invoke(this, EventArgs.Empty);
            }
    
            #endregion
        }
    }
    

      这个DelegateCommandBase除了继承我们前面说的ICommand接口之外还实现了一个IActiveAware的接口,这个接口主要定义了一个IsActive的属性和IsActiveChanged的事件,这里就不再贴出具体的定义。后面我们来重点看一下Prism中的DelegateCommand的实现,然后重点分析一下这个里面的实现。

    namespace Prism.Commands
    {
        /// <summary>
        /// An <see cref="ICommand"/> whose delegates do not take any parameters for <see cref="Execute()"/> and <see cref="CanExecute()"/>.
        /// </summary>
        /// <see cref="DelegateCommandBase"/>
        /// <see cref="DelegateCommand{T}"/>
        public class DelegateCommand : DelegateCommandBase
        {
            Action _executeMethod;
            Func<bool> _canExecuteMethod;
    
            /// <summary>
            /// Creates a new instance of <see cref="DelegateCommand"/> with the <see cref="Action"/> to invoke on execution.
            /// </summary>
            /// <param name="executeMethod">The <see cref="Action"/> to invoke when <see cref="ICommand.Execute(object)"/> is called.</param>
            public DelegateCommand(Action executeMethod)
                : this(executeMethod, () => true)
            {
    
            }
    
            /// <summary>
            /// Creates a new instance of <see cref="DelegateCommand"/> with the <see cref="Action"/> to invoke on execution
            /// and a <see langword="Func" /> to query for determining if the command can execute.
            /// </summary>
            /// <param name="executeMethod">The <see cref="Action"/> to invoke when <see cref="ICommand.Execute"/> is called.</param>
            /// <param name="canExecuteMethod">The <see cref="Func{TResult}"/> to invoke when <see cref="ICommand.CanExecute"/> is called</param>
            public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
                : base()
            {
                if (executeMethod == null || canExecuteMethod == null)
                    throw new ArgumentNullException(nameof(executeMethod), Resources.DelegateCommandDelegatesCannotBeNull);
    
                _executeMethod = executeMethod;
                _canExecuteMethod = canExecuteMethod;
            }
    
            ///<summary>
            /// Executes the command.
            ///</summary>
            public void Execute()
            {
                _executeMethod();
            }
    
            /// <summary>
            /// Determines if the command can be executed.
            /// </summary>
            /// <returns>Returns <see langword="true"/> if the command can execute,otherwise returns <see langword="false"/>.</returns>
            public bool CanExecute()
            {
                return _canExecuteMethod();
            }
    
            /// <summary>
            /// Handle the internal invocation of <see cref="ICommand.Execute(object)"/>
            /// </summary>
            /// <param name="parameter">Command Parameter</param>
            protected override void Execute(object parameter)
            {
                Execute();
            }
    
            /// <summary>
            /// Handle the internal invocation of <see cref="ICommand.CanExecute(object)"/>
            /// </summary>
            /// <param name="parameter"></param>
            /// <returns><see langword="true"/> if the Command Can Execute, otherwise <see langword="false" /></returns>
            protected override bool CanExecute(object parameter)
            {
                return CanExecute();
            }
    
            /// <summary>
            /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
            /// </summary>
            /// <typeparam name="T">The object type containing the property specified in the expression.</typeparam>
            /// <param name="propertyExpression">The property expression. Example: ObservesProperty(() => PropertyName).</param>
            /// <returns>The current instance of DelegateCommand</returns>
            public DelegateCommand ObservesProperty<T>(Expression<Func<T>> propertyExpression)
            {
                ObservesPropertyInternal(propertyExpression);
                return this;
            }
    
            /// <summary>
            /// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
            /// </summary>
            /// <param name="canExecuteExpression">The property expression. Example: ObservesCanExecute(() => PropertyName).</param>
            /// <returns>The current instance of DelegateCommand</returns>
            public DelegateCommand ObservesCanExecute(Expression<Func<bool>> canExecuteExpression)
            {
                _canExecuteMethod = canExecuteExpression.Compile();
                ObservesPropertyInternal(canExecuteExpression);
                return this;
            }
        }
    }
    

      这个类中的其它的定义和我们常规的实现没有什么区别,重点是这个里面这个里面增加了ObservesPropertyObservesCanExecute这两个带Expression参数的方法,这两个方法其内部都调用了一个叫做ObservesPropertyInternal的方法,我们来看看这个在基类DelegateCommandBase中定义的方法。

    /// <summary>
            /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
            /// </summary>
            /// <typeparam name="T">The object type containing the property specified in the expression.</typeparam>
            /// <param name="propertyExpression">The property expression. Example: ObservesProperty(() => PropertyName).</param>
            protected internal void ObservesPropertyInternal<T>(Expression<Func<T>> propertyExpression)
            {
                if (_observedPropertiesExpressions.Contains(propertyExpression.ToString()))
                {
                    throw new ArgumentException($"{propertyExpression.ToString()} is already being observed.",
                        nameof(propertyExpression));
                }
                else
                {
                    _observedPropertiesExpressions.Add(propertyExpression.ToString());
                    PropertyObserver.Observes(propertyExpression, RaiseCanExecuteChanged);
                }
            }
    

      这个方法的内部会将当前的Expression参数传入一个类型为HashSet<string>的_observedPropertiesExpressions的局部变量里面,如何当前集合中存在该变量就抛出异常避免重复添加,如果没有添加过就添加到当前集合中,添加到集合中以后有调用了一个新的的方法,这个PropertyObserver.Observes方法会将当前的DelegateCommandBase 中的RaiseCanExecuteChanged方法作为参数传入到PropertyObserver类中的Observes方法中去,那我们再来看看这个方法到底是什么,我们接着来看代码。

    /// <summary>
        /// Provide a way to observe property changes of INotifyPropertyChanged objects and invokes a 
        /// custom action when the PropertyChanged event is fired.
        /// </summary>
        internal class PropertyObserver
        {
            private readonly Action _action;
    
            private PropertyObserver(Expression propertyExpression, Action action)
            {
                _action = action;
                SubscribeListeners(propertyExpression);
            }
    
            private void SubscribeListeners(Expression propertyExpression)
            {
                var propNameStack = new Stack<PropertyInfo>();
                while (propertyExpression is MemberExpression temp) // Gets the root of the property chain.
                {
                    propertyExpression = temp.Expression;
                    propNameStack.Push(temp.Member as PropertyInfo); // Records the member info as property info
                }
    
                if (!(propertyExpression is ConstantExpression constantExpression))
                    throw new NotSupportedException("Operation not supported for the given expression type. " +
                                                    "Only MemberExpression and ConstantExpression are currently supported.");
    
                var propObserverNodeRoot = new PropertyObserverNode(propNameStack.Pop(), _action);
                PropertyObserverNode previousNode = propObserverNodeRoot;
                foreach (var propName in propNameStack) // Create a node chain that corresponds to the property chain.
                {
                    var currentNode = new PropertyObserverNode(propName, _action);
                    previousNode.Next = currentNode;
                    previousNode = currentNode;
                }
    
                object propOwnerObject = constantExpression.Value;
    
                if (!(propOwnerObject is INotifyPropertyChanged inpcObject))
                    throw new InvalidOperationException("Trying to subscribe PropertyChanged listener in object that " +
                                                        $"owns '{propObserverNodeRoot.PropertyInfo.Name}' property, but the object does not implements INotifyPropertyChanged.");
    
                propObserverNodeRoot.SubscribeListenerFor(inpcObject);
            }
    
            /// <summary>
            /// Observes a property that implements INotifyPropertyChanged, and automatically calls a custom action on 
            /// property changed notifications. The given expression must be in this form: "() => Prop.NestedProp.PropToObserve".
            /// </summary>
            /// <param name="propertyExpression">Expression representing property to be observed. Ex.: "() => Prop.NestedProp.PropToObserve".</param>
            /// <param name="action">Action to be invoked when PropertyChanged event occurs.</param>
            internal static PropertyObserver Observes<T>(Expression<Func<T>> propertyExpression, Action action)
            {
                return new PropertyObserver(propertyExpression.Body, action);
            }
        }
    

        顾名思义PropertyObserver就是一个用来监控Propery属性变化的类,在这个PropertyObserver类中定义了一个静态的方法Observes方法,这个方法会创建一个PropertyObserver的对象,在这个构造方法中调用SubscribeListeners方法,我们再来看看这个方法的内部又创建了PropertyObserverNode的对象,这个对象又是什么?我们再来继续看。

    /// <summary>
        /// Represents each node of nested properties expression and takes care of 
        /// subscribing/unsubscribing INotifyPropertyChanged.PropertyChanged listeners on it.
        /// </summary>
        internal class PropertyObserverNode
        {
            private readonly Action _action;
            private INotifyPropertyChanged _inpcObject;
    
            public PropertyInfo PropertyInfo { get; }
            public PropertyObserverNode Next { get; set; }
    
            public PropertyObserverNode(PropertyInfo propertyInfo, Action action)
            {
                PropertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo));
                _action = () =>
                {
                    action?.Invoke();
                    if (Next == null) return;
                    Next.UnsubscribeListener();
                    GenerateNextNode();
                };
            }
    
            public void SubscribeListenerFor(INotifyPropertyChanged inpcObject)
            {
                _inpcObject = inpcObject;
                _inpcObject.PropertyChanged += OnPropertyChanged;
    
                if (Next != null) GenerateNextNode();
            }
    
            private void GenerateNextNode()
            {
                var nextProperty = PropertyInfo.GetValue(_inpcObject);
                if (nextProperty == null) return;
                if (!(nextProperty is INotifyPropertyChanged nextInpcObject))
                    throw new InvalidOperationException("Trying to subscribe PropertyChanged listener in object that " +
                                                        $"owns '{Next.PropertyInfo.Name}' property, but the object does not implements INotifyPropertyChanged.");
    
                Next.SubscribeListenerFor(nextInpcObject);
            }
    
            private void UnsubscribeListener()
            {
                if (_inpcObject != null)
                    _inpcObject.PropertyChanged -= OnPropertyChanged;
    
                Next?.UnsubscribeListener();
            }
    
            private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (e?.PropertyName == PropertyInfo.Name || string.IsNullOrEmpty(e?.PropertyName))
                {
                    _action?.Invoke();
                }
            }
        }
    

      这个类到底有什么作用,我们只需要监测一个属性的变化,这个PropertyObserverNode肯定是表示对当前属性的一个描述,这个节点里面还定义了一个Next表示当前属性节点的下一个节点,这个该如何解释呢?这里你应该想到了属性这个对象的复杂性,比如下面的一个例子就能够很好的说明,比如我们定义了下面的一个类。

     public class ComplexType : TestPurposeBindableBase
            {
                private int _intProperty;
                public int IntProperty
                {
                    get { return _intProperty; }
                    set { SetProperty(ref _intProperty, value); }
                }
    
                private ComplexType _innerComplexProperty;
                public ComplexType InnerComplexProperty
                {
                    get { return _innerComplexProperty; }
                    set { SetProperty(ref _innerComplexProperty, value); }
                }
            }
    

      现在我们需要监控这样一个属性,如下面的代码所示,我们现在需要监控的属性是 ComplexProperty.InnerComplexProperty.IntProperty,你怎么定义这个属性的节点,那你肯定需要将ComplexProperty和InnerComplexProperty以及IntProperty三个对象都定义为一个ObserverPropertyNode,并且这三个节点之间通过Next属性再在内部互相关联起来,这样通过这样的一个数据结构就能描述所有的属性结构,并且在PropertyObserver中就能监控到每一个属性的变化了,这样是不是就是一个通用框架做的事情。

    var  ComplexProperty = new ComplexType()
                {
                    InnerComplexProperty = new ComplexType()
                };
    

      到了这里是不是感觉很晕,当然这篇只是上篇,在下篇我们会对其中的每一个技术细节进行认真的分析,这篇主要是对整个过程有一个整体上面的把握。

    总结

      这篇文章中主要分析了下面两个问题以及一个疑问,下一篇文章我们将带着这些疑问来做更加细致的分析,从而完整理解这个框架中Prism的实现思路

      1 常规ICommand接口中各个方法以及事件的实现。

      2 整个Prism框架中如何实现这个ICommand接口,以及这个实现类DelegateCommand和PropertyObserver、PropertyObserverNode之间的关系和联系。

      3 一个疑问:这个DelegateCommand方法中定义的这两个ObservesProperty和ObservesCanExecute方法到底有什么作用以及到底该怎么用?

  • 相关阅读:
    NOI2019 I 君的商店
    CF1326F
    APIO2016 划艇
    LeetCode-Remove Nth Node From End of List
    LeetCode-Remove Element
    LeetCode-Remove Duplicates from Sorted List II
    LeetCode-Remove Duplicates from Sorted List
    LeetCode-Unique Paths II
    LeetCode-Unique Paths
    LeetCode-Remove Duplicates from Sorted Array II
  • 原文地址:https://www.cnblogs.com/seekdream/p/14815289.html
Copyright © 2020-2023  润新知