AvaloniaObjectExtensions.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. using System;
  2. using Avalonia.Reactive;
  3. using Avalonia.Data;
  4. namespace Avalonia
  5. {
  6. /// <summary>
  7. /// Provides extension methods for <see cref="AvaloniaObject"/> and related classes.
  8. /// </summary>
  9. public static class AvaloniaObjectExtensions
  10. {
  11. /// <summary>
  12. /// Converts an <see cref="IObservable{T}"/> to an <see cref="IBinding"/>.
  13. /// </summary>
  14. /// <typeparam name="T">The type produced by the observable.</typeparam>
  15. /// <param name="source">The observable</param>
  16. /// <returns>An <see cref="IBinding"/>.</returns>
  17. public static IBinding ToBinding<T>(this IObservable<T> source)
  18. {
  19. return new BindingAdaptor(source.Select(x => (object?)x));
  20. }
  21. /// <summary>
  22. /// Gets an observable for an <see cref="AvaloniaProperty"/>.
  23. /// </summary>
  24. /// <param name="o">The object.</param>
  25. /// <param name="property">The property.</param>
  26. /// <returns>
  27. /// An observable which fires immediately with the current value of the property on the
  28. /// object and subsequently each time the property value changes.
  29. /// </returns>
  30. /// <remarks>
  31. /// The subscription to <paramref name="o"/> is created using a weak reference.
  32. /// </remarks>
  33. public static IObservable<object?> GetObservable(this AvaloniaObject o, AvaloniaProperty property)
  34. {
  35. return new AvaloniaPropertyObservable<object?>(
  36. o ?? throw new ArgumentNullException(nameof(o)),
  37. property ?? throw new ArgumentNullException(nameof(property)));
  38. }
  39. /// <summary>
  40. /// Gets an observable for an <see cref="AvaloniaProperty"/>.
  41. /// </summary>
  42. /// <param name="o">The object.</param>
  43. /// <typeparam name="T">The property type.</typeparam>
  44. /// <param name="property">The property.</param>
  45. /// <returns>
  46. /// An observable which fires immediately with the current value of the property on the
  47. /// object and subsequently each time the property value changes.
  48. /// </returns>
  49. /// <remarks>
  50. /// The subscription to <paramref name="o"/> is created using a weak reference.
  51. /// </remarks>
  52. public static IObservable<T> GetObservable<T>(this AvaloniaObject o, AvaloniaProperty<T> property)
  53. {
  54. return new AvaloniaPropertyObservable<T>(
  55. o ?? throw new ArgumentNullException(nameof(o)),
  56. property ?? throw new ArgumentNullException(nameof(property)));
  57. }
  58. /// <summary>
  59. /// Gets an observable for an <see cref="AvaloniaProperty"/>.
  60. /// </summary>
  61. /// <param name="o">The object.</param>
  62. /// <param name="property">The property.</param>
  63. /// <returns>
  64. /// An observable which fires immediately with the current value of the property on the
  65. /// object and subsequently each time the property value changes.
  66. /// </returns>
  67. /// <remarks>
  68. /// The subscription to <paramref name="o"/> is created using a weak reference.
  69. /// </remarks>
  70. public static IObservable<BindingValue<object?>> GetBindingObservable(
  71. this AvaloniaObject o,
  72. AvaloniaProperty property)
  73. {
  74. return new AvaloniaPropertyBindingObservable<object?>(
  75. o ?? throw new ArgumentNullException(nameof(o)),
  76. property ?? throw new ArgumentNullException(nameof(property)));
  77. }
  78. /// <summary>
  79. /// Gets an observable for an <see cref="AvaloniaProperty"/>.
  80. /// </summary>
  81. /// <param name="o">The object.</param>
  82. /// <typeparam name="T">The property type.</typeparam>
  83. /// <param name="property">The property.</param>
  84. /// <returns>
  85. /// An observable which fires immediately with the current value of the property on the
  86. /// object and subsequently each time the property value changes.
  87. /// </returns>
  88. /// <remarks>
  89. /// The subscription to <paramref name="o"/> is created using a weak reference.
  90. /// </remarks>
  91. public static IObservable<BindingValue<T>> GetBindingObservable<T>(
  92. this AvaloniaObject o,
  93. AvaloniaProperty<T> property)
  94. {
  95. return new AvaloniaPropertyBindingObservable<T>(
  96. o ?? throw new ArgumentNullException(nameof(o)),
  97. property ?? throw new ArgumentNullException(nameof(property)));
  98. }
  99. /// <summary>
  100. /// Gets an observable that listens for property changed events for an
  101. /// <see cref="AvaloniaProperty"/>.
  102. /// </summary>
  103. /// <param name="o">The object.</param>
  104. /// <param name="property">The property.</param>
  105. /// <returns>
  106. /// An observable which when subscribed pushes the property changed event args
  107. /// each time a <see cref="AvaloniaObject.PropertyChanged"/> event is raised
  108. /// for the specified property.
  109. /// </returns>
  110. public static IObservable<AvaloniaPropertyChangedEventArgs> GetPropertyChangedObservable(
  111. this AvaloniaObject o,
  112. AvaloniaProperty property)
  113. {
  114. return new AvaloniaPropertyChangedObservable(
  115. o ?? throw new ArgumentNullException(nameof(o)),
  116. property ?? throw new ArgumentNullException(nameof(property)));
  117. }
  118. /// <summary>
  119. /// Binds an <see cref="AvaloniaProperty"/> to an observable.
  120. /// </summary>
  121. /// <typeparam name="T">The type of the property.</typeparam>
  122. /// <param name="target">The object.</param>
  123. /// <param name="property">The property.</param>
  124. /// <param name="source">The observable.</param>
  125. /// <param name="priority">The priority of the binding.</param>
  126. /// <returns>
  127. /// A disposable which can be used to terminate the binding.
  128. /// </returns>
  129. public static IDisposable Bind<T>(
  130. this AvaloniaObject target,
  131. AvaloniaProperty<T> property,
  132. IObservable<BindingValue<T>> source,
  133. BindingPriority priority = BindingPriority.LocalValue)
  134. {
  135. target = target ?? throw new ArgumentNullException(nameof(target));
  136. property = property ?? throw new ArgumentNullException(nameof(property));
  137. source = source ?? throw new ArgumentNullException(nameof(source));
  138. return property switch
  139. {
  140. StyledPropertyBase<T> styled => target.Bind(styled, source, priority),
  141. DirectPropertyBase<T> direct => target.Bind(direct, source),
  142. _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
  143. };
  144. }
  145. /// <summary>
  146. /// Binds an <see cref="AvaloniaProperty"/> to an observable.
  147. /// </summary>
  148. /// <param name="target">The object.</param>
  149. /// <param name="property">The property.</param>
  150. /// <param name="source">The observable.</param>
  151. /// <param name="priority">The priority of the binding.</param>
  152. /// <returns>
  153. /// A disposable which can be used to terminate the binding.
  154. /// </returns>
  155. public static IDisposable Bind<T>(
  156. this AvaloniaObject target,
  157. AvaloniaProperty<T> property,
  158. IObservable<T> source,
  159. BindingPriority priority = BindingPriority.LocalValue)
  160. {
  161. return property switch
  162. {
  163. StyledPropertyBase<T> styled => target.Bind(styled, source, priority),
  164. DirectPropertyBase<T> direct => target.Bind(direct, source),
  165. _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
  166. };
  167. }
  168. /// <summary>
  169. /// Binds a property on an <see cref="AvaloniaObject"/> to an <see cref="IBinding"/>.
  170. /// </summary>
  171. /// <param name="target">The object.</param>
  172. /// <param name="property">The property to bind.</param>
  173. /// <param name="binding">The binding.</param>
  174. /// <param name="anchor">
  175. /// An optional anchor from which to locate required context. When binding to objects that
  176. /// are not in the logical tree, certain types of binding need an anchor into the tree in
  177. /// order to locate named controls or resources. The <paramref name="anchor"/> parameter
  178. /// can be used to provice this context.
  179. /// </param>
  180. /// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns>
  181. public static IDisposable Bind(
  182. this AvaloniaObject target,
  183. AvaloniaProperty property,
  184. IBinding binding,
  185. object? anchor = null)
  186. {
  187. target = target ?? throw new ArgumentNullException(nameof(target));
  188. property = property ?? throw new ArgumentNullException(nameof(property));
  189. binding = binding ?? throw new ArgumentNullException(nameof(binding));
  190. var metadata = property.GetMetadata(target.GetType()) as IDirectPropertyMetadata;
  191. var result = binding.Initiate(
  192. target,
  193. property,
  194. anchor,
  195. metadata?.EnableDataValidation ?? false);
  196. if (result != null)
  197. {
  198. return BindingOperations.Apply(target, property, result, anchor);
  199. }
  200. else
  201. {
  202. return Disposable.Empty;
  203. }
  204. }
  205. /// <summary>
  206. /// Gets a <see cref="AvaloniaProperty"/> value.
  207. /// </summary>
  208. /// <typeparam name="T">The type of the property.</typeparam>
  209. /// <param name="target">The object.</param>
  210. /// <param name="property">The property.</param>
  211. /// <returns>The value.</returns>
  212. public static T GetValue<T>(this AvaloniaObject target, AvaloniaProperty<T> property)
  213. {
  214. target = target ?? throw new ArgumentNullException(nameof(target));
  215. property = property ?? throw new ArgumentNullException(nameof(property));
  216. return property switch
  217. {
  218. StyledPropertyBase<T> styled => target.GetValue(styled),
  219. DirectPropertyBase<T> direct => target.GetValue(direct),
  220. _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
  221. };
  222. }
  223. /// <summary>
  224. /// Gets an <see cref="AvaloniaProperty"/> base value.
  225. /// </summary>
  226. /// <param name="target">The object.</param>
  227. /// <param name="property">The property.</param>
  228. /// <remarks>
  229. /// For styled properties, gets the value of the property excluding animated values, otherwise
  230. /// <see cref="AvaloniaProperty.UnsetValue"/>. Note that this method does not return
  231. /// property values that come from inherited or default values.
  232. ///
  233. /// For direct properties returns the current value of the property.
  234. /// </remarks>
  235. public static object? GetBaseValue(
  236. this AvaloniaObject target,
  237. AvaloniaProperty property)
  238. {
  239. target = target ?? throw new ArgumentNullException(nameof(target));
  240. property = property ?? throw new ArgumentNullException(nameof(property));
  241. return property.RouteGetBaseValue(target);
  242. }
  243. /// <summary>
  244. /// Gets an <see cref="AvaloniaProperty"/> base value.
  245. /// </summary>
  246. /// <param name="target">The object.</param>
  247. /// <param name="property">The property.</param>
  248. /// <remarks>
  249. /// For styled properties, gets the value of the property excluding animated values, otherwise
  250. /// <see cref="Optional{T}.Empty"/>. Note that this method does not return property values
  251. /// that come from inherited or default values.
  252. ///
  253. /// For direct properties returns the current value of the property.
  254. /// </remarks>
  255. public static Optional<T> GetBaseValue<T>(
  256. this AvaloniaObject target,
  257. AvaloniaProperty<T> property)
  258. {
  259. target = target ?? throw new ArgumentNullException(nameof(target));
  260. property = property ?? throw new ArgumentNullException(nameof(property));
  261. return property switch
  262. {
  263. StyledPropertyBase<T> styled => target.GetBaseValue(styled),
  264. DirectPropertyBase<T> direct => target.GetValue(direct),
  265. _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
  266. };
  267. }
  268. /// <summary>
  269. /// Subscribes to a property changed notifications for changes that originate from a
  270. /// <typeparamref name="TTarget"/>.
  271. /// </summary>
  272. /// <typeparam name="TTarget">The type of the property change sender.</typeparam>
  273. /// <param name="observable">The property changed observable.</param>
  274. /// <param name="action">
  275. /// The method to call. The parameters are the sender and the event args.
  276. /// </param>
  277. /// <returns>A disposable that can be used to terminate the subscription.</returns>
  278. public static IDisposable AddClassHandler<TTarget>(
  279. this IObservable<AvaloniaPropertyChangedEventArgs> observable,
  280. Action<TTarget, AvaloniaPropertyChangedEventArgs> action)
  281. where TTarget : AvaloniaObject
  282. {
  283. return observable.Subscribe(new ClassHandlerObserver<TTarget>(action));
  284. }
  285. /// <summary>
  286. /// Subscribes to a property changed notifications for changes that originate from a
  287. /// <typeparamref name="TTarget"/>.
  288. /// </summary>
  289. /// <typeparam name="TTarget">The type of the property change sender.</typeparam>
  290. /// /// <typeparam name="TValue">The type of the property..</typeparam>
  291. /// <param name="observable">The property changed observable.</param>
  292. /// <param name="action">
  293. /// The method to call. The parameters are the sender and the event args.
  294. /// </param>
  295. /// <returns>A disposable that can be used to terminate the subscription.</returns>
  296. public static IDisposable AddClassHandler<TTarget, TValue>(
  297. this IObservable<AvaloniaPropertyChangedEventArgs<TValue>> observable,
  298. Action<TTarget, AvaloniaPropertyChangedEventArgs<TValue>> action) where TTarget : AvaloniaObject
  299. {
  300. return observable.Subscribe(new ClassHandlerObserver<TTarget, TValue>(action));
  301. }
  302. private class BindingAdaptor : IBinding
  303. {
  304. private IObservable<object?> _source;
  305. public BindingAdaptor(IObservable<object?> source)
  306. {
  307. this._source = source;
  308. }
  309. public InstancedBinding? Initiate(
  310. AvaloniaObject target,
  311. AvaloniaProperty? targetProperty,
  312. object? anchor = null,
  313. bool enableDataValidation = false)
  314. {
  315. return InstancedBinding.OneWay(_source);
  316. }
  317. }
  318. private class ClassHandlerObserver<TTarget, TValue> : IObserver<AvaloniaPropertyChangedEventArgs<TValue>>
  319. {
  320. private readonly Action<TTarget, AvaloniaPropertyChangedEventArgs<TValue>> _action;
  321. public ClassHandlerObserver(Action<TTarget, AvaloniaPropertyChangedEventArgs<TValue>> action)
  322. {
  323. _action = action;
  324. }
  325. public void OnCompleted()
  326. {
  327. }
  328. public void OnError(Exception error)
  329. {
  330. }
  331. public void OnNext(AvaloniaPropertyChangedEventArgs<TValue> value)
  332. {
  333. if (value.Sender is TTarget target)
  334. {
  335. _action(target, value);
  336. }
  337. }
  338. }
  339. private class ClassHandlerObserver<TTarget> : IObserver<AvaloniaPropertyChangedEventArgs>
  340. {
  341. private readonly Action<TTarget, AvaloniaPropertyChangedEventArgs> _action;
  342. public ClassHandlerObserver(Action<TTarget, AvaloniaPropertyChangedEventArgs> action)
  343. {
  344. _action = action;
  345. }
  346. public void OnCompleted()
  347. {
  348. }
  349. public void OnError(Exception error)
  350. {
  351. }
  352. public void OnNext(AvaloniaPropertyChangedEventArgs value)
  353. {
  354. if (value.Sender is TTarget target)
  355. {
  356. _action(target, value);
  357. }
  358. }
  359. }
  360. }
  361. }