背景
在很多時候在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方法到底有什么作用以及到底該怎么用?
