diff --git a/src/Avalonia.Base/AttachedProperty.cs b/src/Avalonia.Base/AttachedProperty.cs index 9d4d40bfef..fdb04b6dfc 100644 --- a/src/Avalonia.Base/AttachedProperty.cs +++ b/src/Avalonia.Base/AttachedProperty.cs @@ -9,7 +9,7 @@ namespace Avalonia /// An attached avalonia property. /// /// The type of the property's value. - public class AttachedProperty : StyledPropertyBase + public class AttachedProperty : StyledProperty { /// /// Initializes a new instance of the class. @@ -35,11 +35,10 @@ namespace Avalonia /// /// The owner type. /// The property. - public StyledProperty AddOwner() where TOwner : IAvaloniaObject + public new AttachedProperty AddOwner() where TOwner : IAvaloniaObject { - var result = new StyledProperty(this, typeof(TOwner)); - AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result); - return result; + AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this); + return this; } } } diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index a46d567d28..4ab813333d 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -12,7 +12,6 @@ using Avalonia.Diagnostics; using Avalonia.Logging; using Avalonia.Threading; using Avalonia.Utilities; -using System.Reactive.Concurrency; namespace Avalonia { @@ -218,11 +217,6 @@ namespace Avalonia } else { - if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property)) - { - ThrowNotRegistered(property); - } - return GetValueInternal(property); } } @@ -377,11 +371,6 @@ namespace Avalonia { PriorityValue v; - if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property)) - { - ThrowNotRegistered(property); - } - if (!_values.TryGetValue(property, out v)) { v = CreatePriorityValue(property); @@ -804,11 +793,6 @@ namespace Avalonia var originalValue = value; - if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property)) - { - ThrowNotRegistered(property); - } - if (!TypeUtilities.TryConvertImplicit(property.PropertyType, value, out value)) { throw new ArgumentException(string.Format( @@ -836,18 +820,32 @@ namespace Avalonia } /// - /// Given a returns a registered avalonia property that is - /// equal or throws if not found. + /// Given a direct property, returns a registered avalonia property that is equivalent or + /// throws if not found. /// /// The property. /// The registered property. - public AvaloniaProperty GetRegistered(AvaloniaProperty property) + private AvaloniaProperty GetRegistered(AvaloniaProperty property) { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(this, property); + var direct = property as IDirectPropertyAccessor; + + if (direct == null) + { + throw new AvaloniaInternalException( + "AvaloniaObject.GetRegistered should only be called for direct properties"); + } + + if (property.OwnerType.IsAssignableFrom(GetType())) + { + return property; + } + + var result = AvaloniaPropertyRegistry.Instance.GetRegistered(this) + .FirstOrDefault(x => x == property); if (result == null) { - ThrowNotRegistered(property); + throw new ArgumentException($"Property '{property.Name} not registered on '{this.GetType()}"); } return result; @@ -898,15 +896,5 @@ namespace Avalonia value, priority); } - - /// - /// Throws an exception indicating that the specified property is not registered on this - /// object. - /// - /// The property - private void ThrowNotRegistered(AvaloniaProperty p) - { - throw new ArgumentException($"Property '{p.Name} not registered on '{this.GetType()}"); - } } } diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index fb78e3b2a0..f7dabd3a43 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -311,7 +311,9 @@ namespace Avalonia defaultBindingMode: defaultBindingMode); var result = new AttachedProperty(name, typeof(TOwner), metadata, inherits); - AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result); + var registry = AvaloniaPropertyRegistry.Instance; + registry.Register(typeof(TOwner), result); + registry.RegisterAttached(typeof(THost), result); return result; } @@ -344,7 +346,9 @@ namespace Avalonia defaultBindingMode: defaultBindingMode); var result = new AttachedProperty(name, ownerType, metadata, inherits); - AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result); + var registry = AvaloniaPropertyRegistry.Instance; + registry.Register(ownerType, result); + registry.RegisterAttached(typeof(THost), result); return result; } diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index ec1643427b..c0a4ace6ed 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -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 /// public class AvaloniaPropertyRegistry { - /// - /// The registered properties by type. - /// private readonly Dictionary> _registered = new Dictionary>(); - - /// - /// The registered properties by type cached values to increase performance. - /// - private readonly Dictionary> _registeredCache = - new Dictionary>(); - - /// - /// The registered attached properties by owner type. - /// private readonly Dictionary> _attached = new Dictionary>(); + private readonly Dictionary> _registeredCache = + new Dictionary>(); + private readonly Dictionary> _attachedCache = + new Dictionary>(); /// /// Gets the instance @@ -39,51 +29,68 @@ namespace Avalonia = new AvaloniaPropertyRegistry(); /// - /// Gets all attached s registered by an owner. + /// Gets all non-attached s registered on a type. /// - /// The owner type. + /// The type. /// A collection of definitions. - public IEnumerable GetAttached(Type ownerType) + public IEnumerable GetRegistered(Type type) { - Dictionary inner; + Contract.Requires(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(); - 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(); + _registeredCache.Add(type, result); + return result; } /// - /// Gets all s registered on a type. + /// Gets all attached s registered on a type. /// /// The type. /// A collection of definitions. - public IEnumerable GetRegistered(Type type) + public IEnumerable GetRegisteredAttached(Type type) { Contract.Requires(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 inner; + var t = type; + result = new List(); - 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; } /// @@ -99,142 +106,92 @@ namespace Avalonia } /// - /// Finds a registered on a type. + /// Finds a registered non-attached property on a type by name. /// /// The type. - /// The property. - /// The registered property or null if not found. - /// - /// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a - /// different object but is equal according to . - /// - public AvaloniaProperty FindRegistered(Type type, AvaloniaProperty property) + /// The property name. + /// + /// The registered property or null if no matching property found. + /// + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegistered(Type type, string name) { - Type currentType = type; - Dictionary cache; - AvaloniaProperty result; + Contract.Requires(type != null); + Contract.Requires(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 inner; - - if (_registered.TryGetValue(currentType, out inner)) - { - if (inner.TryGetValue(property.Id, out result)) - { - if (cache == null) - { - _registeredCache[type] = cache = new Dictionary(); - } - - cache[property.Id] = result; - - return result; - } - } - - currentType = currentType.GetTypeInfo().BaseType; - } - - return null; + return GetRegistered(type).FirstOrDefault(x => x.Name == name); } /// - /// Finds registered on an object. + /// Finds a registered non-attached property on a type by name. /// /// The object. - /// The property. - /// The registered property or null if not found. - /// - /// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a - /// different object but is equal according to . - /// - public AvaloniaProperty FindRegistered(object o, AvaloniaProperty property) + /// The property name. + /// + /// The registered property or null if no matching property found. + /// + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegistered(AvaloniaObject o, string name) { - return FindRegistered(o.GetType(), property); + Contract.Requires(o != null); + Contract.Requires(name != null); + + return FindRegistered(o.GetType(), name); } /// - /// Finds a registered property on a type by name. + /// Finds a registered attached property on a type by name. /// /// The type. - /// - /// The property name. If an attached property it should be in the form - /// "OwnerType.PropertyName". - /// + /// The owner type. + /// The property name. /// /// The registered property or null if no matching property found. /// - public AvaloniaProperty FindRegistered(Type type, string name) + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegisteredAttached(Type type, Type ownerType, string name) { Contract.Requires(type != null); + Contract.Requires(ownerType != null); Contract.Requires(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); } /// - /// Finds a registered property on an object by name. + /// Finds a registered non-attached property on a type by name. /// /// The object. - /// - /// The property name. If an attached property it should be in the form - /// "OwnerType.PropertyName". - /// + /// The owner type. + /// The property name. /// /// The registered property or null if no matching property found. /// - public AvaloniaProperty FindRegistered(AvaloniaObject o, string name) + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegisteredAttached(AvaloniaObject o, Type ownerType, string name) { - return FindRegistered(o.GetType(), name); - } + Contract.Requires(o != null); + Contract.Requires(name != null); - /// - /// Returns a type and all its base types. - /// - /// The type. - /// The type and all its base types. - private IEnumerable GetImplementedTypes(Type type) - { - while (type != null) - { - yield return type.Name; - type = type.GetTypeInfo().BaseType; - } + return FindRegisteredAttached(o.GetType(), ownerType, name); } /// @@ -245,7 +202,11 @@ namespace Avalonia /// True if the property is registered, otherwise false. public bool IsRegistered(Type type, AvaloniaProperty property) { - return FindRegistered(type, property) != null; + Contract.Requires(type != null); + Contract.Requires(property != null); + + return Instance.GetRegistered(type).Any(x => x == property) || + Instance.GetRegisteredAttached(type).Any(x => x == property); } /// @@ -256,6 +217,9 @@ namespace Avalonia /// True if the property is registered, otherwise false. public bool IsRegistered(object o, AvaloniaProperty property) { + Contract.Requires(o != null); + Contract.Requires(property != null); + return IsRegistered(o.GetType(), property); } @@ -274,34 +238,53 @@ namespace Avalonia Contract.Requires(type != null); Contract.Requires(property != null); - Dictionary inner; - - if (!_registered.TryGetValue(type, out inner)) + if (!_registered.TryGetValue(type, out var inner)) { inner = new Dictionary(); + 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) + /// + /// Registers an attached on a type. + /// + /// The type. + /// The property. + /// + /// You won't usually want to call this method directly, instead use the + /// + /// method. + /// + public void RegisterAttached(Type type, AvaloniaProperty property) + { + Contract.Requires(type != null); + Contract.Requires(property != null); + + if (!property.IsAttached) { - if (!_attached.TryGetValue(property.OwnerType, out inner)) - { - inner = new Dictionary(); - _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(); + inner.Add(property.Id, property); + _attached.Add(type, inner); + } + else + { + inner.Add(property.Id, property); } - _registeredCache.Clear(); + _attachedCache.Clear(); } } } \ No newline at end of file diff --git a/src/Avalonia.Base/DirectProperty.cs b/src/Avalonia.Base/DirectProperty.cs index 8352528285..1ce73c20ba 100644 --- a/src/Avalonia.Base/DirectProperty.cs +++ b/src/Avalonia.Base/DirectProperty.cs @@ -75,6 +75,9 @@ namespace Avalonia /// public Action Setter { get; } + /// + Type IDirectPropertyAccessor.Owner => typeof(TOwner); + /// /// Registers the direct property on another type. /// diff --git a/src/Avalonia.Base/IDirectPropertyAccessor.cs b/src/Avalonia.Base/IDirectPropertyAccessor.cs index 62aeef73c7..4f46652693 100644 --- a/src/Avalonia.Base/IDirectPropertyAccessor.cs +++ b/src/Avalonia.Base/IDirectPropertyAccessor.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; + namespace Avalonia { /// @@ -14,6 +16,11 @@ namespace Avalonia /// bool IsReadOnly { get; } + /// + /// Gets the class that registered the property. + /// + Type Owner { get; } + /// /// Gets the value of the property on the instance. /// diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs index a34ccaa413..bc3caff3b9 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs @@ -53,10 +53,7 @@ namespace Avalonia.Markup.Xaml.Converters } } - // First look for non-attached property on the type and then look for an attached property. - var property = AvaloniaPropertyRegistry.Instance.FindRegistered(type, s) ?? - AvaloniaPropertyRegistry.Instance.GetAttached(type) - .FirstOrDefault(x => x.Name == propertyName); + AvaloniaProperty property = AvaloniaPropertyRegistry.Instance.FindRegistered(type, propertyName); if (property == null) { diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs index bdb21abd77..fda5da902a 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs @@ -200,8 +200,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml var type = (getter ?? setter).DeclaringType; - var prop = AvaloniaPropertyRegistry.Instance.GetAttached(type) - .FirstOrDefault(v => v.Name == attachablePropertyName); + var prop = AvaloniaPropertyRegistry.Instance.FindRegistered(type, attachablePropertyName); if (prop != null) { diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs index 7de96ea220..59dbba7084 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs @@ -19,16 +19,36 @@ namespace Avalonia.Markup.Xaml.PortableXaml public class AvaloniaXamlType : XamlType { + static readonly AvaloniaPropertyTypeConverter propertyTypeConverter = new AvaloniaPropertyTypeConverter(); + public AvaloniaXamlType(Type underlyingType, XamlSchemaContext schemaContext) : base(underlyingType, schemaContext) { } + protected override XamlMember LookupAttachableMember(string name) + { + var m = base.LookupAttachableMember(name); + + if (m == null) + { + // Might be an AddOwnered attached property. + var avProp = AvaloniaPropertyRegistry.Instance.FindRegistered(UnderlyingType, name); + + if (avProp?.IsAttached == true) + { + return new AvaloniaPropertyXamlMember(avProp, this); + } + } + + return m; + } + protected override XamlMember LookupMember(string name, bool skipReadOnlyCheck) { var m = base.LookupMember(name, skipReadOnlyCheck); - if (m == null) + if (m == null && !name.Contains(".")) { //so far Portable.xaml haven't found the member/property //but what if we have AvaloniaProperty diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs b/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs index 90eabc69fb..ac64459dd7 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs +++ b/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Linq; using System.Reactive.Linq; using Avalonia.Data; @@ -15,9 +16,9 @@ namespace Avalonia.Markup.Data.Plugins /// public bool Match(object obj, string propertyName) { - if (obj is AvaloniaObject a) + if (obj is AvaloniaObject o) { - return AvaloniaPropertyRegistry.Instance.FindRegistered(a, propertyName) != null; + return LookupProperty(o, propertyName) != null; } return false; @@ -39,7 +40,7 @@ namespace Avalonia.Markup.Data.Plugins var instance = reference.Target; var o = (AvaloniaObject)instance; - var p = AvaloniaPropertyRegistry.Instance.FindRegistered(o, propertyName); + var p = LookupProperty(o, propertyName); if (p != null) { @@ -57,6 +58,54 @@ namespace Avalonia.Markup.Data.Plugins } } + private static AvaloniaProperty LookupProperty(AvaloniaObject o, string propertyName) + { + if (!propertyName.Contains(".")) + { + return AvaloniaPropertyRegistry.Instance.FindRegistered(o, propertyName); + } + else + { + var split = propertyName.Split('.'); + + if (split.Length == 2) + { + // HACK: We need a way to resolve types here using something like IXamlTypeResolver. + // We don't currently have that so we have to make our best guess. + var type = split[0]; + var name = split[1]; + var registry = AvaloniaPropertyRegistry.Instance; + var registered = registry.GetRegisteredAttached(o.GetType()) + .Concat(registry.GetRegistered(o.GetType())); + + foreach (var p in registered) + { + if (p.Name == name && IsOfType(p.OwnerType, type)) + { + return p; + } + } + } + } + + return null; + } + + private static bool IsOfType(Type type, string typeName) + { + while (type != null) + { + if (type.Name == typeName) + { + return true; + } + + type = type.BaseType; + } + + return false; + } + private class Accessor : PropertyAccessorBase { private readonly WeakReference _reference; diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs new file mode 100644 index 0000000000..4e033be3fb --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs @@ -0,0 +1,52 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using Xunit; + +namespace Avalonia.Base.UnitTests +{ + public class AvaloniaObjectTests_AddOwner + { + [Fact] + public void AddOwnered_Property_Retains_Default_Value() + { + var target = new Class2(); + + Assert.Equal("foodefault", target.GetValue(Class2.FooProperty)); + } + + [Fact] + public void AddOwnered_Property_Does_Not_Retain_Validation() + { + var target = new Class2(); + + target.SetValue(Class2.FooProperty, "throw"); + } + + private class Class1 : AvaloniaObject + { + public static readonly StyledProperty FooProperty = + AvaloniaProperty.Register( + "Foo", + "foodefault", + validate: ValidateFoo); + + private static string ValidateFoo(AvaloniaObject arg1, string arg2) + { + if (arg2 == "throw") + { + throw new IndexOutOfRangeException(); + } + + return arg2; + } + } + + private class Class2 : AvaloniaObject + { + public static readonly StyledProperty FooProperty = + Class1.FooProperty.AddOwner(); + } + } +} diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs new file mode 100644 index 0000000000..acaabc73df --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs @@ -0,0 +1,52 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using Xunit; + +namespace Avalonia.Base.UnitTests +{ + public class AvaloniaObjectTests_Attached + { + [Fact] + public void AddOwnered_Property_Retains_Default_Value() + { + var target = new Class2(); + + Assert.Equal("foodefault", target.GetValue(Class2.FooProperty)); + } + + [Fact] + public void AddOwnered_Property_Retains_Validation() + { + var target = new Class2(); + + Assert.Throws(() => target.SetValue(Class2.FooProperty, "throw")); + } + + private class Class1 : AvaloniaObject + { + public static readonly AttachedProperty FooProperty = + AvaloniaProperty.RegisterAttached( + "Foo", + "foodefault", + validate: ValidateFoo); + + private static string ValidateFoo(AvaloniaObject arg1, string arg2) + { + if (arg2 == "throw") + { + throw new IndexOutOfRangeException(); + } + + return arg2; + } + } + + private class Class2 : AvaloniaObject + { + public static readonly AttachedProperty FooProperty = + Class1.FooProperty.AddOwner(); + } + } +} diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs index da0b0252a3..c030657034 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs @@ -4,12 +4,15 @@ using System.Linq; using System.Reactive.Linq; using Xunit; +using Xunit.Abstractions; namespace Avalonia.Base.UnitTests { public class AvaloniaPropertyRegistryTests { - public AvaloniaPropertyRegistryTests() + ITestOutputHelper s; + + public AvaloniaPropertyRegistryTests(ITestOutputHelper s) { // Ensure properties are registered. AvaloniaProperty p; @@ -25,7 +28,7 @@ namespace Avalonia.Base.UnitTests .Select(x => x.Name) .ToArray(); - Assert.Equal(new[] { "Foo", "Baz", "Qux", "Attached" }, names); + Assert.Equal(new[] { "Foo", "Baz", "Qux" }, names); } [Fact] @@ -35,61 +38,41 @@ namespace Avalonia.Base.UnitTests .Select(x => x.Name) .ToArray(); - Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux", "Attached" }, names); + Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux" }, names); } [Fact] - public void GetAttached_Returns_Registered_Properties_For_Base_Types() + public void GetRegisteredAttached_Returns_Registered_Properties() { - string[] names = AvaloniaPropertyRegistry.Instance.GetAttached(typeof(AttachedOwner)).Select(x => x.Name).ToArray(); + string[] names = AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(typeof(Class1)) + .Select(x => x.Name) + .ToArray(); Assert.Equal(new[] { "Attached" }, names); } [Fact] - public void FindRegistered_Finds_Untyped_Property() + public void GetRegisteredAttached_Returns_Registered_Properties_For_Base_Types() { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Foo"); + string[] names = AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(typeof(Class2)) + .Select(x => x.Name) + .ToArray(); - Assert.Equal(Class1.FooProperty, result); + Assert.Equal(new[] { "Attached" }, names); } [Fact] - public void FindRegistered_Finds_Typed_Property() + public void FindRegistered_Finds_Property() { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Class1.Foo"); + var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Foo"); Assert.Equal(Class1.FooProperty, result); } [Fact] - public void FindRegistered_Finds_Typed_Inherited_Property() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Class1.Foo"); - - Assert.Equal(Class2.FooProperty, result); - } - - [Fact] - public void FindRegistered_Finds_Inherited_Property_With_Derived_Type_Name() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Class2.Foo"); - - Assert.Equal(Class2.FooProperty, result); - } - - [Fact] - public void FindRegistered_Finds_Attached_Property() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "AttachedOwner.Attached"); - - Assert.Equal(AttachedOwner.AttachedProperty, result); - } - - [Fact] - public void FindRegistered_Doesnt_Finds_Unqualified_Attached_Property() + public void FindRegistered_Doesnt_Find_Nonregistered_Property() { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Attached"); + var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Bar"); Assert.Null(result); } @@ -99,55 +82,34 @@ namespace Avalonia.Base.UnitTests { var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(AttachedOwner), "Attached"); - Assert.True(AttachedOwner.AttachedProperty == result); + Assert.Same(AttachedOwner.AttachedProperty, result); } [Fact] - public void FindRegistered_Finds_AddOwnered_Untyped_Attached_Property() + public void FindRegistered_Finds_AddOwnered_Attached_Property() { var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "Attached"); - Assert.True(AttachedOwner.AttachedProperty == result); + Assert.Same(AttachedOwner.AttachedProperty, result); } [Fact] - public void FindRegistered_Finds_AddOwnered_Typed_Attached_Property() + public void FindRegistered_Doesnt_Find_Non_AddOwnered_Attached_Property() { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "Class3.Attached"); - - Assert.True(AttachedOwner.AttachedProperty == result); - } - - [Fact] - public void FindRegistered_Finds_AddOwnered_AttachedTyped_Attached_Property() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "AttachedOwner.Attached"); - - Assert.True(AttachedOwner.AttachedProperty == result); - } - - [Fact] - public void FindRegistered_Finds_AddOwnered_BaseTyped_Attached_Property() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "Class1.Attached"); - - Assert.True(AttachedOwner.AttachedProperty == result); - } - - [Fact] - public void FindRegistered_Doesnt_Find_Nonregistered_Property() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Bar"); + var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Attached"); Assert.Null(result); } [Fact] - public void FindRegistered_Doesnt_Find_Nonregistered_Attached_Property() + public void FindRegisteredAttached_Finds_Property() { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class4), "AttachedOwner.Attached"); + var result = AvaloniaPropertyRegistry.Instance.FindRegisteredAttached( + typeof(Class1), + typeof(AttachedOwner), + "Attached"); - Assert.Null(result); + Assert.Equal(AttachedOwner.AttachedProperty, result); } private class Class1 : AvaloniaObject @@ -176,18 +138,18 @@ namespace Avalonia.Base.UnitTests private class Class3 : Class1 { - public static readonly StyledProperty AttachedProperty = + public static readonly AttachedProperty AttachedProperty = AttachedOwner.AttachedProperty.AddOwner(); } - public class Class4 : AvaloniaObject - { - } - private class AttachedOwner : Class1 { public static readonly AttachedProperty AttachedProperty = AvaloniaProperty.RegisterAttached("Attached"); } + + private class AttachedOwner2 : AttachedOwner + { + } } }