Browse Source

Merge pull request #11341 from AvaloniaUI/fixes/11212-attached-property-coercion

Register attached property metadata on TOwner instead of THost.
pull/11387/head
Max Katz 3 years ago
committed by GitHub
parent
commit
ff54192a05
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      src/Avalonia.Base/AttachedProperty.cs
  2. 12
      src/Avalonia.Base/AvaloniaProperty.cs
  3. 4
      src/Avalonia.Base/AvaloniaProperty`1.cs
  4. 2
      src/Avalonia.Base/DirectPropertyBase.cs
  5. 4
      src/Avalonia.Base/StyledProperty.cs
  6. 2
      tests/Avalonia.Base.UnitTests/AttachedPropertyTests.cs
  7. 17
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs
  8. 1
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs
  9. 2
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs
  10. 2
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
  11. 4
      tests/Avalonia.Base.UnitTests/StyledPropertyTests.cs
  12. 1
      tests/Avalonia.Base.UnitTests/Utilities/AvaloniaPropertyDictionaryTests.cs
  13. 2
      tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs

4
src/Avalonia.Base/AttachedProperty.cs

@ -13,16 +13,18 @@ namespace Avalonia
/// </summary> /// </summary>
/// <param name="name">The name of the property.</param> /// <param name="name">The name of the property.</param>
/// <param name="ownerType">The class that is registering the property.</param> /// <param name="ownerType">The class that is registering the property.</param>
/// <param name="hostType">The class that the property being is registered on.</param>
/// <param name="metadata">The property metadata.</param> /// <param name="metadata">The property metadata.</param>
/// <param name="inherits">Whether the property inherits its value.</param> /// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="validate">A value validation callback.</param> /// <param name="validate">A value validation callback.</param>
public AttachedProperty( public AttachedProperty(
string name, string name,
Type ownerType, Type ownerType,
Type hostType,
StyledPropertyMetadata<TValue> metadata, StyledPropertyMetadata<TValue> metadata,
bool inherits = false, bool inherits = false,
Func<TValue, bool>? validate = null) Func<TValue, bool>? validate = null)
: base(name, ownerType, metadata, inherits, validate) : base(name, ownerType, hostType, metadata, inherits, validate)
{ {
IsAttached = true; IsAttached = true;
} }

12
src/Avalonia.Base/AvaloniaProperty.cs

@ -39,12 +39,14 @@ namespace Avalonia
/// <param name="name">The name of the property.</param> /// <param name="name">The name of the property.</param>
/// <param name="valueType">The type of the property's value.</param> /// <param name="valueType">The type of the property's value.</param>
/// <param name="ownerType">The type of the class that registers the property.</param> /// <param name="ownerType">The type of the class that registers the property.</param>
/// <param name="hostType">The class that the property being is registered on.</param>
/// <param name="metadata">The property metadata.</param> /// <param name="metadata">The property metadata.</param>
/// <param name="notifying">A <see cref="Notifying"/> callback.</param> /// <param name="notifying">A <see cref="Notifying"/> callback.</param>
protected AvaloniaProperty( protected AvaloniaProperty(
string name, string name,
Type valueType, Type valueType,
Type ownerType, Type ownerType,
Type hostType,
AvaloniaPropertyMetadata metadata, AvaloniaPropertyMetadata metadata,
Action<AvaloniaObject, bool>? notifying = null) Action<AvaloniaObject, bool>? notifying = null)
{ {
@ -63,9 +65,9 @@ namespace Avalonia
Notifying = notifying; Notifying = notifying;
Id = s_nextId++; Id = s_nextId++;
_metadata.Add(ownerType, metadata ?? throw new ArgumentNullException(nameof(metadata))); _metadata.Add(hostType, metadata ?? throw new ArgumentNullException(nameof(metadata)));
_defaultMetadata = metadata.GenerateTypeSafeMetadata(); _defaultMetadata = metadata.GenerateTypeSafeMetadata();
_singleMetadata = new(ownerType, metadata); _singleMetadata = new(hostType, metadata);
} }
/// <summary> /// <summary>
@ -255,6 +257,7 @@ namespace Avalonia
var result = new StyledProperty<TValue>( var result = new StyledProperty<TValue>(
name, name,
typeof(TOwner), typeof(TOwner),
typeof(TOwner),
metadata, metadata,
inherits, inherits,
validate); validate);
@ -301,6 +304,7 @@ namespace Avalonia
var result = new StyledProperty<TValue>( var result = new StyledProperty<TValue>(
name, name,
typeof(TOwner), typeof(TOwner),
typeof(TOwner),
metadata, metadata,
inherits, inherits,
validate, validate,
@ -338,7 +342,7 @@ namespace Avalonia
defaultBindingMode: defaultBindingMode, defaultBindingMode: defaultBindingMode,
coerce: coerce); coerce: coerce);
var result = new AttachedProperty<TValue>(name, typeof(TOwner), metadata, inherits, validate); var result = new AttachedProperty<TValue>(name, typeof(TOwner), typeof(THost), metadata, inherits, validate);
var registry = AvaloniaPropertyRegistry.Instance; var registry = AvaloniaPropertyRegistry.Instance;
registry.Register(typeof(TOwner), result); registry.Register(typeof(TOwner), result);
registry.RegisterAttached(typeof(THost), result); registry.RegisterAttached(typeof(THost), result);
@ -375,7 +379,7 @@ namespace Avalonia
defaultBindingMode: defaultBindingMode, defaultBindingMode: defaultBindingMode,
coerce: coerce); coerce: coerce);
var result = new AttachedProperty<TValue>(name, ownerType, metadata, inherits, validate); var result = new AttachedProperty<TValue>(name, ownerType, typeof(THost), metadata, inherits, validate);
var registry = AvaloniaPropertyRegistry.Instance; var registry = AvaloniaPropertyRegistry.Instance;
registry.Register(ownerType, result); registry.Register(ownerType, result);
registry.RegisterAttached(typeof(THost), result); registry.RegisterAttached(typeof(THost), result);

4
src/Avalonia.Base/AvaloniaProperty`1.cs

@ -19,14 +19,16 @@ namespace Avalonia
/// </summary> /// </summary>
/// <param name="name">The name of the property.</param> /// <param name="name">The name of the property.</param>
/// <param name="ownerType">The type of the class that registers the property.</param> /// <param name="ownerType">The type of the class that registers the property.</param>
/// <param name="hostType">The class that the property being is registered on.</param>
/// <param name="metadata">The property metadata.</param> /// <param name="metadata">The property metadata.</param>
/// <param name="notifying">A <see cref="AvaloniaProperty.Notifying"/> callback.</param> /// <param name="notifying">A <see cref="AvaloniaProperty.Notifying"/> callback.</param>
protected AvaloniaProperty( protected AvaloniaProperty(
string name, string name,
Type ownerType, Type ownerType,
Type hostType,
AvaloniaPropertyMetadata metadata, AvaloniaPropertyMetadata metadata,
Action<AvaloniaObject, bool>? notifying = null) Action<AvaloniaObject, bool>? notifying = null)
: base(name, typeof(TValue), ownerType, metadata, notifying) : base(name, typeof(TValue), ownerType, hostType, metadata, notifying)
{ {
_changed = new LightweightSubject<AvaloniaPropertyChangedEventArgs<TValue>>(); _changed = new LightweightSubject<AvaloniaPropertyChangedEventArgs<TValue>>();
} }

2
src/Avalonia.Base/DirectPropertyBase.cs

@ -24,7 +24,7 @@ namespace Avalonia
string name, string name,
Type ownerType, Type ownerType,
AvaloniaPropertyMetadata metadata) AvaloniaPropertyMetadata metadata)
: base(name, ownerType, metadata) : base(name, ownerType, ownerType, metadata)
{ {
Owner = ownerType; Owner = ownerType;
} }

4
src/Avalonia.Base/StyledProperty.cs

@ -16,6 +16,7 @@ namespace Avalonia
/// </summary> /// </summary>
/// <param name="name">The name of the property.</param> /// <param name="name">The name of the property.</param>
/// <param name="ownerType">The type of the class that registers the property.</param> /// <param name="ownerType">The type of the class that registers the property.</param>
/// <param name="hostType">The class that the property being is registered on.</param>
/// <param name="metadata">The property metadata.</param> /// <param name="metadata">The property metadata.</param>
/// <param name="inherits">Whether the property inherits its value.</param> /// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="validate"> /// <param name="validate">
@ -26,11 +27,12 @@ namespace Avalonia
public StyledProperty( public StyledProperty(
string name, string name,
Type ownerType, Type ownerType,
Type hostType,
StyledPropertyMetadata<TValue> metadata, StyledPropertyMetadata<TValue> metadata,
bool inherits = false, bool inherits = false,
Func<TValue, bool>? validate = null, Func<TValue, bool>? validate = null,
Action<AvaloniaObject, bool>? notifying = null) Action<AvaloniaObject, bool>? notifying = null)
: base(name, ownerType, metadata, notifying) : base(name, ownerType, hostType, metadata, notifying)
{ {
Inherits = inherits; Inherits = inherits;
ValidateValue = validate; ValidateValue = validate;

2
tests/Avalonia.Base.UnitTests/AttachedPropertyTests.cs

@ -1,3 +1,4 @@
using Avalonia.Controls;
using Xunit; using Xunit;
namespace Avalonia.Base.UnitTests namespace Avalonia.Base.UnitTests
@ -10,6 +11,7 @@ namespace Avalonia.Base.UnitTests
var property = new AttachedProperty<string>( var property = new AttachedProperty<string>(
"Foo", "Foo",
typeof(Class1), typeof(Class1),
typeof(Control),
new StyledPropertyMetadata<string>()); new StyledPropertyMetadata<string>());
Assert.True(property.IsAttached); Assert.True(property.IsAttached);

17
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs

@ -31,6 +31,16 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(100, target.GetValue(Class1.AttachedProperty)); Assert.Equal(100, target.GetValue(Class1.AttachedProperty));
} }
[Fact]
public void Coerces_Set_Value_Attached_On_Class_Not_Derived_From_Owner()
{
var target = new Class2();
target.SetValue(Class1.AttachedProperty, 150);
Assert.Equal(100, target.GetValue(Class1.AttachedProperty));
}
[Fact] [Fact]
public void Coerces_Bound_Value() public void Coerces_Bound_Value()
{ {
@ -301,7 +311,7 @@ namespace Avalonia.Base.UnitTests
coerce: CoerceFoo); coerce: CoerceFoo);
public static readonly AttachedProperty<int> AttachedProperty = public static readonly AttachedProperty<int> AttachedProperty =
AvaloniaProperty.RegisterAttached<Class1, Class1, int>( AvaloniaProperty.RegisterAttached<Class1, AvaloniaObject, int>(
"Attached", "Attached",
defaultValue: 11, defaultValue: 11,
coerce: CoerceFoo); coerce: CoerceFoo);
@ -332,8 +342,9 @@ namespace Avalonia.Base.UnitTests
public static int CoerceFoo(AvaloniaObject instance, int value) public static int CoerceFoo(AvaloniaObject instance, int value)
{ {
var o = (Class1)instance; return instance is Class1 o ?
return Math.Clamp(value, o.MinFoo, o.MaxFoo); Math.Clamp(value, o.MinFoo, o.MaxFoo) :
Math.Clamp(value, 0, 100);
} }
protected override void OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change)

1
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs

@ -15,6 +15,7 @@ namespace Avalonia.Base.UnitTests
new StyledProperty<int>( new StyledProperty<int>(
"BadDefault", "BadDefault",
typeof(Class1), typeof(Class1),
typeof(Class1),
new StyledPropertyMetadata<int>(101), new StyledPropertyMetadata<int>(101),
validate: Class1.ValidateFoo)); validate: Class1.ValidateFoo));
} }

2
tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs

@ -22,7 +22,7 @@ namespace Avalonia.Base.UnitTests
{ {
var registry = new AvaloniaPropertyRegistry(); var registry = new AvaloniaPropertyRegistry();
var metadata = new StyledPropertyMetadata<int>(); var metadata = new StyledPropertyMetadata<int>();
var property = new AttachedProperty<int>("test", typeof(object), metadata, true); var property = new AttachedProperty<int>("test", typeof(object), typeof(object), metadata, true);
registry.Register(typeof(object), property); registry.Register(typeof(object), property);
registry.RegisterAttached(typeof(AvaloniaPropertyRegistryTests), property); registry.RegisterAttached(typeof(AvaloniaPropertyRegistryTests), property);
property.AddOwner<Class4>(); property.AddOwner<Class4>();

2
tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

@ -157,7 +157,7 @@ namespace Avalonia.Base.UnitTests
private class TestProperty<TValue> : AvaloniaProperty<TValue> private class TestProperty<TValue> : AvaloniaProperty<TValue>
{ {
public TestProperty(string name, Type ownerType, TestMetadata metadata = null) public TestProperty(string name, Type ownerType, TestMetadata metadata = null)
: base(name, ownerType, metadata ?? new TestMetadata()) : base(name, ownerType, ownerType, metadata ?? new TestMetadata())
{ {
} }

4
tests/Avalonia.Base.UnitTests/StyledPropertyTests.cs

@ -9,7 +9,8 @@ namespace Avalonia.Base.UnitTests
{ {
var p1 = new StyledProperty<string>( var p1 = new StyledProperty<string>(
"p1", "p1",
typeof(Class1), typeof(Class1),
typeof(Class1),
new StyledPropertyMetadata<string>()); new StyledPropertyMetadata<string>());
var p2 = p1.AddOwner<Class2>(); var p2 = p1.AddOwner<Class2>();
@ -24,6 +25,7 @@ namespace Avalonia.Base.UnitTests
var p1 = new StyledProperty<string>( var p1 = new StyledProperty<string>(
"p1", "p1",
typeof(Class1), typeof(Class1),
typeof(Class1),
new StyledPropertyMetadata<string>()); new StyledPropertyMetadata<string>());
var p2 = p1.AddOwner<Class2>(); var p2 = p1.AddOwner<Class2>();

1
tests/Avalonia.Base.UnitTests/Utilities/AvaloniaPropertyDictionaryTests.cs

@ -18,6 +18,7 @@ namespace Avalonia.Base.UnitTests.Utilities
TestProperties[i] = new StyledProperty<string>( TestProperties[i] = new StyledProperty<string>(
$"Test{i}", $"Test{i}",
typeof(AvaloniaPropertyDictionaryTests), typeof(AvaloniaPropertyDictionaryTests),
typeof(AvaloniaPropertyDictionaryTests),
new StyledPropertyMetadata<string>()); new StyledPropertyMetadata<string>());
} }

2
tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs

@ -172,7 +172,7 @@ internal sealed class AvaloniaPropertyValueStoreOld<TValue>
internal class MockProperty : StyledProperty<int> internal class MockProperty : StyledProperty<int>
{ {
public MockProperty(string name) : base(name, typeof(object), new StyledPropertyMetadata<int>()) public MockProperty(string name) : base(name, typeof(object), typeof(object), new StyledPropertyMetadata<int>())
{ {
} }
} }

Loading…
Cancel
Save