zoukankan      html  css  js  c++  java
  • 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方法到底有什么作用以及到底该怎么用?

  • 相关阅读:
    485串口接线
    mvc3 升级mvc5
    VB连接ACCESS数据库,使用 LIKE 通配符问题
    VB6 读写西门子PLC
    可用的 .net core 支持 RSA 私钥加密工具类
    解决 Win7 远程桌面 已停止工作的问题
    解决 WinForm 重写 CreateParams 隐藏窗口以后的显示问题
    解决安装 .net framework 发生 extracting files error 问题
    CentOS7 安装配置笔记
    通过特殊处理 Resize 事件解决 WinForm 加载时闪烁问题的一个方法
  • 原文地址:https://www.cnblogs.com/seekdream/p/14815289.html
Copyright © 2011-2022 走看看