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方法到底有什么作用以及到底该怎么用?

浙公网安备 33010602011771号