StyledProperty.cs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. using System;
  2. using System.Diagnostics.CodeAnalysis;
  3. using Avalonia.Data;
  4. using Avalonia.PropertyStore;
  5. using Avalonia.Utilities;
  6. namespace Avalonia
  7. {
  8. /// <summary>
  9. /// A styled avalonia property.
  10. /// </summary>
  11. public class StyledProperty<TValue> : AvaloniaProperty<TValue>, IStyledPropertyAccessor
  12. {
  13. /// <summary>
  14. /// Initializes a new instance of the <see cref="StyledProperty{T}"/> class.
  15. /// </summary>
  16. /// <param name="name">The name of the property.</param>
  17. /// <param name="ownerType">The type of the class that registers the property.</param>
  18. /// <param name="metadata">The property metadata.</param>
  19. /// <param name="inherits">Whether the property inherits its value.</param>
  20. /// <param name="validate">
  21. /// <para>A method which returns "false" for values that are never valid for this property.</para>
  22. /// <para>This method is not part of the property's metadata and so cannot be changed after registration.</para>
  23. /// </param>
  24. /// <param name="notifying">A <see cref="AvaloniaProperty.Notifying"/> callback.</param>
  25. public StyledProperty(
  26. string name,
  27. Type ownerType,
  28. StyledPropertyMetadata<TValue> metadata,
  29. bool inherits = false,
  30. Func<TValue, bool>? validate = null,
  31. Action<AvaloniaObject, bool>? notifying = null)
  32. : base(name, ownerType, metadata, notifying)
  33. {
  34. Inherits = inherits;
  35. ValidateValue = validate;
  36. if (validate?.Invoke(metadata.DefaultValue) == false)
  37. {
  38. throw new ArgumentException(
  39. $"'{metadata.DefaultValue}' is not a valid default value for '{name}'.");
  40. }
  41. }
  42. /// <summary>
  43. /// A method which returns "false" for values that are never valid for this property.
  44. /// </summary>
  45. public Func<TValue, bool>? ValidateValue { get; }
  46. /// <summary>
  47. /// Registers the property on another type.
  48. /// </summary>
  49. /// <typeparam name="TOwner">The type of the additional owner.</typeparam>
  50. /// <returns>The property.</returns>
  51. public StyledProperty<TValue> AddOwner<TOwner>(StyledPropertyMetadata<TValue>? metadata = null) where TOwner : AvaloniaObject
  52. {
  53. AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this);
  54. if (metadata != null)
  55. {
  56. OverrideMetadata<TOwner>(metadata);
  57. }
  58. return this;
  59. }
  60. public TValue CoerceValue(AvaloniaObject instance, TValue baseValue)
  61. {
  62. var metadata = GetMetadata(instance.GetType());
  63. if (metadata.CoerceValue != null)
  64. {
  65. return metadata.CoerceValue.Invoke(instance, baseValue);
  66. }
  67. return baseValue;
  68. }
  69. /// <summary>
  70. /// Gets the default value for the property on the specified type.
  71. /// </summary>
  72. /// <param name="type">The type.</param>
  73. /// <returns>The default value.</returns>
  74. public TValue GetDefaultValue(Type type)
  75. {
  76. return GetMetadata(type).DefaultValue;
  77. }
  78. /// <summary>
  79. /// Gets the property metadata for the specified type.
  80. /// </summary>
  81. /// <param name="type">The type.</param>
  82. /// <returns>
  83. /// The property metadata.
  84. /// </returns>
  85. public new StyledPropertyMetadata<TValue> GetMetadata(Type type)
  86. {
  87. _ = type ?? throw new ArgumentNullException(nameof(type));
  88. return (StyledPropertyMetadata<TValue>)base.GetMetadata(type);
  89. }
  90. /// <summary>
  91. /// Overrides the default value for the property on the specified type.
  92. /// </summary>
  93. /// <typeparam name="T">The type.</typeparam>
  94. /// <param name="defaultValue">The default value.</param>
  95. public void OverrideDefaultValue<T>(TValue defaultValue) where T : AvaloniaObject
  96. {
  97. OverrideDefaultValue(typeof(T), defaultValue);
  98. }
  99. /// <summary>
  100. /// Overrides the default value for the property on the specified type.
  101. /// </summary>
  102. /// <param name="type">The type.</param>
  103. /// <param name="defaultValue">The default value.</param>
  104. public void OverrideDefaultValue(Type type, TValue defaultValue)
  105. {
  106. OverrideMetadata(type, new StyledPropertyMetadata<TValue>(defaultValue));
  107. }
  108. /// <summary>
  109. /// Overrides the metadata for the property on the specified type.
  110. /// </summary>
  111. /// <typeparam name="T">The type.</typeparam>
  112. /// <param name="metadata">The metadata.</param>
  113. public void OverrideMetadata<T>(StyledPropertyMetadata<TValue> metadata) where T : AvaloniaObject => OverrideMetadata(typeof(T), metadata);
  114. /// <summary>
  115. /// Overrides the metadata for the property on the specified type.
  116. /// </summary>
  117. /// <param name="type">The type.</param>
  118. /// <param name="metadata">The metadata.</param>
  119. public void OverrideMetadata(Type type, StyledPropertyMetadata<TValue> metadata)
  120. {
  121. if (ValidateValue != null)
  122. {
  123. if (!ValidateValue(metadata.DefaultValue))
  124. {
  125. throw new ArgumentException(
  126. $"'{metadata.DefaultValue}' is not a valid default value for '{Name}'.");
  127. }
  128. }
  129. base.OverrideMetadata(type, metadata);
  130. }
  131. /// <summary>
  132. /// Gets the string representation of the property.
  133. /// </summary>
  134. /// <returns>The property's string representation.</returns>
  135. public override string ToString()
  136. {
  137. return Name;
  138. }
  139. /// <inheritdoc/>
  140. object? IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type);
  141. bool IStyledPropertyAccessor.ValidateValue(object? value)
  142. {
  143. if (value is null && !typeof(TValue).IsValueType)
  144. return ValidateValue?.Invoke(default!) ?? true;
  145. if (value is TValue typed)
  146. return ValidateValue?.Invoke(typed) ?? true;
  147. return false;
  148. }
  149. internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o)
  150. {
  151. return o.GetValueStore().CreateEffectiveValue(this);
  152. }
  153. /// <inheritdoc/>
  154. internal override void RouteClearValue(AvaloniaObject o)
  155. {
  156. o.ClearValue<TValue>(this);
  157. }
  158. /// <inheritdoc/>
  159. internal override object? RouteGetValue(AvaloniaObject o)
  160. {
  161. return o.GetValue<TValue>(this);
  162. }
  163. /// <inheritdoc/>
  164. internal override object? RouteGetBaseValue(AvaloniaObject o)
  165. {
  166. var value = o.GetBaseValue<TValue>(this);
  167. return value.HasValue ? value.Value : AvaloniaProperty.UnsetValue;
  168. }
  169. /// <inheritdoc/>
  170. internal override IDisposable? RouteSetValue(
  171. AvaloniaObject target,
  172. object? value,
  173. BindingPriority priority)
  174. {
  175. if (ShouldSetValue(target, value, out var converted))
  176. return target.SetValue<TValue>(this, converted, priority);
  177. return null;
  178. }
  179. internal override void RouteSetCurrentValue(AvaloniaObject target, object? value)
  180. {
  181. if (ShouldSetValue(target, value, out var converted))
  182. target.SetCurrentValue<TValue>(this, converted);
  183. }
  184. internal override IDisposable RouteBind(
  185. AvaloniaObject target,
  186. IObservable<object?> source,
  187. BindingPriority priority)
  188. {
  189. return target.Bind<TValue>(this, source, priority);
  190. }
  191. [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
  192. private bool ShouldSetValue(AvaloniaObject target, object? value, [NotNullWhen(true)] out TValue? converted)
  193. {
  194. if (value == BindingOperations.DoNothing)
  195. {
  196. converted = default;
  197. return false;
  198. }
  199. if (value == UnsetValue)
  200. {
  201. target.ClearValue(this);
  202. converted = default;
  203. return false;
  204. }
  205. else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var v))
  206. {
  207. converted = (TValue)v!;
  208. return true;
  209. }
  210. else
  211. {
  212. var type = value?.GetType().FullName ?? "(null)";
  213. throw new ArgumentException($"Invalid value for Property '{Name}': '{value}' ({type})");
  214. }
  215. }
  216. private object? GetDefaultBoxedValue(Type type)
  217. {
  218. _ = type ?? throw new ArgumentNullException(nameof(type));
  219. return GetMetadata(type).DefaultValue;
  220. }
  221. }
  222. }