|
|
|
@ -4,7 +4,6 @@ |
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Linq; |
|
|
|
using System.Reflection; |
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
|
|
|
|
namespace Avalonia |
|
|
|
@ -14,23 +13,14 @@ namespace Avalonia |
|
|
|
/// </summary>
|
|
|
|
public class AvaloniaPropertyRegistry |
|
|
|
{ |
|
|
|
/// <summary>
|
|
|
|
/// The registered properties by type.
|
|
|
|
/// </summary>
|
|
|
|
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registered = |
|
|
|
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>(); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The registered properties by type cached values to increase performance.
|
|
|
|
/// </summary>
|
|
|
|
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registeredCache = |
|
|
|
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>(); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The registered attached properties by owner type.
|
|
|
|
/// </summary>
|
|
|
|
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _attached = |
|
|
|
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>(); |
|
|
|
private readonly Dictionary<Type, List<AvaloniaProperty>> _registeredCache = |
|
|
|
new Dictionary<Type, List<AvaloniaProperty>>(); |
|
|
|
private readonly Dictionary<Type, List<AvaloniaProperty>> _attachedCache = |
|
|
|
new Dictionary<Type, List<AvaloniaProperty>>(); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the <see cref="AvaloniaPropertyRegistry"/> instance
|
|
|
|
@ -39,51 +29,68 @@ namespace Avalonia |
|
|
|
= new AvaloniaPropertyRegistry(); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets all attached <see cref="AvaloniaProperty"/>s registered by an owner.
|
|
|
|
/// Gets all non-attached <see cref="AvaloniaProperty"/>s registered on a type.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="ownerType">The owner type.</param>
|
|
|
|
/// <param name="type">The type.</param>
|
|
|
|
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
|
|
|
|
public IEnumerable<AvaloniaProperty> GetAttached(Type ownerType) |
|
|
|
public IEnumerable<AvaloniaProperty> GetRegistered(Type type) |
|
|
|
{ |
|
|
|
Dictionary<int, AvaloniaProperty> inner; |
|
|
|
Contract.Requires<ArgumentNullException>(type != null); |
|
|
|
|
|
|
|
if (_registeredCache.TryGetValue(type, out var result)) |
|
|
|
{ |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
// Ensure the type's static ctor has been run.
|
|
|
|
RuntimeHelpers.RunClassConstructor(ownerType.TypeHandle); |
|
|
|
var t = type; |
|
|
|
result = new List<AvaloniaProperty>(); |
|
|
|
|
|
|
|
if (_attached.TryGetValue(ownerType, out inner)) |
|
|
|
while (t != null) |
|
|
|
{ |
|
|
|
return inner.Values; |
|
|
|
// Ensure the type's static ctor has been run.
|
|
|
|
RuntimeHelpers.RunClassConstructor(t.TypeHandle); |
|
|
|
|
|
|
|
if (_registered.TryGetValue(t, out var registered)) |
|
|
|
{ |
|
|
|
result.AddRange(registered.Values); |
|
|
|
} |
|
|
|
|
|
|
|
t = t.BaseType; |
|
|
|
} |
|
|
|
|
|
|
|
return Enumerable.Empty<AvaloniaProperty>(); |
|
|
|
_registeredCache.Add(type, result); |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets all <see cref="AvaloniaProperty"/>s registered on a type.
|
|
|
|
/// Gets all attached <see cref="AvaloniaProperty"/>s registered on a type.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="type">The type.</param>
|
|
|
|
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
|
|
|
|
public IEnumerable<AvaloniaProperty> GetRegistered(Type type) |
|
|
|
public IEnumerable<AvaloniaProperty> GetRegisteredAttached(Type type) |
|
|
|
{ |
|
|
|
Contract.Requires<ArgumentNullException>(type != null); |
|
|
|
|
|
|
|
while (type != null) |
|
|
|
if (_attachedCache.TryGetValue(type, out var result)) |
|
|
|
{ |
|
|
|
// Ensure the type's static ctor has been run.
|
|
|
|
RuntimeHelpers.RunClassConstructor(type.TypeHandle); |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
Dictionary<int, AvaloniaProperty> inner; |
|
|
|
var t = type; |
|
|
|
result = new List<AvaloniaProperty>(); |
|
|
|
|
|
|
|
if (_registered.TryGetValue(type, out inner)) |
|
|
|
while (t != null) |
|
|
|
{ |
|
|
|
if (_attached.TryGetValue(t, out var attached)) |
|
|
|
{ |
|
|
|
foreach (var p in inner) |
|
|
|
{ |
|
|
|
yield return p.Value; |
|
|
|
} |
|
|
|
result.AddRange(attached.Values); |
|
|
|
} |
|
|
|
|
|
|
|
type = type.GetTypeInfo().BaseType; |
|
|
|
t = t.BaseType; |
|
|
|
} |
|
|
|
|
|
|
|
_attachedCache.Add(type, result); |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -99,142 +106,92 @@ namespace Avalonia |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Finds a <see cref="AvaloniaProperty"/> registered on a type.
|
|
|
|
/// Finds a registered non-attached property on a type by name.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="type">The type.</param>
|
|
|
|
/// <param name="property">The property.</param>
|
|
|
|
/// <returns>The registered property or null if not found.</returns>
|
|
|
|
/// <remarks>
|
|
|
|
/// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a
|
|
|
|
/// different object but is equal according to <see cref="object.Equals(object)"/>.
|
|
|
|
/// </remarks>
|
|
|
|
public AvaloniaProperty FindRegistered(Type type, AvaloniaProperty property) |
|
|
|
/// <param name="name">The property name.</param>
|
|
|
|
/// <returns>
|
|
|
|
/// The registered property or null if no matching property found.
|
|
|
|
/// </returns>
|
|
|
|
/// <exception cref="InvalidOperationException">
|
|
|
|
/// The property name contains a '.'.
|
|
|
|
/// </exception>
|
|
|
|
public AvaloniaProperty FindRegistered(Type type, string name) |
|
|
|
{ |
|
|
|
Type currentType = type; |
|
|
|
Dictionary<int, AvaloniaProperty> cache; |
|
|
|
AvaloniaProperty result; |
|
|
|
Contract.Requires<ArgumentNullException>(type != null); |
|
|
|
Contract.Requires<ArgumentNullException>(name != null); |
|
|
|
|
|
|
|
if (_registeredCache.TryGetValue(type, out cache)) |
|
|
|
if (name.Contains('.')) |
|
|
|
{ |
|
|
|
if (cache.TryGetValue(property.Id, out result)) |
|
|
|
{ |
|
|
|
return result; |
|
|
|
} |
|
|
|
throw new InvalidOperationException("Attached properties not supported."); |
|
|
|
} |
|
|
|
|
|
|
|
while (currentType != null) |
|
|
|
{ |
|
|
|
Dictionary<int, AvaloniaProperty> inner; |
|
|
|
|
|
|
|
if (_registered.TryGetValue(currentType, out inner)) |
|
|
|
{ |
|
|
|
if (inner.TryGetValue(property.Id, out result)) |
|
|
|
{ |
|
|
|
if (cache == null) |
|
|
|
{ |
|
|
|
_registeredCache[type] = cache = new Dictionary<int, AvaloniaProperty>(); |
|
|
|
} |
|
|
|
|
|
|
|
cache[property.Id] = result; |
|
|
|
|
|
|
|
return result; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
currentType = currentType.GetTypeInfo().BaseType; |
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
return GetRegistered(type).FirstOrDefault(x => x.Name == name); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Finds <see cref="AvaloniaProperty"/> registered on an object.
|
|
|
|
/// Finds a registered non-attached property on a type by name.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="o">The object.</param>
|
|
|
|
/// <param name="property">The property.</param>
|
|
|
|
/// <returns>The registered property or null if not found.</returns>
|
|
|
|
/// <remarks>
|
|
|
|
/// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a
|
|
|
|
/// different object but is equal according to <see cref="object.Equals(object)"/>.
|
|
|
|
/// </remarks>
|
|
|
|
public AvaloniaProperty FindRegistered(object o, AvaloniaProperty property) |
|
|
|
/// <param name="name">The property name.</param>
|
|
|
|
/// <returns>
|
|
|
|
/// The registered property or null if no matching property found.
|
|
|
|
/// </returns>
|
|
|
|
/// <exception cref="InvalidOperationException">
|
|
|
|
/// The property name contains a '.'.
|
|
|
|
/// </exception>
|
|
|
|
public AvaloniaProperty FindRegistered(AvaloniaObject o, string name) |
|
|
|
{ |
|
|
|
return FindRegistered(o.GetType(), property); |
|
|
|
Contract.Requires<ArgumentNullException>(o != null); |
|
|
|
Contract.Requires<ArgumentNullException>(name != null); |
|
|
|
|
|
|
|
return FindRegistered(o.GetType(), name); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Finds a registered property on a type by name.
|
|
|
|
/// Finds a registered attached property on a type by name.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="type">The type.</param>
|
|
|
|
/// <param name="name">
|
|
|
|
/// The property name. If an attached property it should be in the form
|
|
|
|
/// "OwnerType.PropertyName".
|
|
|
|
/// </param>
|
|
|
|
/// <param name="ownerType">The owner type.</param>
|
|
|
|
/// <param name="name">The property name.</param>
|
|
|
|
/// <returns>
|
|
|
|
/// The registered property or null if no matching property found.
|
|
|
|
/// </returns>
|
|
|
|
public AvaloniaProperty FindRegistered(Type type, string name) |
|
|
|
/// <exception cref="InvalidOperationException">
|
|
|
|
/// The property name contains a '.'.
|
|
|
|
/// </exception>
|
|
|
|
public AvaloniaProperty FindRegisteredAttached(Type type, Type ownerType, string name) |
|
|
|
{ |
|
|
|
Contract.Requires<ArgumentNullException>(type != null); |
|
|
|
Contract.Requires<ArgumentNullException>(ownerType != null); |
|
|
|
Contract.Requires<ArgumentNullException>(name != null); |
|
|
|
|
|
|
|
var parts = name.Split('.'); |
|
|
|
var types = GetImplementedTypes(type).ToList(); |
|
|
|
|
|
|
|
if (parts.Length < 1 || parts.Length > 2) |
|
|
|
if (name.Contains('.')) |
|
|
|
{ |
|
|
|
throw new ArgumentException("Invalid property name."); |
|
|
|
throw new InvalidOperationException("Attached properties not supported."); |
|
|
|
} |
|
|
|
|
|
|
|
string propertyName; |
|
|
|
var results = GetRegistered(type); |
|
|
|
|
|
|
|
if (parts.Length == 1) |
|
|
|
{ |
|
|
|
propertyName = parts[0]; |
|
|
|
results = results.Where(x => !x.IsAttached || types.Contains(x.OwnerType.Name)); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
if (!types.Contains(parts[0])) |
|
|
|
{ |
|
|
|
results = results.Where(x => x.OwnerType.Name == parts[0]); |
|
|
|
} |
|
|
|
|
|
|
|
propertyName = parts[1]; |
|
|
|
} |
|
|
|
|
|
|
|
return results.FirstOrDefault(x => x.Name == propertyName); |
|
|
|
return GetRegisteredAttached(type).FirstOrDefault(x => x.Name == name); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Finds a registered property on an object by name.
|
|
|
|
/// Finds a registered non-attached property on a type by name.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="o">The object.</param>
|
|
|
|
/// <param name="name">
|
|
|
|
/// The property name. If an attached property it should be in the form
|
|
|
|
/// "OwnerType.PropertyName".
|
|
|
|
/// </param>
|
|
|
|
/// <param name="ownerType">The owner type.</param>
|
|
|
|
/// <param name="name">The property name.</param>
|
|
|
|
/// <returns>
|
|
|
|
/// The registered property or null if no matching property found.
|
|
|
|
/// </returns>
|
|
|
|
public AvaloniaProperty FindRegistered(AvaloniaObject o, string name) |
|
|
|
/// <exception cref="InvalidOperationException">
|
|
|
|
/// The property name contains a '.'.
|
|
|
|
/// </exception>
|
|
|
|
public AvaloniaProperty FindRegisteredAttached(AvaloniaObject o, Type ownerType, string name) |
|
|
|
{ |
|
|
|
return FindRegistered(o.GetType(), name); |
|
|
|
} |
|
|
|
Contract.Requires<ArgumentNullException>(o != null); |
|
|
|
Contract.Requires<ArgumentNullException>(name != null); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Returns a type and all its base types.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="type">The type.</param>
|
|
|
|
/// <returns>The type and all its base types.</returns>
|
|
|
|
private IEnumerable<string> GetImplementedTypes(Type type) |
|
|
|
{ |
|
|
|
while (type != null) |
|
|
|
{ |
|
|
|
yield return type.Name; |
|
|
|
type = type.GetTypeInfo().BaseType; |
|
|
|
} |
|
|
|
return FindRegisteredAttached(o.GetType(), ownerType, name); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -245,7 +202,11 @@ namespace Avalonia |
|
|
|
/// <returns>True if the property is registered, otherwise false.</returns>
|
|
|
|
public bool IsRegistered(Type type, AvaloniaProperty property) |
|
|
|
{ |
|
|
|
return FindRegistered(type, property) != null; |
|
|
|
Contract.Requires<ArgumentNullException>(type != null); |
|
|
|
Contract.Requires<ArgumentNullException>(property != null); |
|
|
|
|
|
|
|
return Instance.GetRegistered(type).Any(x => x == property) || |
|
|
|
Instance.GetRegisteredAttached(type).Any(x => x == property); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -256,6 +217,9 @@ namespace Avalonia |
|
|
|
/// <returns>True if the property is registered, otherwise false.</returns>
|
|
|
|
public bool IsRegistered(object o, AvaloniaProperty property) |
|
|
|
{ |
|
|
|
Contract.Requires<ArgumentNullException>(o != null); |
|
|
|
Contract.Requires<ArgumentNullException>(property != null); |
|
|
|
|
|
|
|
return IsRegistered(o.GetType(), property); |
|
|
|
} |
|
|
|
|
|
|
|
@ -274,34 +238,53 @@ namespace Avalonia |
|
|
|
Contract.Requires<ArgumentNullException>(type != null); |
|
|
|
Contract.Requires<ArgumentNullException>(property != null); |
|
|
|
|
|
|
|
Dictionary<int, AvaloniaProperty> inner; |
|
|
|
|
|
|
|
if (!_registered.TryGetValue(type, out inner)) |
|
|
|
if (!_registered.TryGetValue(type, out var inner)) |
|
|
|
{ |
|
|
|
inner = new Dictionary<int, AvaloniaProperty>(); |
|
|
|
inner.Add(property.Id, property); |
|
|
|
_registered.Add(type, inner); |
|
|
|
} |
|
|
|
|
|
|
|
if (!inner.ContainsKey(property.Id)) |
|
|
|
else if (!inner.ContainsKey(property.Id)) |
|
|
|
{ |
|
|
|
inner.Add(property.Id, property); |
|
|
|
} |
|
|
|
|
|
|
|
_registeredCache.Clear(); |
|
|
|
} |
|
|
|
|
|
|
|
if (property.IsAttached) |
|
|
|
/// <summary>
|
|
|
|
/// Registers an attached <see cref="AvaloniaProperty"/> on a type.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="type">The type.</param>
|
|
|
|
/// <param name="property">The property.</param>
|
|
|
|
/// <remarks>
|
|
|
|
/// You won't usually want to call this method directly, instead use the
|
|
|
|
/// <see cref="AvaloniaProperty.RegisterAttached{THost, TValue}(string, Type, TValue, bool, Data.BindingMode, Func{THost, TValue, TValue})"/>
|
|
|
|
/// method.
|
|
|
|
/// </remarks>
|
|
|
|
public void RegisterAttached(Type type, AvaloniaProperty property) |
|
|
|
{ |
|
|
|
Contract.Requires<ArgumentNullException>(type != null); |
|
|
|
Contract.Requires<ArgumentNullException>(property != null); |
|
|
|
|
|
|
|
if (!property.IsAttached) |
|
|
|
{ |
|
|
|
if (!_attached.TryGetValue(property.OwnerType, out inner)) |
|
|
|
{ |
|
|
|
inner = new Dictionary<int, AvaloniaProperty>(); |
|
|
|
_attached.Add(property.OwnerType, inner); |
|
|
|
} |
|
|
|
throw new InvalidOperationException( |
|
|
|
"Cannot register a non-attached property as attached."); |
|
|
|
} |
|
|
|
|
|
|
|
if (!inner.ContainsKey(property.Id)) |
|
|
|
{ |
|
|
|
inner.Add(property.Id, property); |
|
|
|
} |
|
|
|
if (!_attached.TryGetValue(type, out var inner)) |
|
|
|
{ |
|
|
|
inner = new Dictionary<int, AvaloniaProperty>(); |
|
|
|
inner.Add(property.Id, property); |
|
|
|
_attached.Add(type, inner); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
inner.Add(property.Id, property); |
|
|
|
} |
|
|
|
|
|
|
|
_registeredCache.Clear(); |
|
|
|
_attachedCache.Clear(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |