MethodToCommandConverter.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. using System;
  2. using System.ComponentModel;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Globalization;
  5. using System.Linq;
  6. using System.Linq.Expressions;
  7. using System.Reflection;
  8. using System.Windows.Input;
  9. using Avalonia.Utilities;
  10. namespace Avalonia.Data.Converters
  11. {
  12. [RequiresUnreferencedCode(TrimmingMessages.ReflectionBindingRequiresUnreferencedCodeMessage)]
  13. class MethodToCommandConverter : ICommand
  14. {
  15. readonly static Func<object?, bool> AlwaysEnabled = (_) => true;
  16. readonly static MethodInfo tryConvert = typeof(TypeUtilities)
  17. .GetMethod(nameof(TypeUtilities.TryConvert), BindingFlags.Public | BindingFlags.Static)!;
  18. readonly static PropertyInfo currentCulture = typeof(CultureInfo)
  19. .GetProperty(nameof(CultureInfo.CurrentCulture), BindingFlags.Public | BindingFlags.Static)!;
  20. readonly Func<object?, bool> canExecute;
  21. readonly Action<object?> execute;
  22. readonly WeakPropertyChangedProxy? weakPropertyChanged;
  23. readonly PropertyChangedEventHandler? propertyChangedEventHandler;
  24. readonly string[]? dependencyProperties;
  25. public MethodToCommandConverter(Delegate action)
  26. {
  27. var target = action.Target;
  28. var canExecuteMethodName = "Can" + action.Method.Name;
  29. var parameters = action.Method.GetParameters();
  30. var parameterInfo = parameters.Length == 0 ? null : parameters[0].ParameterType;
  31. if (parameterInfo == null)
  32. {
  33. execute = CreateExecute(target, action.Method);
  34. }
  35. else
  36. {
  37. execute = CreateExecute(target, action.Method, parameterInfo);
  38. }
  39. var canExecuteMethod = action.Method.DeclaringType?.GetRuntimeMethods()
  40. .FirstOrDefault(m => m.Name == canExecuteMethodName
  41. && m.GetParameters().Length == 1
  42. && m.GetParameters()[0].ParameterType == typeof(object));
  43. if (canExecuteMethod == null)
  44. {
  45. canExecute = AlwaysEnabled;
  46. }
  47. else
  48. {
  49. canExecute = CreateCanExecute(target, canExecuteMethod);
  50. dependencyProperties = canExecuteMethod
  51. .GetCustomAttributes(typeof(Metadata.DependsOnAttribute), true)
  52. .OfType<Metadata.DependsOnAttribute>()
  53. .Select(x => x.Name)
  54. .ToArray();
  55. if (dependencyProperties.Any()
  56. && target is INotifyPropertyChanged inpc)
  57. {
  58. propertyChangedEventHandler = OnPropertyChanged;
  59. weakPropertyChanged = new WeakPropertyChangedProxy(inpc, propertyChangedEventHandler);
  60. }
  61. }
  62. }
  63. void OnPropertyChanged(object? sender, PropertyChangedEventArgs args)
  64. {
  65. if (string.IsNullOrWhiteSpace(args.PropertyName)
  66. || dependencyProperties?.Contains(args.PropertyName) == true)
  67. {
  68. Threading.Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty)
  69. , Threading.DispatcherPriority.Input);
  70. }
  71. }
  72. #pragma warning disable 0067
  73. public event EventHandler? CanExecuteChanged;
  74. #pragma warning restore 0067
  75. public bool CanExecute(object? parameter) => canExecute(parameter);
  76. public void Execute(object? parameter) => execute(parameter);
  77. static Action<object?> CreateExecute(object? target
  78. , System.Reflection.MethodInfo method)
  79. {
  80. var parameter = Expression.Parameter(typeof(object), "parameter");
  81. var instance = ConvertTarget(target, method);
  82. var call = Expression.Call
  83. (
  84. instance,
  85. method
  86. );
  87. return Expression
  88. .Lambda<Action<object?>>(call, parameter)
  89. .Compile();
  90. }
  91. static Action<object?> CreateExecute(object? target
  92. , System.Reflection.MethodInfo method
  93. , Type parameterType)
  94. {
  95. var parameter = Expression.Parameter(typeof(object), "parameter");
  96. var instance = ConvertTarget(target, method);
  97. Expression body;
  98. if (parameterType == typeof(object))
  99. {
  100. body = Expression.Call(instance,
  101. method,
  102. parameter
  103. );
  104. }
  105. else
  106. {
  107. var arg0 = Expression.Variable(typeof(object), "argX");
  108. var convertCall = Expression.Call(tryConvert,
  109. Expression.Constant(parameterType),
  110. parameter,
  111. Expression.Property(null, currentCulture),
  112. arg0
  113. );
  114. var call = Expression.Call(instance,
  115. method,
  116. Expression.Convert(arg0, parameterType)
  117. );
  118. body = Expression.Block(new[] { arg0 },
  119. convertCall,
  120. call
  121. );
  122. }
  123. return Expression
  124. .Lambda<Action<object?>>(body, parameter)
  125. .Compile();
  126. }
  127. static Func<object?, bool> CreateCanExecute(object? target
  128. , System.Reflection.MethodInfo method)
  129. {
  130. var parameter = Expression.Parameter(typeof(object), "parameter");
  131. var instance = ConvertTarget(target, method);
  132. var call = Expression.Call
  133. (
  134. instance,
  135. method,
  136. parameter
  137. );
  138. return Expression
  139. .Lambda<Func<object?, bool>>(call, parameter)
  140. .Compile();
  141. }
  142. private static Expression? ConvertTarget(object? target, MethodInfo method) =>
  143. target is null ? null : Expression.Convert(Expression.Constant(target), method.DeclaringType!);
  144. internal class WeakPropertyChangedProxy
  145. {
  146. readonly WeakReference<PropertyChangedEventHandler?> _listener = new WeakReference<PropertyChangedEventHandler?>(null);
  147. readonly PropertyChangedEventHandler _handler;
  148. internal WeakReference<INotifyPropertyChanged?> Source { get; } = new WeakReference<INotifyPropertyChanged?>(null);
  149. public WeakPropertyChangedProxy()
  150. {
  151. _handler = new PropertyChangedEventHandler(OnPropertyChanged);
  152. }
  153. public WeakPropertyChangedProxy(INotifyPropertyChanged source, PropertyChangedEventHandler listener) : this()
  154. {
  155. SubscribeTo(source, listener);
  156. }
  157. public void SubscribeTo(INotifyPropertyChanged source, PropertyChangedEventHandler listener)
  158. {
  159. source.PropertyChanged += _handler;
  160. Source.SetTarget(source);
  161. _listener.SetTarget(listener);
  162. }
  163. public void Unsubscribe()
  164. {
  165. if (Source.TryGetTarget(out var source) && source != null)
  166. source.PropertyChanged -= _handler;
  167. Source.SetTarget(null);
  168. _listener.SetTarget(null);
  169. }
  170. void OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
  171. {
  172. if (_listener.TryGetTarget(out var handler) && handler != null)
  173. handler(sender, e);
  174. else
  175. Unsubscribe();
  176. }
  177. }
  178. }
  179. }