From 952e4a99f70d354fd45d9bf94b7e9ca82314ed7f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 19 Oct 2015 09:31:21 +0200 Subject: [PATCH] Created PerspexPropertyRegistry. And move registration of properties, finding properties etc here. --- .../Context/PerspexXamlMemberValuePlugin.cs | 4 +- .../PerspexPropertyTypeConverter.cs | 4 +- src/Perspex.Base/Perspex.Base.csproj | 1 + src/Perspex.Base/PerspexObject.cs | 172 +----------- src/Perspex.Base/PerspexProperty.cs | 9 +- src/Perspex.Base/PerspexPropertyRegistry.cs | 244 ++++++++++++++++++ src/Perspex.Base/PerspexProperty`1.cs | 10 +- src/Perspex.Diagnostics/Debug.cs | 2 +- .../ViewModels/ControlDetailsViewModel.cs | 2 +- .../Perspex.Base.UnitTests.csproj | 1 + .../PerspexObjectTests_Metadata.cs | 24 -- .../PerspexPropertyRegistryTests.cs | 79 ++++++ 12 files changed, 355 insertions(+), 197 deletions(-) create mode 100644 src/Perspex.Base/PerspexPropertyRegistry.cs create mode 100644 tests/Perspex.Base.UnitTests/PerspexPropertyRegistryTests.cs diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs index 36e1d03ea8..8dc4999a12 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs @@ -88,7 +88,7 @@ namespace Perspex.Markup.Xaml.Context if (attached == null) { propertyName = _xamlMember.Name; - property = perspexObject.GetRegisteredProperties() + property = PerspexPropertyRegistry.Instance.GetRegistered(perspexObject) .FirstOrDefault(x => x.Name == propertyName); } else @@ -98,7 +98,7 @@ namespace Perspex.Markup.Xaml.Context propertyName = attached.DeclaringType.UnderlyingType.Name + '.' + _xamlMember.Name; - property = perspexObject.GetRegisteredProperties() + property = PerspexPropertyRegistry.Instance.GetRegistered(perspexObject) .Where(x => x.IsAttached && x.OwnerType == attached.DeclaringType.UnderlyingType) .FirstOrDefault(x => x.Name == _xamlMember.Name); } diff --git a/src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyTypeConverter.cs b/src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyTypeConverter.cs index f492e01111..1cdc3cc4f1 100644 --- a/src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyTypeConverter.cs +++ b/src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyTypeConverter.cs @@ -46,12 +46,12 @@ namespace Perspex.Markup.Xaml.Converters } // First look for non-attached property on the type and then look for an attached property. - var property = PerspexObject.GetRegisteredProperties(type) + var property = PerspexPropertyRegistry.Instance.GetRegistered(type) .FirstOrDefault(x => x.Name == propertyName); if (property == null) { - property = PerspexObject.GetAttachedProperties(type) + property = PerspexPropertyRegistry.Instance.GetAttached(type) .FirstOrDefault(x => x.Name == propertyName); } diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index b7227d831b..5b3a086f59 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -50,6 +50,7 @@ + diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index 337fbf185d..a61e04a8ac 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -25,18 +25,6 @@ namespace Perspex /// public class PerspexObject : IObservablePropertyBag, INotifyPropertyChanged { - /// - /// The registered properties by type. - /// - private static readonly Dictionary> s_registered = - new Dictionary>(); - - /// - /// The registered attached properties by owner type. - /// - private static readonly Dictionary> s_attached = - new Dictionary>(); - /// /// The parent object that inherited values are inherited from. /// @@ -70,7 +58,7 @@ namespace Perspex new PropertyEnricher("Id", GetHashCode()), }); - foreach (var property in GetRegisteredProperties()) + foreach (var property in PerspexPropertyRegistry.Instance.GetRegistered(this)) { object value = property.IsDirect ? property.Getter(this) : @@ -129,7 +117,7 @@ namespace Perspex _inheritanceParent.PropertyChanged -= ParentPropertyChanged; } - var inherited = (from property in GetRegisteredProperties(GetType()) + var inherited = (from property in PerspexPropertyRegistry.Instance.GetRegistered(this) where property.Inherits select new { @@ -215,92 +203,6 @@ namespace Perspex } } - /// - /// Gets all s registered on a type. - /// - /// The type. - /// A collection of definitions. - public static IEnumerable GetRegisteredProperties(Type type) - { - Contract.Requires(type != null); - - TypeInfo i = type.GetTypeInfo(); - - while (type != null) - { - List list; - - if (s_registered.TryGetValue(type, out list)) - { - foreach (PerspexProperty p in list) - { - yield return p; - } - } - - type = type.GetTypeInfo().BaseType; - } - } - - /// - /// Gets all attached s registered by an owner. - /// - /// The owner type. - /// A collection of definitions. - public static IEnumerable GetAttachedProperties(Type ownerType) - { - List list; - - if (s_attached.TryGetValue(ownerType, out list)) - { - return list; - } - - return Enumerable.Empty(); - } - - /// - /// Registers a on a type. - /// - /// The type. - /// The property. - /// - /// You won't usually want to call this method directly, instead use the - /// method. - /// - public static void Register(Type type, PerspexProperty property) - { - Contract.Requires(type != null); - Contract.Requires(property != null); - - List list; - - if (!s_registered.TryGetValue(type, out list)) - { - list = new List(); - s_registered.Add(type, list); - } - - if (!list.Contains(property)) - { - list.Add(property); - } - - if (property.IsAttached) - { - if (!s_attached.TryGetValue(property.OwnerType, out list)) - { - list = new List(); - s_attached.Add(property.OwnerType, list); - } - - if (!list.Contains(property)) - { - list.Add(property); - } - } - } - public bool CheckAccess() => Dispatcher.UIThread.CheckAccess(); public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess(); @@ -409,7 +311,7 @@ namespace Perspex object result = PerspexProperty.UnsetValue; PriorityValue value; - if (!IsRegistered(property)) + if (!PerspexPropertyRegistry.Instance.IsRegistered(this, property)) { ThrowNotRegistered(property); } @@ -448,17 +350,6 @@ namespace Perspex } } - /// - /// Gets all properties that are registered on this object. - /// - /// - /// A collection of objects. - /// - public IEnumerable GetRegisteredProperties() - { - return GetRegisteredProperties(GetType()); - } - /// /// Checks whether a is set on this object. /// @@ -478,16 +369,6 @@ namespace Perspex return false; } - /// - /// Checks whether a is registered on this class. - /// - /// The property. - /// True if the property is registered, otherwise false. - public bool IsRegistered(PerspexProperty property) - { - return FindRegistered(property) != null; - } - /// /// Sets a value. /// @@ -519,7 +400,7 @@ namespace Perspex PriorityValue v; var originalValue = value; - if (!IsRegistered(property)) + if (!PerspexPropertyRegistry.Instance.IsRegistered(this, property)) { ThrowNotRegistered(property); } @@ -620,7 +501,7 @@ namespace Perspex { PriorityValue v; - if (!IsRegistered(property)) + if (!PerspexPropertyRegistry.Instance.IsRegistered(this, property)) { ThrowNotRegistered(property); } @@ -751,6 +632,12 @@ namespace Perspex } } + /// + bool IPropertyBag.IsRegistered(PerspexProperty property) + { + return PerspexPropertyRegistry.Instance.IsRegistered(this, property); + } + /// /// Gets all priority values set on the object. /// @@ -936,41 +823,6 @@ namespace Perspex } } - /// - /// Given a returns a registered perspex property that is - /// equal. - /// - /// The property. - /// The registered property or null if not found. - /// - /// Calling AddOwner on a direct PerspexProperty creates new new PerspexProperty with - /// an overridden getter and setter. This property is a different object but is equal - /// according to . - /// - public PerspexProperty FindRegistered(PerspexProperty property) - { - Type type = GetType(); - - while (type != null) - { - List list; - - if (s_registered.TryGetValue(type, out list)) - { - var index = list.IndexOf(property); - - if (index != -1) - { - return list[index]; - } - } - - type = type.GetTypeInfo().BaseType; - } - - return null; - } - /// /// Given a returns a registered perspex property that is /// equal or throws if not found. @@ -979,7 +831,7 @@ namespace Perspex /// The registered property. public PerspexProperty GetRegistered(PerspexProperty property) { - var result = FindRegistered(property); + var result = PerspexPropertyRegistry.Instance.FindRegistered(this, property); if (result == null) { diff --git a/src/Perspex.Base/PerspexProperty.cs b/src/Perspex.Base/PerspexProperty.cs index e44284b570..62a3d5cfe2 100644 --- a/src/Perspex.Base/PerspexProperty.cs +++ b/src/Perspex.Base/PerspexProperty.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reactive.Subjects; using System.Reflection; using Perspex.Utilities; @@ -367,7 +368,7 @@ namespace Perspex notifying, false); - PerspexObject.Register(typeof(TOwner), result); + PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result); return result; } @@ -395,7 +396,7 @@ namespace Perspex Cast(getter), Cast(setter)); - PerspexObject.Register(typeof(TOwner), result); + PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result); return result; } @@ -431,7 +432,7 @@ namespace Perspex null, true); - PerspexObject.Register(typeof(THost), result); + PerspexPropertyRegistry.Instance.Register(typeof(THost), result); return result; } @@ -468,7 +469,7 @@ namespace Perspex null, true); - PerspexObject.Register(typeof(THost), result); + PerspexPropertyRegistry.Instance.Register(typeof(THost), result); return result; } diff --git a/src/Perspex.Base/PerspexPropertyRegistry.cs b/src/Perspex.Base/PerspexPropertyRegistry.cs new file mode 100644 index 0000000000..d5df44a66f --- /dev/null +++ b/src/Perspex.Base/PerspexPropertyRegistry.cs @@ -0,0 +1,244 @@ +// 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; + +namespace Perspex +{ + /// + /// Tracks registered instances. + /// + public class PerspexPropertyRegistry + { + /// + /// The registered properties by type. + /// + private readonly Dictionary> _registered = + new Dictionary>(); + + /// + /// The registered attached properties by owner type. + /// + private readonly Dictionary> _attached = + new Dictionary>(); + + /// + /// Gets the instance + /// + public static PerspexPropertyRegistry Instance { get; } + = new PerspexPropertyRegistry(); + + /// + /// Gets all attached s registered by an owner. + /// + /// The owner type. + /// A collection of definitions. + public IEnumerable GetAttached(Type ownerType) + { + List list; + + if (_attached.TryGetValue(ownerType, out list)) + { + return list; + } + + return Enumerable.Empty(); + } + + /// + /// Gets all s registered on a type. + /// + /// The type. + /// A collection of definitions. + public IEnumerable GetRegistered(Type type) + { + Contract.Requires(type != null); + + var i = type.GetTypeInfo(); + + while (type != null) + { + List list; + + if (_registered.TryGetValue(type, out list)) + { + foreach (PerspexProperty p in list) + { + yield return p; + } + } + + type = type.GetTypeInfo().BaseType; + } + } + + /// + /// Gets all s registered on a object. + /// + /// The object. + /// A collection of definitions. + public IEnumerable GetRegistered(PerspexObject o) + { + Contract.Requires(o != null); + + return GetRegistered(o.GetType()); + } + + /// + /// Finds registered on a type. + /// + /// The type. + /// The property. + /// The registered property or null if not found. + /// + /// Calling AddOwner on a PerspexProperty creates a new PerspexProperty that is a + /// different object but is equal according to . + /// + public PerspexProperty FindRegistered(Type type, PerspexProperty property) + { + while (type != null) + { + List list; + + if (_registered.TryGetValue(type, out list)) + { + var index = list.IndexOf(property); + + if (index != -1) + { + return list[index]; + } + } + + type = type.GetTypeInfo().BaseType; + } + + return null; + } + + /// + /// Finds registered on an object. + /// + /// The object. + /// The property. + /// The registered property or null if not found. + /// + /// Calling AddOwner on a PerspexProperty creates a new PerspexProperty that is a + /// different object but is equal according to . + /// + public PerspexProperty FindRegistered(object o, PerspexProperty property) + { + return FindRegistered(o.GetType(), property); + } + + /// + /// Finds a registered property on a type by name. + /// + /// The type. + /// + /// The property name. If an attached property it should be in the form + /// "OwnerType.PropertyName". + /// + /// + /// The registered property or null if no matching property found. + /// + public PerspexProperty FindRegistered(Type type, string name) + { + Contract.Requires(type != null); + Contract.Requires(name != null); + + var parts = name.Split('.'); + + if (parts.Length < 1 || parts.Length > 2) + { + throw new ArgumentException("Invalid property name."); + } + + if (parts.Length == 1) + { + var result = GetRegistered(type) + .FirstOrDefault(x => !x.IsAttached && x.Name == parts[0]); + + if (result != null) + { + return result; + } + + // A type can .AddOwner an attached property. + return GetRegistered(type) + .FirstOrDefault(x => x.Name == parts[0]); + } + else + { + return GetRegistered(type) + .FirstOrDefault(x => x.IsAttached && x.OwnerType.Name == parts[0] && x.Name == parts[1]); + } + } + /// + /// Checks whether a is registered on a type. + /// + /// The type. + /// The property. + /// True if the property is registered, otherwise false. + public bool IsRegistered(Type type, PerspexProperty property) + { + return FindRegistered(type, property) != null; + } + + /// + /// Checks whether a is registered on a object. + /// + /// The object. + /// The property. + /// True if the property is registered, otherwise false. + public bool IsRegistered(object o, PerspexProperty property) + { + return IsRegistered(o.GetType(), property); + } + + /// + /// Registers a on a type. + /// + /// The type. + /// The property. + /// + /// You won't usually want to call this method directly, instead use the + /// method. + /// + public void Register(Type type, PerspexProperty property) + { + Contract.Requires(type != null); + Contract.Requires(property != null); + + List list; + + if (!_registered.TryGetValue(type, out list)) + { + list = new List(); + _registered.Add(type, list); + } + + if (!list.Contains(property)) + { + list.Add(property); + } + + if (property.IsAttached) + { + if (!_attached.TryGetValue(property.OwnerType, out list)) + { + list = new List(); + _attached.Add(property.OwnerType, list); + } + + if (!list.Contains(property)) + { + list.Add(property); + } + } + } + } +} diff --git a/src/Perspex.Base/PerspexProperty`1.cs b/src/Perspex.Base/PerspexProperty`1.cs index 73ac84e102..9a39b95c0c 100644 --- a/src/Perspex.Base/PerspexProperty`1.cs +++ b/src/Perspex.Base/PerspexProperty`1.cs @@ -105,7 +105,7 @@ namespace Perspex "You must provide a new getter and setter when calling AddOwner on a direct PerspexProperty."); } - PerspexObject.Register(typeof(TOwner), this); + PerspexPropertyRegistry.Instance.Register(typeof(TOwner), this); return this; } @@ -119,8 +119,12 @@ namespace Perspex Action setter = null) where TOwner : PerspexObject { - var result = new PerspexProperty(this, CastReturn(getter), CastParam1(setter)); - PerspexObject.Register(typeof(TOwner), result); + var result = new PerspexProperty( + this, + CastReturn(getter), + CastParam1(setter)); + + PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result); return result; } diff --git a/src/Perspex.Diagnostics/Debug.cs b/src/Perspex.Diagnostics/Debug.cs index d5362c3257..1e07363a71 100644 --- a/src/Perspex.Diagnostics/Debug.cs +++ b/src/Perspex.Diagnostics/Debug.cs @@ -37,7 +37,7 @@ namespace Perspex.Diagnostics builder.Append(" "); builder.AppendLine(control.Classes.ToString()); - foreach (var property in control.GetRegisteredProperties()) + foreach (var property in PerspexPropertyRegistry.Instance.GetRegistered(control)) { var value = control.GetDiagnostic(property); diff --git a/src/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs index 8a45ec8244..73abcf4a04 100644 --- a/src/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -14,7 +14,7 @@ namespace Perspex.Diagnostics.ViewModels { if (control != null) { - Properties = control.GetRegisteredProperties() + Properties = PerspexPropertyRegistry.Instance.GetRegistered(control) .Select(x => new PropertyDetails(control, x)) .OrderBy(x => x.IsAttached) .ThenBy(x => x.Name); diff --git a/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj b/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj index e343d3e512..398d073db5 100644 --- a/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj +++ b/tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj @@ -75,6 +75,7 @@ + diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Metadata.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Metadata.cs index 7fa816cf82..4233806b65 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Metadata.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Metadata.cs @@ -18,30 +18,6 @@ namespace Perspex.Base.UnitTests p = AttachedOwner.AttachedProperty; } - [Fact] - public void GetRegisteredProperties_Returns_Registered_Properties() - { - string[] names = PerspexObject.GetRegisteredProperties(typeof(Class1)).Select(x => x.Name).ToArray(); - - Assert.Equal(new[] { "Foo", "Baz", "Qux", "Attached" }, names); - } - - [Fact] - public void GetRegisteredProperties_Returns_Registered_Properties_For_Base_Types() - { - string[] names = PerspexObject.GetRegisteredProperties(typeof(Class2)).Select(x => x.Name).ToArray(); - - Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux", "Attached" }, names); - } - - [Fact] - public void GetAttachedProperties_Returns_Registered_Properties_For_Base_Types() - { - string[] names = PerspexObject.GetAttachedProperties(typeof(AttachedOwner)).Select(x => x.Name).ToArray(); - - Assert.Equal(new[] { "Attached" }, names); - } - [Fact] public void IsSet_Returns_False_For_Unset_Property() { diff --git a/tests/Perspex.Base.UnitTests/PerspexPropertyRegistryTests.cs b/tests/Perspex.Base.UnitTests/PerspexPropertyRegistryTests.cs new file mode 100644 index 0000000000..c0a9eb1f4f --- /dev/null +++ b/tests/Perspex.Base.UnitTests/PerspexPropertyRegistryTests.cs @@ -0,0 +1,79 @@ +// 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.Linq; +using System.Reactive.Linq; +using Xunit; + +namespace Perspex.Base.UnitTests +{ + public class PerspexPropertyRegistryTests + { + public PerspexPropertyRegistryTests() + { + // Ensure properties are registered. + PerspexProperty p; + p = Class1.FooProperty; + p = Class2.BarProperty; + p = AttachedOwner.AttachedProperty; + } + + [Fact] + public void GetRegistered_Returns_Registered_Properties() + { + string[] names = PerspexPropertyRegistry.Instance.GetRegistered(typeof(Class1)) + .Select(x => x.Name) + .ToArray(); + + Assert.Equal(new[] { "Foo", "Baz", "Qux", "Attached" }, names); + } + + [Fact] + public void GetRegistered_Returns_Registered_Properties_For_Base_Types() + { + string[] names = PerspexPropertyRegistry.Instance.GetRegistered(typeof(Class2)) + .Select(x => x.Name) + .ToArray(); + + Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux", "Attached" }, names); + } + + [Fact] + public void GetAttached_Returns_Registered_Properties_For_Base_Types() + { + string[] names = PerspexPropertyRegistry.Instance.GetAttached(typeof(AttachedOwner)).Select(x => x.Name).ToArray(); + + Assert.Equal(new[] { "Attached" }, names); + } + + private class Class1 : PerspexObject + { + public static readonly PerspexProperty FooProperty = + PerspexProperty.Register("Foo"); + + public static readonly PerspexProperty BazProperty = + PerspexProperty.Register("Baz"); + + public static readonly PerspexProperty QuxProperty = + PerspexProperty.Register("Qux"); + } + + private class Class2 : Class1 + { + public static readonly PerspexProperty BarProperty = + PerspexProperty.Register("Bar"); + + public static readonly PerspexProperty FlobProperty = + PerspexProperty.Register("Flob"); + + public static readonly PerspexProperty FredProperty = + PerspexProperty.Register("Fred"); + } + + private class AttachedOwner + { + public static readonly PerspexProperty AttachedProperty = + PerspexProperty.RegisterAttached("Attached"); + } + } +}