using System; using System.Diagnostics.CodeAnalysis; using Avalonia.Data; using Avalonia.PropertyStore; using Avalonia.Utilities; namespace Avalonia { /// /// A styled avalonia property. /// public class StyledProperty : AvaloniaProperty, IStyledPropertyAccessor { /// /// Initializes a new instance of the class. /// /// The name of the property. /// The type of the class that registers the property. /// The property metadata. /// Whether the property inherits its value. /// /// A method which returns "false" for values that are never valid for this property. /// This method is not part of the property's metadata and so cannot be changed after registration. /// /// A callback. public StyledProperty( string name, Type ownerType, StyledPropertyMetadata metadata, bool inherits = false, Func? validate = null, Action? notifying = null) : base(name, ownerType, metadata, notifying) { Inherits = inherits; ValidateValue = validate; if (validate?.Invoke(metadata.DefaultValue) == false) { throw new ArgumentException( $"'{metadata.DefaultValue}' is not a valid default value for '{name}'."); } } /// /// A method which returns "false" for values that are never valid for this property. /// public Func? ValidateValue { get; } /// /// Registers the property on another type. /// /// The type of the additional owner. /// The property. public StyledProperty AddOwner(StyledPropertyMetadata? metadata = null) where TOwner : AvaloniaObject { AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this); if (metadata != null) { OverrideMetadata(metadata); } return this; } public TValue CoerceValue(AvaloniaObject instance, TValue baseValue) { var metadata = GetMetadata(instance.GetType()); if (metadata.CoerceValue != null) { return metadata.CoerceValue.Invoke(instance, baseValue); } return baseValue; } /// /// Gets the default value for the property on the specified type. /// /// The type. /// The default value. public TValue GetDefaultValue(Type type) { return GetMetadata(type).DefaultValue; } /// /// Gets the property metadata for the specified type. /// /// The type. /// /// The property metadata. /// public new StyledPropertyMetadata GetMetadata(Type type) { _ = type ?? throw new ArgumentNullException(nameof(type)); return (StyledPropertyMetadata)base.GetMetadata(type); } /// /// Overrides the default value for the property on the specified type. /// /// The type. /// The default value. public void OverrideDefaultValue(TValue defaultValue) where T : AvaloniaObject { OverrideDefaultValue(typeof(T), defaultValue); } /// /// Overrides the default value for the property on the specified type. /// /// The type. /// The default value. public void OverrideDefaultValue(Type type, TValue defaultValue) { OverrideMetadata(type, new StyledPropertyMetadata(defaultValue)); } /// /// Overrides the metadata for the property on the specified type. /// /// The type. /// The metadata. public void OverrideMetadata(StyledPropertyMetadata metadata) where T : AvaloniaObject => OverrideMetadata(typeof(T), metadata); /// /// Overrides the metadata for the property on the specified type. /// /// The type. /// The metadata. public void OverrideMetadata(Type type, StyledPropertyMetadata metadata) { if (ValidateValue != null) { if (!ValidateValue(metadata.DefaultValue)) { throw new ArgumentException( $"'{metadata.DefaultValue}' is not a valid default value for '{Name}'."); } } base.OverrideMetadata(type, metadata); } /// /// Gets the string representation of the property. /// /// The property's string representation. public override string ToString() { return Name; } /// object? IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type); bool IStyledPropertyAccessor.ValidateValue(object? value) { if (value is null && !typeof(TValue).IsValueType) return ValidateValue?.Invoke(default!) ?? true; if (value is TValue typed) return ValidateValue?.Invoke(typed) ?? true; return false; } internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o) { return o.GetValueStore().CreateEffectiveValue(this); } /// internal override void RouteClearValue(AvaloniaObject o) { o.ClearValue(this); } /// internal override object? RouteGetValue(AvaloniaObject o) { return o.GetValue(this); } /// internal override object? RouteGetBaseValue(AvaloniaObject o) { var value = o.GetBaseValue(this); return value.HasValue ? value.Value : AvaloniaProperty.UnsetValue; } /// internal override IDisposable? RouteSetValue( AvaloniaObject target, object? value, BindingPriority priority) { if (ShouldSetValue(target, value, out var converted)) return target.SetValue(this, converted, priority); return null; } internal override void RouteSetCurrentValue(AvaloniaObject target, object? value) { if (ShouldSetValue(target, value, out var converted)) target.SetCurrentValue(this, converted); } internal override IDisposable RouteBind( AvaloniaObject target, IObservable source, BindingPriority priority) { return target.Bind(this, source, priority); } [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] private bool ShouldSetValue(AvaloniaObject target, object? value, [NotNullWhen(true)] out TValue? converted) { if (value == BindingOperations.DoNothing) { converted = default; return false; } if (value == UnsetValue) { target.ClearValue(this); converted = default; return false; } else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var v)) { converted = (TValue)v!; return true; } else { var type = value?.GetType().FullName ?? "(null)"; throw new ArgumentException($"Invalid value for Property '{Name}': '{value}' ({type})"); } } private object? GetDefaultBoxedValue(Type type) { _ = type ?? throw new ArgumentNullException(nameof(type)); return GetMetadata(type).DefaultValue; } } }