|
|
@@ -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();
|
|
|
}
|
|
|
}
|
|
|
}
|