PriorityValue.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  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 prioritised 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 priorites. 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(PriorityValue, object, object)"/> method on the
  23. /// owner object is fired with the old and new values.
  24. /// </remarks>
  25. internal class PriorityValue
  26. {
  27. private readonly IPriorityValueOwner _owner;
  28. private readonly Type _valueType;
  29. private readonly Dictionary<int, PriorityLevel> _levels = new Dictionary<int, PriorityLevel>();
  30. private object _value;
  31. private readonly Func<object, object> _validate;
  32. /// <summary>
  33. /// Initializes a new instance of the <see cref="PriorityValue"/> class.
  34. /// </summary>
  35. /// <param name="owner">The owner of the object.</param>
  36. /// <param name="property">The property that the value represents.</param>
  37. /// <param name="valueType">The value type.</param>
  38. /// <param name="validate">An optional validation function.</param>
  39. public PriorityValue(
  40. IPriorityValueOwner owner,
  41. AvaloniaProperty property,
  42. Type valueType,
  43. Func<object, object> validate = null)
  44. {
  45. _owner = owner;
  46. Property = property;
  47. _valueType = valueType;
  48. _value = AvaloniaProperty.UnsetValue;
  49. ValuePriority = int.MaxValue;
  50. _validate = validate;
  51. }
  52. /// <summary>
  53. /// Gets the property that the value represents.
  54. /// </summary>
  55. public AvaloniaProperty Property { get; }
  56. /// <summary>
  57. /// Gets the current value.
  58. /// </summary>
  59. public object Value => _value;
  60. /// <summary>
  61. /// Gets the priority of the binding that is currently active.
  62. /// </summary>
  63. public int ValuePriority
  64. {
  65. get;
  66. private set;
  67. }
  68. /// <summary>
  69. /// Adds a new binding.
  70. /// </summary>
  71. /// <param name="binding">The binding.</param>
  72. /// <param name="priority">The binding priority.</param>
  73. /// <returns>
  74. /// A disposable that will remove the binding.
  75. /// </returns>
  76. public IDisposable Add(IObservable<object> binding, int priority)
  77. {
  78. return GetLevel(priority).Add(binding);
  79. }
  80. /// <summary>
  81. /// Sets the value for a specified priority.
  82. /// </summary>
  83. /// <param name="value">The value.</param>
  84. /// <param name="priority">The priority</param>
  85. public void SetValue(object value, int priority)
  86. {
  87. GetLevel(priority).DirectValue = value;
  88. }
  89. /// <summary>
  90. /// Gets the currently active bindings on this object.
  91. /// </summary>
  92. /// <returns>An enumerable collection of bindings.</returns>
  93. public IEnumerable<PriorityBindingEntry> GetBindings()
  94. {
  95. foreach (var level in _levels)
  96. {
  97. foreach (var binding in level.Value.Bindings)
  98. {
  99. yield return binding;
  100. }
  101. }
  102. }
  103. /// <summary>
  104. /// Returns diagnostic string that can help the user debug the bindings in effect on
  105. /// this object.
  106. /// </summary>
  107. /// <returns>A diagnostic string.</returns>
  108. public string GetDiagnostic()
  109. {
  110. var b = new StringBuilder();
  111. var first = true;
  112. foreach (var level in _levels)
  113. {
  114. if (!first)
  115. {
  116. b.AppendLine();
  117. }
  118. b.Append(ValuePriority == level.Key ? "*" : string.Empty);
  119. b.Append("Priority ");
  120. b.Append(level.Key);
  121. b.Append(": ");
  122. b.AppendLine(level.Value.Value?.ToString() ?? "(null)");
  123. b.AppendLine("--------");
  124. b.Append("Direct: ");
  125. b.AppendLine(level.Value.DirectValue?.ToString() ?? "(null)");
  126. foreach (var binding in level.Value.Bindings)
  127. {
  128. b.Append(level.Value.ActiveBindingIndex == binding.Index ? "*" : string.Empty);
  129. b.Append(binding.Description ?? binding.Observable.GetType().Name);
  130. b.Append(": ");
  131. b.AppendLine(binding.Value?.ToString() ?? "(null)");
  132. }
  133. first = false;
  134. }
  135. return b.ToString();
  136. }
  137. /// <summary>
  138. /// Called when the value for a priority level changes.
  139. /// </summary>
  140. /// <param name="level">The priority level of the changed entry.</param>
  141. public void LevelValueChanged(PriorityLevel level)
  142. {
  143. if (level.Priority <= ValuePriority)
  144. {
  145. if (level.Value != AvaloniaProperty.UnsetValue)
  146. {
  147. UpdateValue(level.Value, level.Priority);
  148. }
  149. else
  150. {
  151. foreach (var i in _levels.Values.OrderBy(x => x.Priority))
  152. {
  153. if (i.Value != AvaloniaProperty.UnsetValue)
  154. {
  155. UpdateValue(i.Value, i.Priority);
  156. return;
  157. }
  158. }
  159. UpdateValue(AvaloniaProperty.UnsetValue, int.MaxValue);
  160. }
  161. }
  162. }
  163. /// <summary>
  164. /// Called when a priority level encounters an error.
  165. /// </summary>
  166. /// <param name="level">The priority level of the changed entry.</param>
  167. /// <param name="error">The binding error.</param>
  168. public void LevelError(PriorityLevel level, BindingNotification error)
  169. {
  170. Logger.Log(
  171. LogEventLevel.Error,
  172. LogArea.Binding,
  173. _owner,
  174. "Error binding to {Target}.{Property}: {Message}",
  175. _owner,
  176. Property,
  177. error.Error.Message);
  178. }
  179. /// <summary>
  180. /// Causes a revalidation of the value.
  181. /// </summary>
  182. public void Revalidate()
  183. {
  184. if (_validate != null)
  185. {
  186. PriorityLevel level;
  187. if (_levels.TryGetValue(ValuePriority, out level))
  188. {
  189. UpdateValue(level.Value, level.Priority);
  190. }
  191. }
  192. }
  193. /// <summary>
  194. /// Gets the <see cref="PriorityLevel"/> with the specified priority, creating it if it
  195. /// doesn't already exist.
  196. /// </summary>
  197. /// <param name="priority">The priority.</param>
  198. /// <returns>The priority level.</returns>
  199. private PriorityLevel GetLevel(int priority)
  200. {
  201. PriorityLevel result;
  202. if (!_levels.TryGetValue(priority, out result))
  203. {
  204. result = new PriorityLevel(this, priority);
  205. _levels.Add(priority, result);
  206. }
  207. return result;
  208. }
  209. /// <summary>
  210. /// Updates the current <see cref="Value"/> and notifies all subscibers.
  211. /// </summary>
  212. /// <param name="value">The value to set.</param>
  213. /// <param name="priority">The priority level that the value came from.</param>
  214. private void UpdateValue(object value, int priority)
  215. {
  216. var notification = value as BindingNotification;
  217. object castValue;
  218. if (notification != null)
  219. {
  220. value = (notification.HasValue) ? notification.Value : null;
  221. }
  222. if (TypeUtilities.TryCast(_valueType, value, out castValue))
  223. {
  224. var old = _value;
  225. if (_validate != null && castValue != AvaloniaProperty.UnsetValue)
  226. {
  227. castValue = _validate(castValue);
  228. }
  229. ValuePriority = priority;
  230. _value = castValue;
  231. if (notification?.HasValue == true)
  232. {
  233. notification.SetValue(castValue);
  234. }
  235. if (notification == null || notification.HasValue)
  236. {
  237. _owner?.Changed(this, old, _value);
  238. }
  239. if (notification != null)
  240. {
  241. _owner?.BindingNotificationReceived(this, notification);
  242. }
  243. }
  244. else
  245. {
  246. Logger.Error(
  247. LogArea.Binding,
  248. _owner,
  249. "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
  250. Property.Name,
  251. _valueType,
  252. value,
  253. value.GetType());
  254. }
  255. }
  256. }
  257. }