DelegateCommand{T}.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq.Expressions;
  4. using System.Reflection;
  5. using System.Resources;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. namespace HandyControl.Interactivity.Commands
  9. {
  10. /// <summary>
  11. /// An <see cref="ICommand"/> whose delegates can be attached for <see cref="Execute(T)"/> and <see cref="CanExecute(T)"/>.
  12. /// </summary>
  13. /// <typeparam name="T">Parameter type.</typeparam>
  14. /// <remarks>
  15. /// The constructor deliberately prevents the use of value types.
  16. /// Because ICommand takes an object, having a value type for T would cause unexpected behavior when CanExecute(null) is called during XAML initialization for command bindings.
  17. /// Using default(T) was considered and rejected as a solution because the implementor would not be able to distinguish between a valid and defaulted values.
  18. /// <para/>
  19. /// Instead, callers should support a value type by using a nullable value type and checking the HasValue property before using the Value property.
  20. /// <example>
  21. /// <code>
  22. /// public MyClass()
  23. /// {
  24. /// this.submitCommand = new DelegateCommand&lt;int?&gt;(this.Submit, this.CanSubmit);
  25. /// }
  26. ///
  27. /// private bool CanSubmit(int? customerId)
  28. /// {
  29. /// return (customerId.HasValue &amp;&amp; customers.Contains(customerId.Value));
  30. /// }
  31. /// </code>
  32. /// </example>
  33. /// </remarks>
  34. public class DelegateCommand<T> : DelegateCommandBase
  35. {
  36. readonly Action<T> _executeMethod;
  37. Func<T, bool> _canExecuteMethod;
  38. /// <summary>
  39. /// Initializes a new instance of <see cref="DelegateCommand{T}"/>.
  40. /// </summary>
  41. /// <param name="executeMethod">Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.</param>
  42. /// <remarks><see cref="CanExecute(T)"/> will always return true.</remarks>
  43. public DelegateCommand(Action<T> executeMethod)
  44. : this(executeMethod, (o) => true)
  45. {
  46. }
  47. /// <summary>
  48. /// Initializes a new instance of <see cref="DelegateCommand{T}"/>.
  49. /// </summary>
  50. /// <param name="executeMethod">Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.</param>
  51. /// <param name="canExecuteMethod">Delegate to execute when CanExecute is called on the command. This can be null.</param>
  52. /// <exception cref="ArgumentNullException">When both <paramref name="executeMethod"/> and <paramref name="canExecuteMethod"/> are <see langword="null" />.</exception>
  53. public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
  54. : base()
  55. {
  56. if (executeMethod == null || canExecuteMethod == null)
  57. throw new ArgumentNullException(nameof(executeMethod), "Neither the executeMethod nor the canExecuteMethod delegates can be null.");
  58. TypeInfo genericTypeInfo = typeof(T).GetTypeInfo();
  59. // DelegateCommand allows object or Nullable<>.
  60. // note: Nullable<> is a struct so we cannot use a class constraint.
  61. if (genericTypeInfo.IsValueType)
  62. {
  63. if ((!genericTypeInfo.IsGenericType) || (!typeof(Nullable<>).GetTypeInfo().IsAssignableFrom(genericTypeInfo.GetGenericTypeDefinition().GetTypeInfo())))
  64. {
  65. throw new InvalidCastException("T for DelegateCommand&lt;T&gt; is not an object nor Nullable.");
  66. }
  67. }
  68. _executeMethod = executeMethod;
  69. _canExecuteMethod = canExecuteMethod;
  70. }
  71. ///<summary>
  72. ///Executes the command and invokes the <see cref="Action{T}"/> provided during construction.
  73. ///</summary>
  74. ///<param name="parameter">Data used by the command.</param>
  75. public void Execute(T parameter)
  76. {
  77. try
  78. {
  79. _executeMethod(parameter);
  80. }
  81. catch (Exception ex)
  82. {
  83. if (!ExceptionHandler.CanHandle(ex))
  84. throw;
  85. ExceptionHandler.Handle(ex, parameter);
  86. }
  87. }
  88. ///<summary>
  89. ///Determines if the command can execute by invoked the <see cref="Func{T,Bool}"/> provided during construction.
  90. ///</summary>
  91. ///<param name="parameter">Data used by the command to determine if it can execute.</param>
  92. ///<returns>
  93. ///<see langword="true" /> if this command can be executed; otherwise, <see langword="false" />.
  94. ///</returns>
  95. public bool CanExecute(T parameter)
  96. {
  97. try
  98. {
  99. return _canExecuteMethod(parameter);
  100. }
  101. catch (Exception ex)
  102. {
  103. if (!ExceptionHandler.CanHandle(ex))
  104. throw;
  105. ExceptionHandler.Handle(ex, parameter);
  106. return false;
  107. }
  108. }
  109. /// <summary>
  110. /// Handle the internal invocation of <see cref="ICommand.Execute(object)"/>
  111. /// </summary>
  112. /// <param name="parameter">Command Parameter</param>
  113. protected override void Execute(object? parameter)
  114. {
  115. try
  116. {
  117. // Note: We don't call Execute because we would potentially invoke the Try/Catch twice.
  118. // It is also needed here incase (T)parameter throws the exception
  119. _executeMethod((T)parameter!);
  120. }
  121. catch (Exception ex)
  122. {
  123. if (!ExceptionHandler.CanHandle(ex))
  124. throw;
  125. ExceptionHandler.Handle(ex, parameter);
  126. }
  127. }
  128. /// <summary>
  129. /// Handle the internal invocation of <see cref="ICommand.CanExecute(object)"/>
  130. /// </summary>
  131. /// <param name="parameter"></param>
  132. /// <returns><see langword="true"/> if the Command Can Execute, otherwise <see langword="false" /></returns>
  133. protected override bool CanExecute(object? parameter)
  134. {
  135. try
  136. {
  137. // Note: We don't call Execute because we would potentially invoke the Try/Catch twice.
  138. // It is also needed here incase (T)parameter throws the exception
  139. return CanExecute((T)parameter!);
  140. }
  141. catch (Exception ex)
  142. {
  143. if (!ExceptionHandler.CanHandle(ex))
  144. throw;
  145. ExceptionHandler.Handle(ex, parameter);
  146. return false;
  147. }
  148. }
  149. /// <summary>
  150. /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
  151. /// </summary>
  152. /// <typeparam name="TType">The type of the return value of the method that this delegate encapsulates</typeparam>
  153. /// <param name="propertyExpression">The property expression. Example: ObservesProperty(() => PropertyName).</param>
  154. /// <returns>The current instance of DelegateCommand</returns>
  155. public DelegateCommand<T> ObservesProperty<TType>(Expression<Func<TType>> propertyExpression)
  156. {
  157. ObservesPropertyInternal(propertyExpression);
  158. return this;
  159. }
  160. /// <summary>
  161. /// 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.
  162. /// </summary>
  163. /// <param name="canExecuteExpression">The property expression. Example: ObservesCanExecute(() => PropertyName).</param>
  164. /// <returns>The current instance of DelegateCommand</returns>
  165. public DelegateCommand<T> ObservesCanExecute(Expression<Func<bool>> canExecuteExpression)
  166. {
  167. Expression<Func<T, bool>> expression = System.Linq.Expressions.Expression.Lambda<Func<T, bool>>(canExecuteExpression.Body, System.Linq.Expressions.Expression.Parameter(typeof(T), "o"));
  168. _canExecuteMethod = expression.Compile();
  169. ObservesPropertyInternal(canExecuteExpression);
  170. return this;
  171. }
  172. /// <summary>
  173. /// Registers an callback if an exception is encountered while executing the <see cref="DelegateCommand"/>
  174. /// </summary>
  175. /// <param name="catch">The Callback</param>
  176. /// <returns>The current instance of <see cref="DelegateCommand"/></returns>
  177. public DelegateCommand<T> Catch(Action<Exception> @catch)
  178. {
  179. ExceptionHandler.Register<Exception>(@catch);
  180. return this;
  181. }
  182. /// <summary>
  183. /// Registers an callback if an exception is encountered while executing the <see cref="DelegateCommand"/>
  184. /// </summary>
  185. /// <param name="catch">The Callback</param>
  186. /// <returns>The current instance of <see cref="DelegateCommand"/></returns>
  187. public DelegateCommand<T> Catch(Action<Exception, object> @catch)
  188. {
  189. ExceptionHandler.Register<Exception>(@catch);
  190. return this;
  191. }
  192. /// <summary>
  193. /// Registers an callback if an exception is encountered while executing the <see cref="DelegateCommand"/>
  194. /// </summary>
  195. /// <typeparam name="TException">The Exception Type</typeparam>
  196. /// <param name="catch">The Callback</param>
  197. /// <returns>The current instance of <see cref="DelegateCommand"/></returns>
  198. public DelegateCommand<T> Catch<TException>(Action<TException> @catch)
  199. where TException : Exception
  200. {
  201. ExceptionHandler.Register<TException>(@catch);
  202. return this;
  203. }
  204. /// <summary>
  205. /// Registers an callback if an exception is encountered while executing the <see cref="DelegateCommand"/>
  206. /// </summary>
  207. /// <typeparam name="TException">The Exception Type</typeparam>
  208. /// <param name="catch">The Callback</param>
  209. /// <returns>The current instance of <see cref="DelegateCommand"/></returns>
  210. public DelegateCommand<T> Catch<TException>(Action<TException, object> @catch)
  211. where TException : Exception
  212. {
  213. ExceptionHandler.Register<TException>(@catch);
  214. return this;
  215. }
  216. /// <summary>
  217. /// Registers an async callback if an exception is encountered while executing the <see cref="DelegateCommand"/>
  218. /// </summary>
  219. /// <param name="catch">The Callback</param>
  220. /// <returns>The current instance of <see cref="DelegateCommand"/></returns>
  221. public DelegateCommand<T> Catch(Func<Exception, Task> @catch)
  222. {
  223. ExceptionHandler.Register<Exception>(@catch);
  224. return this;
  225. }
  226. /// <summary>
  227. /// Registers an async callback if an exception is encountered while executing the <see cref="DelegateCommand"/>
  228. /// </summary>
  229. /// <param name="catch">The Callback</param>
  230. /// <returns>The current instance of <see cref="DelegateCommand"/></returns>
  231. public DelegateCommand<T> Catch(Func<Exception, object, Task> @catch)
  232. {
  233. ExceptionHandler.Register<Exception>(@catch);
  234. return this;
  235. }
  236. /// <summary>
  237. /// Registers an async callback if an exception is encountered while executing the <see cref="DelegateCommand"/>
  238. /// </summary>
  239. /// <typeparam name="TException">The Exception Type</typeparam>
  240. /// <param name="catch">The Callback</param>
  241. /// <returns>The current instance of <see cref="DelegateCommand"/></returns>
  242. public DelegateCommand<T> Catch<TException>(Func<TException, Task> @catch)
  243. where TException : Exception
  244. {
  245. ExceptionHandler.Register<TException>(@catch);
  246. return this;
  247. }
  248. /// <summary>
  249. /// Registers an async callback if an exception is encountered while executing the <see cref="DelegateCommand"/>
  250. /// </summary>
  251. /// <typeparam name="TException">The Exception Type</typeparam>
  252. /// <param name="catch">The Callback</param>
  253. /// <returns>The current instance of <see cref="DelegateCommand"/></returns>
  254. public DelegateCommand<T> Catch<TException>(Func<TException, object, Task> @catch)
  255. where TException : Exception
  256. {
  257. ExceptionHandler.Register<TException>(@catch);
  258. return this;
  259. }
  260. }
  261. }