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; } } }
这个类中的其它的定义和我们常规的实现没有什么区别,重点是这个里面这个里面增加了ObservesProperty和ObservesCanExecute这两个带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方法到底有什么作用以及到底该怎么用?