using System; using System.Reflection; using Avalonia.Data; using Avalonia.PropertyStore; using Avalonia.Reactive; using Avalonia.Styling; using Avalonia.Utilities; namespace Avalonia { /// /// Base class for styled properties. /// public abstract class StyledPropertyBase : AvaloniaProperty, IStyledPropertyAccessor { private readonly bool _inherits; /// /// 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 value validation callback. /// A callback. protected StyledPropertyBase( string name, Type ownerType, StyledPropertyMetadata metadata, bool inherits = false, Func? validate = null, Action? notifying = null) : base(name, ownerType, metadata, notifying) { _inherits = inherits; ValidateValue = validate; HasCoercion |= metadata.CoerceValue != null; if (validate?.Invoke(metadata.DefaultValue) == false) { throw new ArgumentException( $"'{metadata.DefaultValue}' is not a valid default value for '{name}'."); } } /// /// Initializes a new instance of the class. /// /// The property to add the owner to. /// The type of the class that registers the property. protected StyledPropertyBase(StyledPropertyBase source, Type ownerType) : base(source, ownerType, null) { _inherits = source.Inherits; } /// /// Gets a value indicating whether the property inherits its value. /// /// /// A value indicating whether the property inherits its value. /// public override bool Inherits => _inherits; /// /// Gets the value validation callback for the property. /// public Func? ValidateValue { get; } /// /// Gets a value indicating whether this property has any value coercion callbacks defined /// in its metadata. /// internal bool HasCoercion { get; private set; } public TValue CoerceValue(IAvaloniaObject 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 : IAvaloniaObject { 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 : IAvaloniaObject { base.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}'."); } } HasCoercion |= metadata.CoerceValue != null; 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 new EffectiveValue(o, 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 (value == BindingOperations.DoNothing) { return null; } else if (value == UnsetValue) { target.ClearValue(this); return null; } else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var converted)) { return target.SetValue(this, (TValue)converted!, priority); } else { var type = value?.GetType().FullName ?? "(null)"; throw new ArgumentException($"Invalid value for Property '{Name}': '{value}' ({type})"); } } internal override IDisposable RouteBind( AvaloniaObject target, IObservable source, BindingPriority priority) { return target.Bind(this, source, priority); } private object? GetDefaultBoxedValue(Type type) { _ = type ?? throw new ArgumentNullException(nameof(type)); return GetMetadata(type).DefaultValue; } } }