@ -4,7 +4,6 @@
using System ;
using System ;
using System.Collections.Generic ;
using System.Collections.Generic ;
using System.Linq ;
using System.Linq ;
using System.Reflection ;
using System.Runtime.CompilerServices ;
using System.Runtime.CompilerServices ;
namespace Avalonia
namespace Avalonia
@ -14,23 +13,14 @@ namespace Avalonia
/// </summary>
/// </summary>
public class AvaloniaPropertyRegistry
public class AvaloniaPropertyRegistry
{
{
/// <summary>
/// The registered properties by type.
/// </summary>
private readonly Dictionary < Type , Dictionary < int , AvaloniaProperty > > _ registered =
private readonly Dictionary < Type , Dictionary < int , AvaloniaProperty > > _ registered =
new Dictionary < Type , Dictionary < int , AvaloniaProperty > > ( ) ;
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 =
private readonly Dictionary < Type , Dictionary < int , AvaloniaProperty > > _ attached =
new Dictionary < Type , Dictionary < int , AvaloniaProperty > > ( ) ;
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>
/// <summary>
/// Gets the <see cref="AvaloniaPropertyRegistry"/> instance
/// Gets the <see cref="AvaloniaPropertyRegistry"/> instance
@ -39,51 +29,68 @@ namespace Avalonia
= new AvaloniaPropertyRegistry ( ) ;
= new AvaloniaPropertyRegistry ( ) ;
/// <summary>
/// <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>
/// </summary>
/// <param name="ownerType">The owner type.</param>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
public IEnumerable < AvaloniaProperty > GetAttached ( Type ownerT ype )
public IEnumerable < AvaloniaProperty > GetRegistered ( Type t ype )
{
{
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.
var t = type ;
RuntimeHelpers . RunClassConstructor ( ownerType . TypeHandle ) ;
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>
/// <summary>
/// Gets all <see cref="AvaloniaProperty"/>s registered on a type.
/// Gets all attached <see cref="AvaloniaProperty"/>s registered on a type.
/// </summary>
/// </summary>
/// <param name="type">The type.</param>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
/// <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 ) ;
Contract . Requires < ArgumentNullException > ( type ! = null ) ;
wh il e ( type ! = null )
if ( _ attachedCach e . TryGetValue ( type , out var result ) )
{
{
// Ensure the type's static ctor has been run.
return result ;
RuntimeHelpers . RunClassConstructor ( type . TypeHandle ) ;
}
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 )
result . AddRange ( attached . Values ) ;
{
yield return p . Value ;
}
}
}
type = type . GetTypeInfo ( ) . BaseType ;
t = t . BaseType ;
}
}
_ attachedCache . Add ( type , result ) ;
return result ;
}
}
/// <summary>
/// <summary>
@ -99,142 +106,92 @@ namespace Avalonia
}
}
/// <summary>
/// <summary>
/// Finds a <see cref="AvaloniaProperty"/> registered on a typ e.
/// Finds a registered non-attached property on a type by nam e.
/// </summary>
/// </summary>
/// <param name="type">The type.</param>
/// <param name="type">The type.</param>
/// <param name="property">The property.</param>
/// <param name="name">The property name.</param>
/// <returns>The registered property or null if not found.</returns>
/// <returns>
/// <remarks>
/// The registered property or null if no matching property found.
/// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a
/// </returns>
/// different object but is equal according to <see cref="object.Equals(object)"/>.
/// <exception cref="InvalidOperationException">
/// </remarks>
/// The property name contains a '.'.
public AvaloniaProperty FindRegistered ( Type type , AvaloniaProperty property )
/// </exception>
public AvaloniaProperty FindRegistered ( Type type , string name )
{
{
Type currentType = type ;
Contract . Requires < ArgumentNullException > ( type ! = null ) ;
Dictionary < int , AvaloniaProperty > cache ;
Contract . Requires < ArgumentNullException > ( name ! = null ) ;
AvaloniaProperty result ;
if ( _ registeredCache . TryGetValue ( type , out cache ) )
if ( name . Contains ( '.' ) )
{
{
if ( cache . TryGetValue ( property . Id , out result ) )
throw new InvalidOperationException ( "Attached properties not supported." ) ;
{
return result ;
}
}
}
while ( currentType ! = null )
return GetRegistered ( type ) . FirstOrDefault ( x = > x . Name = = name ) ;
{
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 ;
}
}
/// <summary>
/// <summary>
/// Finds <see cref="AvaloniaProperty"/> registered on an object .
/// Finds a registered non-attached property on a type by name.
/// </summary>
/// </summary>
/// <param name="o">The object.</param>
/// <param name="o">The object.</param>
/// <param name="property">The property.</param>
/// <param name="name">The property name.</param>
/// <returns>The registered property or null if not found.</returns>
/// <returns>
/// <remarks>
/// The registered property or null if no matching property found.
/// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a
/// </returns>
/// different object but is equal according to <see cref="object.Equals(object)"/>.
/// <exception cref="InvalidOperationException">
/// </remarks>
/// The property name contains a '.'.
public AvaloniaProperty FindRegistered ( object o , AvaloniaProperty property )
/// </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>
/// <summary>
/// Finds a registered property on a type by name.
/// Finds a registered attached property on a type by name.
/// </summary>
/// </summary>
/// <param name="type">The type.</param>
/// <param name="type">The type.</param>
/// <param name="name">
/// <param name="ownerType">The owner type.</param>
/// The property name. If an attached property it should be in the form
/// <param name="name">The property name.</param>
/// "OwnerType.PropertyName".
/// </param>
/// <returns>
/// <returns>
/// The registered property or null if no matching property found.
/// The registered property or null if no matching property found.
/// </returns>
/// </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 > ( type ! = null ) ;
Contract . Requires < ArgumentNullException > ( ownerType ! = null ) ;
Contract . Requires < ArgumentNullException > ( name ! = null ) ;
Contract . Requires < ArgumentNullException > ( name ! = null ) ;
var parts = name . Split ( '.' ) ;
if ( name . Contains ( '.' ) )
var types = GetImplementedTypes ( type ) . ToList ( ) ;
if ( parts . Length < 1 | | parts . Length > 2 )
{
{
throw new ArgumentException ( "Invalid property name .") ;
throw new InvalidOperationException ( "Attached properties not supported." ) ;
}
}
string propertyName ;
return GetRegisteredAttached ( type ) . FirstOrDefault ( x = > x . Name = = name ) ;
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 ) ;
}
}
/// <summary>
/// <summary>
/// Finds a registered property on an object by name.
/// Finds a registered non-attached property on a type by name.
/// </summary>
/// </summary>
/// <param name="o">The object.</param>
/// <param name="o">The object.</param>
/// <param name="name">
/// <param name="ownerType">The owner type.</param>
/// The property name. If an attached property it should be in the form
/// <param name="name">The property name.</param>
/// "OwnerType.PropertyName".
/// </param>
/// <returns>
/// <returns>
/// The registered property or null if no matching property found.
/// The registered property or null if no matching property found.
/// </returns>
/// </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>
return FindRegisteredAttached ( o . GetType ( ) , ownerType , name ) ;
/// 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 ;
}
}
}
/// <summary>
/// <summary>
@ -245,7 +202,11 @@ namespace Avalonia
/// <returns>True if the property is registered, otherwise false.</returns>
/// <returns>True if the property is registered, otherwise false.</returns>
public bool IsRegistered ( Type type , AvaloniaProperty property )
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>
/// <summary>
@ -256,6 +217,9 @@ namespace Avalonia
/// <returns>True if the property is registered, otherwise false.</returns>
/// <returns>True if the property is registered, otherwise false.</returns>
public bool IsRegistered ( object o , AvaloniaProperty property )
public bool IsRegistered ( object o , AvaloniaProperty property )
{
{
Contract . Requires < ArgumentNullException > ( o ! = null ) ;
Contract . Requires < ArgumentNullException > ( property ! = null ) ;
return IsRegistered ( o . GetType ( ) , property ) ;
return IsRegistered ( o . GetType ( ) , property ) ;
}
}
@ -274,34 +238,53 @@ namespace Avalonia
Contract . Requires < ArgumentNullException > ( type ! = null ) ;
Contract . Requires < ArgumentNullException > ( type ! = null ) ;
Contract . Requires < ArgumentNullException > ( property ! = null ) ;
Contract . Requires < ArgumentNullException > ( property ! = null ) ;
Dictionary < int , AvaloniaProperty > inner ;
if ( ! _ registered . TryGetValue ( type , out var inner ) )
if ( ! _ registered . TryGetValue ( type , out inner ) )
{
{
inner = new Dictionary < int , AvaloniaProperty > ( ) ;
inner = new Dictionary < int , AvaloniaProperty > ( ) ;
inner . Add ( property . Id , property ) ;
_ registered . Add ( type , inner ) ;
_ registered . Add ( type , inner ) ;
}
}
else if ( ! inner . ContainsKey ( property . Id ) )
if ( ! inner . ContainsKey ( property . Id ) )
{
{
inner . Add ( property . Id , property ) ;
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 ) )
throw new InvalidOperationException (
{
"Cannot register a non-attached property as attached." ) ;
inner = new Dictionary < int , AvaloniaProperty > ( ) ;
}
_ attached . Add ( property . OwnerType , inner ) ;
}
if ( ! inner . ContainsKey ( property . Id ) )
if ( ! _ attached . TryGetValue ( type , out var inner ) )
{
{
inner . Add ( property . Id , property ) ;
inner = new Dictionary < int , AvaloniaProperty > ( ) ;
}
inner . Add ( property . Id , property ) ;
_ attached . Add ( type , inner ) ;
}
else
{
inner . Add ( property . Id , property ) ;
}
}
_ registeredCache . Clear ( ) ;
_ attach edCache. Clear ( ) ;
}
}
}
}
}
}