using System;
using System.Collections.Generic;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Utilities;
namespace Avalonia
{
///
/// Base class for avalonia properties.
///
public abstract class AvaloniaProperty : IEquatable, IPropertyInfo
{
///
/// Represents an unset property value.
///
public static readonly object UnsetValue = new UnsetValueType();
private static int s_nextId;
private readonly AvaloniaPropertyMetadata _defaultMetadata;
private readonly Dictionary _metadata;
private readonly Dictionary _metadataCache = new Dictionary();
private bool _hasMetadataOverrides;
///
/// Initializes a new instance of the class.
///
/// The name of the property.
/// The type of the property's value.
/// The type of the class that registers the property.
/// The property metadata.
/// A callback.
protected AvaloniaProperty(
string name,
Type valueType,
Type ownerType,
AvaloniaPropertyMetadata metadata,
Action? notifying = null)
{
_ = name ?? throw new ArgumentNullException(nameof(name));
if (name.Contains("."))
{
throw new ArgumentException("'name' may not contain periods.");
}
_metadata = new Dictionary();
Name = name;
PropertyType = valueType ?? throw new ArgumentNullException(nameof(valueType));
OwnerType = ownerType ?? throw new ArgumentNullException(nameof(ownerType));
Notifying = notifying;
Id = s_nextId++;
_metadata.Add(ownerType, metadata ?? throw new ArgumentNullException(nameof(metadata)));
_defaultMetadata = metadata;
}
///
/// Initializes a new instance of the class.
///
/// The direct property to copy.
/// The new owner type.
/// Optional overridden metadata.
protected AvaloniaProperty(
AvaloniaProperty source,
Type ownerType,
AvaloniaPropertyMetadata? metadata)
{
_metadata = new Dictionary();
Name = source?.Name ?? throw new ArgumentNullException(nameof(source));
PropertyType = source.PropertyType;
OwnerType = ownerType ?? throw new ArgumentNullException(nameof(ownerType));
Notifying = source.Notifying;
Id = source.Id;
_defaultMetadata = source._defaultMetadata;
// Properties that have different owner can't use fast path for metadata.
_hasMetadataOverrides = true;
if (metadata != null)
{
_metadata.Add(ownerType, metadata);
}
}
///
/// Gets the name of the property.
///
public string Name { get; }
///
/// Gets the type of the property's value.
///
public Type PropertyType { get; }
///
/// Gets the type of the class that registered the property.
///
public Type OwnerType { get; }
///
/// Gets a value indicating whether the property inherits its value.
///
public virtual bool Inherits => false;
///
/// Gets a value indicating whether this is an attached property.
///
public virtual bool IsAttached => false;
///
/// Gets a value indicating whether this is a direct property.
///
public virtual bool IsDirect => false;
///
/// Gets a value indicating whether this is a readonly property.
///
public virtual bool IsReadOnly => false;
///
/// Gets an observable that is fired when this property changes on any
/// instance.
///
///
/// An observable that is fired when this property changes on any
/// instance.
///
public IObservable Changed => GetChanged();
///
/// Gets a method that gets called before and after the property starts being notified on an
/// object.
///
///
/// When a property changes, change notifications are sent to all property subscribers;
/// for example via the observable and and the
/// event. If this callback is set for a property,
/// then it will be called before and after these notifications take place. The bool argument
/// will be true before the property change notifications are sent and false afterwards. This
/// callback is intended to support Control.IsDataContextChanging.
///
public Action? Notifying { get; }
///
/// Gets the integer ID that represents this property.
///
internal int Id { get; }
///
/// Provides access to a property's binding via the
/// indexer.
///
/// The property.
/// A describing the binding.
public static IndexerDescriptor operator !(AvaloniaProperty property)
{
return new IndexerDescriptor
{
Priority = BindingPriority.LocalValue,
Property = property,
};
}
///
/// Provides access to a property's template binding via the
/// indexer.
///
/// The property.
/// A describing the binding.
public static IndexerDescriptor operator ~(AvaloniaProperty property)
{
return new IndexerDescriptor
{
Priority = BindingPriority.TemplatedParent,
Property = property,
};
}
///
/// Tests two s for equality.
///
/// The first property.
/// The second property.
/// True if the properties are equal, otherwise false.
public static bool operator ==(AvaloniaProperty? a, AvaloniaProperty? b)
{
if (object.ReferenceEquals(a, b))
{
return true;
}
else if (a is null || b is null)
{
return false;
}
else
{
return a.Equals(b);
}
}
///
/// Tests two s for inequality.
///
/// The first property.
/// The second property.
/// True if the properties are equal, otherwise false.
public static bool operator !=(AvaloniaProperty? a, AvaloniaProperty? b)
{
return !(a == b);
}
///
/// Registers a .
///
/// The type of the class that is registering the property.
/// The type of the property's value.
/// The name of the property.
/// The default value of the property.
/// Whether the property inherits its value.
/// The default binding mode for the property.
/// A value validation callback.
/// A value coercion callback.
///
/// A method that gets called before and after the property starts being notified on an
/// object; the bool argument will be true before and false afterwards. This callback is
/// intended to support IsDataContextChanging.
///
/// A
public static StyledProperty Register(
string name,
TValue defaultValue = default!,
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay,
Func? validate = null,
Func? coerce = null,
Action? notifying = null)
where TOwner : IAvaloniaObject
{
_ = name ?? throw new ArgumentNullException(nameof(name));
var metadata = new StyledPropertyMetadata(
defaultValue,
defaultBindingMode: defaultBindingMode,
coerce: coerce);
var result = new StyledProperty(
name,
typeof(TOwner),
metadata,
inherits,
validate,
notifying);
AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
}
///
/// Registers an attached .
///
/// The type of the class that is registering the property.
/// The type of the class that the property is to be registered on.
/// The type of the property's value.
/// The name of the property.
/// The default value of the property.
/// Whether the property inherits its value.
/// The default binding mode for the property.
/// A value validation callback.
/// A value coercion callback.
/// A
public static AttachedProperty RegisterAttached(
string name,
TValue defaultValue = default!,
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay,
Func? validate = null,
Func? coerce = null)
where THost : IAvaloniaObject
{
_ = name ?? throw new ArgumentNullException(nameof(name));
var metadata = new StyledPropertyMetadata(
defaultValue,
defaultBindingMode: defaultBindingMode,
coerce: coerce);
var result = new AttachedProperty(name, typeof(TOwner), metadata, inherits, validate);
var registry = AvaloniaPropertyRegistry.Instance;
registry.Register(typeof(TOwner), result);
registry.RegisterAttached(typeof(THost), result);
return result;
}
///
/// Registers an attached .
///
/// The type of the class that the property is to be registered on.
/// The type of the property's value.
/// The name of the property.
/// The type of the class that is registering the property.
/// The default value of the property.
/// Whether the property inherits its value.
/// The default binding mode for the property.
/// A value validation callback.
/// A value coercion callback.
/// A
public static AttachedProperty RegisterAttached(
string name,
Type ownerType,
TValue defaultValue = default!,
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay,
Func? validate = null,
Func? coerce = null)
where THost : IAvaloniaObject
{
_ = name ?? throw new ArgumentNullException(nameof(name));
var metadata = new StyledPropertyMetadata(
defaultValue,
defaultBindingMode: defaultBindingMode,
coerce: coerce);
var result = new AttachedProperty(name, ownerType, metadata, inherits, validate);
var registry = AvaloniaPropertyRegistry.Instance;
registry.Register(ownerType, result);
registry.RegisterAttached(typeof(THost), result);
return result;
}
///
/// Registers a direct .
///
/// The type of the class that is registering the property.
/// The type of the property's value.
/// The name of the property.
/// Gets the current value of the property.
/// Sets the value of the property.
/// The value to use when the property is cleared.
/// The default binding mode for the property.
///
/// Whether the property is interested in data validation.
///
/// A
public static DirectProperty RegisterDirect(
string name,
Func getter,
Action? setter = null,
TValue unsetValue = default!,
BindingMode defaultBindingMode = BindingMode.OneWay,
bool enableDataValidation = false)
where TOwner : IAvaloniaObject
{
_ = name ?? throw new ArgumentNullException(nameof(name));
_ = getter ?? throw new ArgumentNullException(nameof(getter));
var metadata = new DirectPropertyMetadata(
unsetValue: unsetValue,
defaultBindingMode: defaultBindingMode,
enableDataValidation: enableDataValidation);
var result = new DirectProperty(
name,
getter,
setter,
metadata);
AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
}
///
/// Returns a binding accessor that can be passed to 's []
/// operator to initiate a binding.
///
/// A .
///
/// The ! and ~ operators are short forms of this.
///
public IndexerDescriptor Bind()
{
return new IndexerDescriptor
{
Property = this,
};
}
///
public override bool Equals(object? obj)
{
var p = obj as AvaloniaProperty;
return p is not null && Equals(p);
}
///
public bool Equals(AvaloniaProperty? other)
{
return Id == other?.Id;
}
///
public override int GetHashCode()
{
return Id;
}
///
/// Gets the property metadata for the specified type.
///
/// The type.
///
/// The property metadata.
///
public AvaloniaPropertyMetadata GetMetadata() where T : IAvaloniaObject
{
return GetMetadata(typeof(T));
}
///
/// Gets the property metadata for the specified type.
///
/// The type.
///
/// The property metadata.
///
public AvaloniaPropertyMetadata GetMetadata(Type type)
{
if (!_hasMetadataOverrides)
{
return _defaultMetadata;
}
return GetMetadataWithOverrides(type);
}
///
/// Checks whether the is valid for the property.
///
/// The value.
/// True if the value is valid, otherwise false.
public bool IsValidValue(object? value)
{
return TypeUtilities.TryConvertImplicit(PropertyType, value, out _);
}
///
/// Gets the string representation of the property.
///
/// The property's string representation.
public override string ToString()
{
return Name;
}
///
/// Uses the visitor pattern to resolve an untyped property to a typed property.
///
/// The type of user data passed.
/// The visitor which will accept the typed property.
/// The user data to pass.
public abstract void Accept(IAvaloniaPropertyVisitor visitor, ref TData data)
where TData : struct;
///
/// Routes an untyped ClearValue call to a typed call.
///
/// The object instance.
internal abstract void RouteClearValue(AvaloniaObject o);
///
/// Routes an untyped GetValue call to a typed call.
///
/// The object instance.
internal abstract object? RouteGetValue(AvaloniaObject o);
///
/// Routes an untyped GetBaseValue call to a typed call.
///
/// The object instance.
/// The maximum priority for the value.
internal abstract object? RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority);
///
/// Routes an untyped SetValue call to a typed call.
///
/// The object instance.
/// The value.
/// The priority.
///
/// An if setting the property can be undone, otherwise null.
///
internal abstract IDisposable? RouteSetValue(
AvaloniaObject o,
object? value,
BindingPriority priority);
///
/// Routes an untyped Bind call to a typed call.
///
/// The object instance.
/// The binding source.
/// The priority.
internal abstract IDisposable RouteBind(
AvaloniaObject o,
IObservable> source,
BindingPriority priority);
internal abstract void RouteInheritanceParentChanged(AvaloniaObject o, AvaloniaObject? oldParent);
///
/// Overrides the metadata for the property on the specified type.
///
/// The type.
/// The metadata.
protected void OverrideMetadata(Type type, AvaloniaPropertyMetadata metadata)
{
_ = type ?? throw new ArgumentNullException(nameof(type));
_ = metadata ?? throw new ArgumentNullException(nameof(metadata));
if (_metadata.ContainsKey(type))
{
throw new InvalidOperationException(
$"Metadata is already set for {Name} on {type}.");
}
var baseMetadata = GetMetadata(type);
metadata.Merge(baseMetadata, this);
_metadata.Add(type, metadata);
_metadataCache.Clear();
_hasMetadataOverrides = true;
}
protected abstract IObservable GetChanged();
private AvaloniaPropertyMetadata GetMetadataWithOverrides(Type type)
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (_metadataCache.TryGetValue(type, out var result))
{
return result;
}
Type? currentType = type;
while (currentType != null)
{
if (_metadata.TryGetValue(currentType, out result))
{
_metadataCache[type] = result;
return result;
}
currentType = currentType.BaseType;
}
_metadataCache[type] = _defaultMetadata;
return _defaultMetadata;
}
bool IPropertyInfo.CanGet => true;
bool IPropertyInfo.CanSet => true;
object? IPropertyInfo.Get(object target) => ((AvaloniaObject)target).GetValue(this);
void IPropertyInfo.Set(object target, object? value) => ((AvaloniaObject)target).SetValue(this, value);
}
///
/// Class representing the .
///
public sealed class UnsetValueType
{
internal UnsetValueType() { }
///
/// Returns the string representation of the .
///
/// The string "(unset)".
public override string ToString() => "(unset)";
}
}