Added GenerateTypeSafeMetadata to property metadata
This method is used to strip coercion methods out of StyledPropertyMetadata when applying it to a new owner. Also fixed AvaloniaProperty.(IPropertyInfo.CanSet) returning true for read-only properties
This commit is contained in:
@@ -8,9 +8,9 @@
|
||||
"samples\\GpuInterop\\GpuInterop.csproj",
|
||||
"samples\\IntegrationTestApp\\IntegrationTestApp.csproj",
|
||||
"samples\\MiniMvvm\\MiniMvvm.csproj",
|
||||
"samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
|
||||
"samples\\SampleControls\\ControlSamples.csproj",
|
||||
"samples\\Sandbox\\Sandbox.csproj",
|
||||
"samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
|
||||
"src\\Avalonia.Base\\Avalonia.Base.csproj",
|
||||
"src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj",
|
||||
"src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj",
|
||||
@@ -41,6 +41,7 @@
|
||||
"src\\Windows\\Avalonia.Direct2D1\\Avalonia.Direct2D1.csproj",
|
||||
"src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj",
|
||||
"src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj",
|
||||
"src\\tools\\Avalonia.Generators\\Avalonia.Generators.csproj",
|
||||
"src\\tools\\DevAnalyzers\\DevAnalyzers.csproj",
|
||||
"src\\tools\\DevGenerators\\DevGenerators.csproj",
|
||||
"src\\tools\\PublicAnalyzers\\Avalonia.Analyzers.csproj",
|
||||
@@ -63,4 +64,4 @@
|
||||
"tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Data.Core;
|
||||
using Avalonia.PropertyStore;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Utilities;
|
||||
|
||||
namespace Avalonia
|
||||
@@ -20,12 +19,20 @@ namespace Avalonia
|
||||
public static readonly object UnsetValue = new UnsetValueType();
|
||||
|
||||
private static int s_nextId;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a metadata object for types which have no metadata of their own.
|
||||
/// </summary>
|
||||
private readonly AvaloniaPropertyMetadata _defaultMetadata;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a fast path when the property has no metadata overrides.
|
||||
/// </summary>
|
||||
private KeyValuePair<Type, AvaloniaPropertyMetadata>? _singleMetadata;
|
||||
|
||||
private readonly Dictionary<Type, AvaloniaPropertyMetadata> _metadata;
|
||||
private readonly Dictionary<Type, AvaloniaPropertyMetadata> _metadataCache = new Dictionary<Type, AvaloniaPropertyMetadata>();
|
||||
|
||||
private bool _hasMetadataOverrides;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AvaloniaProperty"/> class.
|
||||
/// </summary>
|
||||
@@ -57,7 +64,8 @@ namespace Avalonia
|
||||
Id = s_nextId++;
|
||||
|
||||
_metadata.Add(ownerType, metadata ?? throw new ArgumentNullException(nameof(metadata)));
|
||||
_defaultMetadata = metadata;
|
||||
_defaultMetadata = metadata.GenerateTypeSafeMetadata();
|
||||
_singleMetadata = new(ownerType, metadata);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -80,9 +88,6 @@ namespace Avalonia
|
||||
Id = source.Id;
|
||||
_defaultMetadata = source._defaultMetadata;
|
||||
|
||||
// Properties that have different owner can't use fast path for metadata.
|
||||
_hasMetadataOverrides = true;
|
||||
|
||||
if (metadata != null)
|
||||
{
|
||||
_metadata.Add(ownerType, metadata);
|
||||
@@ -442,33 +447,14 @@ namespace Avalonia
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property metadata for the specified type.
|
||||
/// Gets the <see cref="AvaloniaPropertyMetadata"/> which applies to this property when it is used with the specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type.</typeparam>
|
||||
/// <returns>
|
||||
/// The property metadata.
|
||||
/// </returns>
|
||||
public AvaloniaPropertyMetadata GetMetadata<T>() where T : AvaloniaObject
|
||||
{
|
||||
return GetMetadata(typeof(T));
|
||||
}
|
||||
/// <typeparam name="T">The type for which to retrieve metadata.</typeparam>
|
||||
public AvaloniaPropertyMetadata GetMetadata<T>() where T : AvaloniaObject => GetMetadata(typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property metadata for the specified type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>
|
||||
/// The property metadata.
|
||||
/// </returns>
|
||||
public AvaloniaPropertyMetadata GetMetadata(Type type)
|
||||
{
|
||||
if (!_hasMetadataOverrides)
|
||||
{
|
||||
return _defaultMetadata;
|
||||
}
|
||||
|
||||
return GetMetadataWithOverrides(type);
|
||||
}
|
||||
/// <inheritdoc cref="GetMetadata{T}"/>
|
||||
/// <param name="type">The type for which to retrieve metadata.</param>
|
||||
public AvaloniaPropertyMetadata GetMetadata(Type type) => GetMetadataWithOverrides(type);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the <paramref name="value"/> is valid for the property.
|
||||
@@ -567,7 +553,7 @@ namespace Avalonia
|
||||
_metadata.Add(type, metadata);
|
||||
_metadataCache.Clear();
|
||||
|
||||
_hasMetadataOverrides = true;
|
||||
_singleMetadata = null;
|
||||
}
|
||||
|
||||
protected abstract IObservable<AvaloniaPropertyChangedEventArgs> GetChanged();
|
||||
@@ -584,7 +570,12 @@ namespace Avalonia
|
||||
return result;
|
||||
}
|
||||
|
||||
Type? currentType = type;
|
||||
if (_singleMetadata is { } singleMetadata)
|
||||
{
|
||||
return _metadataCache[type] = singleMetadata.Key.IsAssignableFrom(type) ? singleMetadata.Value : _defaultMetadata;
|
||||
}
|
||||
|
||||
var currentType = type;
|
||||
|
||||
while (currentType != null)
|
||||
{
|
||||
@@ -598,13 +589,11 @@ namespace Avalonia
|
||||
currentType = currentType.BaseType;
|
||||
}
|
||||
|
||||
_metadataCache[type] = _defaultMetadata;
|
||||
|
||||
return _defaultMetadata;
|
||||
return _metadataCache[type] = _defaultMetadata;
|
||||
}
|
||||
|
||||
bool IPropertyInfo.CanGet => true;
|
||||
bool IPropertyInfo.CanSet => true;
|
||||
bool IPropertyInfo.CanSet => !IsReadOnly;
|
||||
object? IPropertyInfo.Get(object target) => ((AvaloniaObject)target).GetValue(this);
|
||||
void IPropertyInfo.Set(object target, object? value) => ((AvaloniaObject)target).SetValue(this, value);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Avalonia
|
||||
/// <summary>
|
||||
/// Base class for avalonia property metadata.
|
||||
/// </summary>
|
||||
public class AvaloniaPropertyMetadata
|
||||
public abstract class AvaloniaPropertyMetadata
|
||||
{
|
||||
private BindingMode _defaultBindingMode;
|
||||
|
||||
@@ -61,5 +61,13 @@ namespace Avalonia
|
||||
|
||||
EnableDataValidation ??= baseMetadata.EnableDataValidation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a copy of this object configured for use with any owner type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For example, delegates which receive the owner object should be removed.
|
||||
/// </remarks>
|
||||
public abstract AvaloniaPropertyMetadata GenerateTypeSafeMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,5 +45,7 @@ namespace Avalonia
|
||||
UnsetValue ??= src.UnsetValue;
|
||||
}
|
||||
}
|
||||
|
||||
public override AvaloniaPropertyMetadata GenerateTypeSafeMetadata() => new DirectPropertyMetadata<TValue>(UnsetValue, DefaultBindingMode, EnableDataValidation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@ namespace Avalonia
|
||||
/// <param name="ownerType">The type of the class that registers the property.</param>
|
||||
/// <param name="metadata">The property metadata.</param>
|
||||
/// <param name="inherits">Whether the property inherits its value.</param>
|
||||
/// <param name="validate">A value validation callback.</param>
|
||||
/// <param name="validate">
|
||||
/// <para>A method which returns "false" for values that are never valid for this property.</para>
|
||||
/// <para>This method is not part of the property's metadata and so cannot be changed after registration.</para>
|
||||
/// </param>
|
||||
/// <param name="notifying">A <see cref="AvaloniaProperty.Notifying"/> callback.</param>
|
||||
public StyledProperty(
|
||||
string name,
|
||||
@@ -41,7 +44,7 @@ namespace Avalonia
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value validation callback for the property.
|
||||
/// A method which returns "false" for values that are never valid for this property.
|
||||
/// </summary>
|
||||
public Func<TValue, bool>? ValidateValue { get; }
|
||||
|
||||
|
||||
@@ -58,5 +58,7 @@ namespace Avalonia
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override AvaloniaPropertyMetadata GenerateTypeSafeMetadata() => new StyledPropertyMetadata<TValue>(DefaultValue, DefaultBindingMode, enableDataValidation: EnableDataValidation ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.PropertyStore;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Utilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Avalonia.Base.UnitTests
|
||||
@@ -29,7 +27,7 @@ namespace Avalonia.Base.UnitTests
|
||||
[Fact]
|
||||
public void GetMetadata_Returns_Supplied_Value()
|
||||
{
|
||||
var metadata = new AvaloniaPropertyMetadata();
|
||||
var metadata = new TestMetadata();
|
||||
var target = new TestProperty<string>("test", typeof(Class1), metadata);
|
||||
|
||||
Assert.Same(metadata, target.GetMetadata<Class1>());
|
||||
@@ -38,26 +36,30 @@ namespace Avalonia.Base.UnitTests
|
||||
[Fact]
|
||||
public void GetMetadata_Returns_Supplied_Value_For_Derived_Class()
|
||||
{
|
||||
var metadata = new AvaloniaPropertyMetadata();
|
||||
var metadata = new TestMetadata();
|
||||
var target = new TestProperty<string>("test", typeof(Class1), metadata);
|
||||
|
||||
Assert.Same(metadata, target.GetMetadata<Class2>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMetadata_Returns_Supplied_Value_For_Unrelated_Class()
|
||||
public void GetMetadata_Returns_TypeSafe_Metadata_For_Unrelated_Class()
|
||||
{
|
||||
var metadata = new AvaloniaPropertyMetadata();
|
||||
var metadata = new TestMetadata(BindingMode.OneWayToSource, true, x => { _ = (StyledElement)x; });
|
||||
var target = new TestProperty<string>("test", typeof(Class3), metadata);
|
||||
|
||||
Assert.Same(metadata, target.GetMetadata<Class2>());
|
||||
var targetMetadata = (TestMetadata)target.GetMetadata<Class2>();
|
||||
|
||||
Assert.Equal(metadata.DefaultBindingMode, targetMetadata.DefaultBindingMode);
|
||||
Assert.Equal(metadata.EnableDataValidation, targetMetadata.EnableDataValidation);
|
||||
Assert.Equal(null, targetMetadata.OwnerSpecificAction);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMetadata_Returns_Overridden_Value()
|
||||
{
|
||||
var metadata = new AvaloniaPropertyMetadata();
|
||||
var overridden = new AvaloniaPropertyMetadata();
|
||||
var metadata = new TestMetadata();
|
||||
var overridden = new TestMetadata();
|
||||
var target = new TestProperty<string>("test", typeof(Class1), metadata);
|
||||
|
||||
target.OverrideMetadata<Class2>(overridden);
|
||||
@@ -68,9 +70,9 @@ namespace Avalonia.Base.UnitTests
|
||||
[Fact]
|
||||
public void OverrideMetadata_Should_Merge_Values()
|
||||
{
|
||||
var metadata = new AvaloniaPropertyMetadata(BindingMode.TwoWay);
|
||||
var metadata = new TestMetadata(BindingMode.TwoWay);
|
||||
var notify = (Action<AvaloniaObject, bool>)((a, b) => { });
|
||||
var overridden = new AvaloniaPropertyMetadata();
|
||||
var overridden = new TestMetadata();
|
||||
var target = new TestProperty<string>("test", typeof(Class1), metadata);
|
||||
|
||||
target.OverrideMetadata<Class2>(overridden);
|
||||
@@ -131,15 +133,31 @@ namespace Avalonia.Base.UnitTests
|
||||
[Fact]
|
||||
public void PropertyMetadata_BindingMode_Default_Returns_OneWay()
|
||||
{
|
||||
var data = new AvaloniaPropertyMetadata(defaultBindingMode: BindingMode.Default);
|
||||
var data = new TestMetadata(defaultBindingMode: BindingMode.Default);
|
||||
|
||||
Assert.Equal(BindingMode.OneWay, data.DefaultBindingMode);
|
||||
}
|
||||
|
||||
private class TestMetadata : AvaloniaPropertyMetadata
|
||||
{
|
||||
public Action<AvaloniaObject> OwnerSpecificAction { get; }
|
||||
|
||||
public TestMetadata(BindingMode defaultBindingMode = BindingMode.Default,
|
||||
bool? enableDataValidation = null,
|
||||
Action<AvaloniaObject> ownerSpecificAction = null)
|
||||
: base(defaultBindingMode, enableDataValidation)
|
||||
{
|
||||
OwnerSpecificAction = ownerSpecificAction;
|
||||
}
|
||||
|
||||
public override AvaloniaPropertyMetadata GenerateTypeSafeMetadata() =>
|
||||
new TestMetadata(DefaultBindingMode, EnableDataValidation, null);
|
||||
}
|
||||
|
||||
private class TestProperty<TValue> : AvaloniaProperty<TValue>
|
||||
{
|
||||
public TestProperty(string name, Type ownerType, AvaloniaPropertyMetadata metadata = null)
|
||||
: base(name, ownerType, metadata ?? new AvaloniaPropertyMetadata())
|
||||
public TestProperty(string name, Type ownerType, TestMetadata metadata = null)
|
||||
: base(name, ownerType, metadata ?? new TestMetadata())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user