Browse Source

Created PerspexPropertyRegistry.

And move registration of properties, finding properties etc here.
pull/297/head
Steven Kirk 10 years ago
parent
commit
952e4a99f7
  1. 4
      src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs
  2. 4
      src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyTypeConverter.cs
  3. 1
      src/Perspex.Base/Perspex.Base.csproj
  4. 172
      src/Perspex.Base/PerspexObject.cs
  5. 9
      src/Perspex.Base/PerspexProperty.cs
  6. 244
      src/Perspex.Base/PerspexPropertyRegistry.cs
  7. 10
      src/Perspex.Base/PerspexProperty`1.cs
  8. 2
      src/Perspex.Diagnostics/Debug.cs
  9. 2
      src/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs
  10. 1
      tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj
  11. 24
      tests/Perspex.Base.UnitTests/PerspexObjectTests_Metadata.cs
  12. 79
      tests/Perspex.Base.UnitTests/PerspexPropertyRegistryTests.cs

4
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);
}

4
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);
}

1
src/Perspex.Base/Perspex.Base.csproj

@ -50,6 +50,7 @@
<Compile Include="PerspexLocator.cs" />
<Compile Include="Metadata\XmlnsDefinitionAttribute.cs" />
<Compile Include="PerspexObjectExtensions.cs" />
<Compile Include="PerspexPropertyRegistry.cs" />
<Compile Include="PerspexProperty`1.cs" />
<Compile Include="Platform\IPclPlatformWrapper.cs" />
<Compile Include="BindingPriority.cs" />

172
src/Perspex.Base/PerspexObject.cs

@ -25,18 +25,6 @@ namespace Perspex
/// </remarks>
public class PerspexObject : IObservablePropertyBag, INotifyPropertyChanged
{
/// <summary>
/// The registered properties by type.
/// </summary>
private static readonly Dictionary<Type, List<PerspexProperty>> s_registered =
new Dictionary<Type, List<PerspexProperty>>();
/// <summary>
/// The registered attached properties by owner type.
/// </summary>
private static readonly Dictionary<Type, List<PerspexProperty>> s_attached =
new Dictionary<Type, List<PerspexProperty>>();
/// <summary>
/// The parent object that inherited values are inherited from.
/// </summary>
@ -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
}
}
/// <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 static IEnumerable<PerspexProperty> GetRegisteredProperties(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
TypeInfo i = type.GetTypeInfo();
while (type != null)
{
List<PerspexProperty> list;
if (s_registered.TryGetValue(type, out list))
{
foreach (PerspexProperty p in list)
{
yield return p;
}
}
type = type.GetTypeInfo().BaseType;
}
}
/// <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 static IEnumerable<PerspexProperty> GetAttachedProperties(Type ownerType)
{
List<PerspexProperty> list;
if (s_attached.TryGetValue(ownerType, out list))
{
return list;
}
return Enumerable.Empty<PerspexProperty>();
}
/// <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 static void Register(Type type, PerspexProperty property)
{
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(property != null);
List<PerspexProperty> list;
if (!s_registered.TryGetValue(type, out list))
{
list = new List<PerspexProperty>();
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<PerspexProperty>();
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
}
}
/// <summary>
/// Gets all properties that are registered on this object.
/// </summary>
/// <returns>
/// A collection of <see cref="PerspexProperty"/> objects.
/// </returns>
public IEnumerable<PerspexProperty> GetRegisteredProperties()
{
return GetRegisteredProperties(GetType());
}
/// <summary>
/// Checks whether a <see cref="PerspexProperty"/> is set on this object.
/// </summary>
@ -478,16 +369,6 @@ namespace Perspex
return false;
}
/// <summary>
/// Checks whether a <see cref="PerspexProperty"/> is registered on this class.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>True if the property is registered, otherwise false.</returns>
public bool IsRegistered(PerspexProperty property)
{
return FindRegistered(property) != null;
}
/// <summary>
/// Sets a <see cref="PerspexProperty"/> value.
/// </summary>
@ -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
}
}
/// <inheritdoc/>
bool IPropertyBag.IsRegistered(PerspexProperty property)
{
return PerspexPropertyRegistry.Instance.IsRegistered(this, property);
}
/// <summary>
/// Gets all priority values set on the object.
/// </summary>
@ -936,41 +823,6 @@ namespace Perspex
}
}
/// <summary>
/// Given a <see cref="PerspexProperty"/> returns a registered perspex property that is
/// equal.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The registered property or null if not found.</returns>
/// <remarks>
/// 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 <see cref="object.Equals(object)"/>.
/// </remarks>
public PerspexProperty FindRegistered(PerspexProperty property)
{
Type type = GetType();
while (type != null)
{
List<PerspexProperty> 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;
}
/// <summary>
/// Given a <see cref="PerspexProperty"/> returns a registered perspex property that is
/// equal or throws if not found.
@ -979,7 +831,7 @@ namespace Perspex
/// <returns>The registered property.</returns>
public PerspexProperty GetRegistered(PerspexProperty property)
{
var result = FindRegistered(property);
var result = PerspexPropertyRegistry.Instance.FindRegistered(this, property);
if (result == null)
{

9
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;
}

244
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
{
/// <summary>
/// Tracks registered <see cref="PerspexProperty"/> instances.
/// </summary>
public class PerspexPropertyRegistry
{
/// <summary>
/// The registered properties by type.
/// </summary>
private readonly Dictionary<Type, List<PerspexProperty>> _registered =
new Dictionary<Type, List<PerspexProperty>>();
/// <summary>
/// The registered attached properties by owner type.
/// </summary>
private readonly Dictionary<Type, List<PerspexProperty>> _attached =
new Dictionary<Type, List<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)
{
List<PerspexProperty> list;
if (_attached.TryGetValue(ownerType, out list))
{
return list;
}
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)
{
List<PerspexProperty> list;
if (_registered.TryGetValue(type, out list))
{
foreach (PerspexProperty p in list)
{
yield return p;
}
}
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 <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)
{
List<PerspexProperty> list;
if (_registered.TryGetValue(type, out list))
{
var index = list.IndexOf(property);
if (index != -1)
{
return list[index];
}
}
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('.');
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]);
}
}
/// <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);
List<PerspexProperty> list;
if (!_registered.TryGetValue(type, out list))
{
list = new List<PerspexProperty>();
_registered.Add(type, list);
}
if (!list.Contains(property))
{
list.Add(property);
}
if (property.IsAttached)
{
if (!_attached.TryGetValue(property.OwnerType, out list))
{
list = new List<PerspexProperty>();
_attached.Add(property.OwnerType, list);
}
if (!list.Contains(property))
{
list.Add(property);
}
}
}
}
}

10
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<TOwner, TValue> setter = null)
where TOwner : PerspexObject
{
var result = new PerspexProperty<TValue>(this, CastReturn(getter), CastParam1(setter));
PerspexObject.Register(typeof(TOwner), result);
var result = new PerspexProperty<TValue>(
this,
CastReturn(getter),
CastParam1(setter));
PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
}

2
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);

2
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);

1
tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj

@ -75,6 +75,7 @@
<Compile Include="Collections\PropertyChangedTracker.cs" />
<Compile Include="PerspexObjectTests_Direct.cs" />
<Compile Include="PerspexObjectTests_GetObservable.cs" />
<Compile Include="PerspexPropertyRegistryTests.cs" />
<Compile Include="PerspexObjectTests_Validation.cs" />
<Compile Include="PerspexObjectTests_Binding.cs" />
<Compile Include="PerspexObjectTests_Inheritance.cs" />

24
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()
{

79
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<string> FooProperty =
PerspexProperty.Register<Class1, string>("Foo");
public static readonly PerspexProperty<string> BazProperty =
PerspexProperty.Register<Class1, string>("Baz");
public static readonly PerspexProperty<int> QuxProperty =
PerspexProperty.Register<Class1, int>("Qux");
}
private class Class2 : Class1
{
public static readonly PerspexProperty<string> BarProperty =
PerspexProperty.Register<Class2, string>("Bar");
public static readonly PerspexProperty<double> FlobProperty =
PerspexProperty.Register<Class2, double>("Flob");
public static readonly PerspexProperty<double?> FredProperty =
PerspexProperty.Register<Class2, double?>("Fred");
}
private class AttachedOwner
{
public static readonly PerspexProperty<string> AttachedProperty =
PerspexProperty.RegisterAttached<AttachedOwner, Class1, string>("Attached");
}
}
}
Loading…
Cancel
Save