A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

281 lines
9.8 KiB

// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Perspex
{
/// <summary>
/// Tracks registered <see cref="PerspexProperty"/> instances.
/// </summary>
public class PerspexPropertyRegistry
{
/// <summary>
/// The registered properties by type.
/// </summary>
private readonly Dictionary<Type, Dictionary<int, PerspexProperty>> _registered =
new Dictionary<Type, Dictionary<int, PerspexProperty>>();
/// <summary>
/// The registered attached properties by owner type.
/// </summary>
private readonly Dictionary<Type, Dictionary<int, PerspexProperty>> _attached =
new Dictionary<Type, Dictionary<int, PerspexProperty>>();
/// <summary>
/// Gets the <see cref="PerspexPropertyRegistry"/> instance
/// </summary>
public static PerspexPropertyRegistry Instance { get; }
= new PerspexPropertyRegistry();
/// <summary>
/// Gets all attached <see cref="PerspexProperty"/>s registered by an owner.
/// </summary>
/// <param name="ownerType">The owner type.</param>
/// <returns>A collection of <see cref="PerspexProperty"/> definitions.</returns>
public IEnumerable<PerspexProperty> GetAttached(Type ownerType)
{
Dictionary<int, PerspexProperty> inner;
if (_attached.TryGetValue(ownerType, out inner))
{
return inner.Values;
}
return Enumerable.Empty<PerspexProperty>();
}
/// <summary>
/// Gets all <see cref="PerspexProperty"/>s registered on a type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="PerspexProperty"/> definitions.</returns>
public IEnumerable<PerspexProperty> GetRegistered(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
var i = type.GetTypeInfo();
while (type != null)
{
// Ensure the type's static ctor has been run.
RuntimeHelpers.RunClassConstructor(type.TypeHandle);
Dictionary<int, PerspexProperty> inner;
if (_registered.TryGetValue(type, out inner))
{
foreach (var p in inner)
{
yield return p.Value;
}
}
type = type.GetTypeInfo().BaseType;
}
}
/// <summary>
/// Gets all <see cref="PerspexProperty"/>s registered on a object.
/// </summary>
/// <param name="o">The object.</param>
/// <returns>A collection of <see cref="PerspexProperty"/> definitions.</returns>
public IEnumerable<PerspexProperty> GetRegistered(PerspexObject o)
{
Contract.Requires<ArgumentNullException>(o != null);
return GetRegistered(o.GetType());
}
/// <summary>
/// Finds a <see cref="PerspexProperty"/> registered on a type.
/// </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 PerspexProperty creates a new PerspexProperty that is a
/// different object but is equal according to <see cref="object.Equals(object)"/>.
/// </remarks>
public PerspexProperty FindRegistered(Type type, PerspexProperty property)
{
while (type != null)
{
Dictionary<int, PerspexProperty> inner;
if (_registered.TryGetValue(type, out inner))
{
PerspexProperty result;
if (inner.TryGetValue(property.Id, out result))
{
return result;
}
}
type = type.GetTypeInfo().BaseType;
}
return null;
}
/// <summary>
/// Finds <see cref="PerspexProperty"/> registered on an object.
/// </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 PerspexProperty creates a new PerspexProperty that is a
/// different object but is equal according to <see cref="object.Equals(object)"/>.
/// </remarks>
public PerspexProperty FindRegistered(object o, PerspexProperty property)
{
return FindRegistered(o.GetType(), property);
}
/// <summary>
/// Finds a registered 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>
/// <returns>
/// The registered property or null if no matching property found.
/// </returns>
public PerspexProperty FindRegistered(Type type, string name)
{
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(name != null);
var parts = name.Split('.');
var types = GetImplementedTypes(type).ToList();
if (parts.Length < 1 || parts.Length > 2)
{
throw new ArgumentException("Invalid property name.");
}
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);
}
/// <summary>
/// Finds a registered property on an object 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>
/// <returns>
/// The registered property or null if no matching property found.
/// </returns>
public PerspexProperty FindRegistered(PerspexObject o, string name)
{
return FindRegistered(o.GetType(), name);
}
/// <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;
}
}
/// <summary>
/// Checks whether a <see cref="PerspexProperty"/> is registered on a type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="property">The property.</param>
/// <returns>True if the property is registered, otherwise false.</returns>
public bool IsRegistered(Type type, PerspexProperty property)
{
return FindRegistered(type, property) != null;
}
/// <summary>
/// Checks whether a <see cref="PerspexProperty"/> is registered on a object.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="property">The property.</param>
/// <returns>True if the property is registered, otherwise false.</returns>
public bool IsRegistered(object o, PerspexProperty property)
{
return IsRegistered(o.GetType(), property);
}
/// <summary>
/// Registers a <see cref="PerspexProperty"/> 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="PerspexProperty.Register"/> method.
/// </remarks>
public void Register(Type type, PerspexProperty property)
{
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(property != null);
Dictionary<int, PerspexProperty> inner;
if (!_registered.TryGetValue(type, out inner))
{
inner = new Dictionary<int, PerspexProperty>();
_registered.Add(type, inner);
}
if (!inner.ContainsKey(property.Id))
{
inner.Add(property.Id, property);
}
if (property.IsAttached)
{
if (!_attached.TryGetValue(property.OwnerType, out inner))
{
inner = new Dictionary<int, PerspexProperty>();
_attached.Add(property.OwnerType, inner);
}
if (!inner.ContainsKey(property.Id))
{
inner.Add(property.Id, property);
}
}
}
}
}