PriorityValue.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Text;
  7. using Avalonia.Data;
  8. using Avalonia.Logging;
  9. using Avalonia.Utilities;
  10. namespace Avalonia
  11. {
  12. /// <summary>
  13. /// Maintains a list of prioritized bindings together with a current value.
  14. /// </summary>
  15. /// <remarks>
  16. /// Bindings, in the form of <see cref="IObservable{Object}"/>s are added to the object using
  17. /// the <see cref="Add"/> method. With the observable is passed a priority, where lower values
  18. /// represent higher priorities. The current <see cref="Value"/> is selected from the highest
  19. /// priority binding that doesn't return <see cref="AvaloniaProperty.UnsetValue"/>. Where there
  20. /// are multiple bindings registered with the same priority, the most recently added binding
  21. /// has a higher priority. Each time the value changes, the
  22. /// <see cref="IPriorityValueOwner.Changed"/> method on the
  23. /// owner object is fired with the old and new values.
  24. /// </remarks>
  25. internal class PriorityValue
  26. {
  27. private readonly Type _valueType;
  28. private readonly SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>();
  29. private readonly Func<object, object> _validate;
  30. private (object value, int priority) _value;
  31. /// <summary>
  32. /// Initializes a new instance of the <see cref="PriorityValue"/> class.
  33. /// </summary>
  34. /// <param name="owner">The owner of the object.</param>
  35. /// <param name="property">The property that the value represents.</param>
  36. /// <param name="valueType">The value type.</param>
  37. /// <param name="validate">An optional validation function.</param>
  38. public PriorityValue(
  39. IPriorityValueOwner owner,
  40. AvaloniaProperty property,
  41. Type valueType,
  42. Func<object, object> validate = null)
  43. {
  44. Owner = owner;
  45. Property = property;
  46. _valueType = valueType;
  47. _value = (AvaloniaProperty.UnsetValue, int.MaxValue);
  48. _validate = validate;
  49. }
  50. /// <summary>
  51. /// Gets a value indicating whether the property is animating.
  52. /// </summary>
  53. public bool IsAnimating
  54. {
  55. get
  56. {
  57. return ValuePriority <= (int)BindingPriority.Animation &&
  58. GetLevel(ValuePriority).ActiveBindingIndex != -1;
  59. }
  60. }
  61. /// <summary>
  62. /// Gets the owner of the value.
  63. /// </summary>
  64. public IPriorityValueOwner Owner { get; }
  65. /// <summary>
  66. /// Gets the property that the value represents.
  67. /// </summary>
  68. public AvaloniaProperty Property { get; }
  69. /// <summary>
  70. /// Gets the current value.
  71. /// </summary>
  72. public object Value => _value.value;
  73. /// <summary>
  74. /// Gets the priority of the binding that is currently active.
  75. /// </summary>
  76. public int ValuePriority => _value.priority;
  77. /// <summary>
  78. /// Adds a new binding.
  79. /// </summary>
  80. /// <param name="binding">The binding.</param>
  81. /// <param name="priority">The binding priority.</param>
  82. /// <returns>
  83. /// A disposable that will remove the binding.
  84. /// </returns>
  85. public IDisposable Add(IObservable<object> binding, int priority)
  86. {
  87. return GetLevel(priority).Add(binding);
  88. }
  89. /// <summary>
  90. /// Sets the value for a specified priority.
  91. /// </summary>
  92. /// <param name="value">The value.</param>
  93. /// <param name="priority">The priority</param>
  94. public void SetValue(object value, int priority)
  95. {
  96. GetLevel(priority).DirectValue = value;
  97. }
  98. /// <summary>
  99. /// Gets the currently active bindings on this object.
  100. /// </summary>
  101. /// <returns>An enumerable collection of bindings.</returns>
  102. public IEnumerable<PriorityBindingEntry> GetBindings()
  103. {
  104. foreach (var level in _levels)
  105. {
  106. foreach (var binding in level.Value.Bindings)
  107. {
  108. yield return binding;
  109. }
  110. }
  111. }
  112. /// <summary>
  113. /// Returns diagnostic string that can help the user debug the bindings in effect on
  114. /// this object.
  115. /// </summary>
  116. /// <returns>A diagnostic string.</returns>
  117. public string GetDiagnostic()
  118. {
  119. var b = new StringBuilder();
  120. var first = true;
  121. foreach (var level in _levels)
  122. {
  123. if (!first)
  124. {
  125. b.AppendLine();
  126. }
  127. b.Append(ValuePriority == level.Key ? "*" : string.Empty);
  128. b.Append("Priority ");
  129. b.Append(level.Key);
  130. b.Append(": ");
  131. b.AppendLine(level.Value.Value?.ToString() ?? "(null)");
  132. b.AppendLine("--------");
  133. b.Append("Direct: ");
  134. b.AppendLine(level.Value.DirectValue?.ToString() ?? "(null)");
  135. foreach (var binding in level.Value.Bindings)
  136. {
  137. b.Append(level.Value.ActiveBindingIndex == binding.Index ? "*" : string.Empty);
  138. b.Append(binding.Description ?? binding.Observable.GetType().Name);
  139. b.Append(": ");
  140. b.AppendLine(binding.Value?.ToString() ?? "(null)");
  141. }
  142. first = false;
  143. }
  144. return b.ToString();
  145. }
  146. /// <summary>
  147. /// Called when the value for a priority level changes.
  148. /// </summary>
  149. /// <param name="level">The priority level of the changed entry.</param>
  150. public void LevelValueChanged(PriorityLevel level)
  151. {
  152. if (level.Priority <= ValuePriority)
  153. {
  154. if (level.Value != AvaloniaProperty.UnsetValue)
  155. {
  156. UpdateValue(level.Value, level.Priority);
  157. }
  158. else
  159. {
  160. foreach (var i in _levels.Values.OrderBy(x => x.Priority))
  161. {
  162. if (i.Value != AvaloniaProperty.UnsetValue)
  163. {
  164. UpdateValue(i.Value, i.Priority);
  165. return;
  166. }
  167. }
  168. UpdateValue(AvaloniaProperty.UnsetValue, int.MaxValue);
  169. }
  170. }
  171. }
  172. /// <summary>
  173. /// Called when a priority level encounters an error.
  174. /// </summary>
  175. /// <param name="level">The priority level of the changed entry.</param>
  176. /// <param name="error">The binding error.</param>
  177. public void LevelError(PriorityLevel level, BindingNotification error)
  178. {
  179. error.LogIfError(Owner, Property);
  180. }
  181. /// <summary>
  182. /// Causes a revalidation of the value.
  183. /// </summary>
  184. public void Revalidate()
  185. {
  186. if (_validate != null)
  187. {
  188. PriorityLevel level;
  189. if (_levels.TryGetValue(ValuePriority, out level))
  190. {
  191. UpdateValue(level.Value, level.Priority);
  192. }
  193. }
  194. }
  195. /// <summary>
  196. /// Gets the <see cref="PriorityLevel"/> with the specified priority, creating it if it
  197. /// doesn't already exist.
  198. /// </summary>
  199. /// <param name="priority">The priority.</param>
  200. /// <returns>The priority level.</returns>
  201. private PriorityLevel GetLevel(int priority)
  202. {
  203. PriorityLevel result;
  204. if (!_levels.TryGetValue(priority, out result))
  205. {
  206. result = new PriorityLevel(this, priority);
  207. _levels.Add(priority, result);
  208. }
  209. return result;
  210. }
  211. /// <summary>
  212. /// Updates the current <see cref="Value"/> and notifies all subscribers.
  213. /// </summary>
  214. /// <param name="value">The value to set.</param>
  215. /// <param name="priority">The priority level that the value came from.</param>
  216. private void UpdateValue(object value, int priority)
  217. {
  218. Owner.Setter.SetAndNotify(Property,
  219. ref _value,
  220. UpdateCore,
  221. (value, priority));
  222. }
  223. private bool UpdateCore(
  224. object update,
  225. ref (object value, int priority) backing,
  226. Action<Action> notify)
  227. => UpdateCore(((object, int))update, ref backing, notify);
  228. private bool UpdateCore(
  229. (object value, int priority) update,
  230. ref (object value, int priority) backing,
  231. Action<Action> notify)
  232. {
  233. var val = update.value;
  234. var notification = val as BindingNotification;
  235. object castValue;
  236. if (notification != null)
  237. {
  238. val = (notification.HasValue) ? notification.Value : null;
  239. }
  240. if (TypeUtilities.TryConvertImplicit(_valueType, val, out castValue))
  241. {
  242. var old = backing.value;
  243. if (_validate != null && castValue != AvaloniaProperty.UnsetValue)
  244. {
  245. castValue = _validate(castValue);
  246. }
  247. backing = (castValue, update.priority);
  248. if (notification?.HasValue == true)
  249. {
  250. notification.SetValue(castValue);
  251. }
  252. if (notification == null || notification.HasValue)
  253. {
  254. notify(() => Owner?.Changed(Property, ValuePriority, old, Value));
  255. }
  256. if (notification != null)
  257. {
  258. Owner?.BindingNotificationReceived(Property, notification);
  259. }
  260. }
  261. else
  262. {
  263. Logger.Error(
  264. LogArea.Binding,
  265. Owner,
  266. "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
  267. Property.Name,
  268. _valueType,
  269. val,
  270. val?.GetType());
  271. }
  272. return true;
  273. }
  274. }
  275. }