diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 60e7b2aeef..42761ab92e 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -462,11 +462,7 @@ namespace Avalonia
/// Coerces the specified .
///
/// The property.
- public void CoerceValue(AvaloniaProperty property)
- {
- throw new NotImplementedException();
- ////_values?.CoerceValue(property);
- }
+ public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property);
///
internal void AddInheritanceChild(AvaloniaObject child)
diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs
index 27d3f319d5..9ed4c67fd6 100644
--- a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs
+++ b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs
@@ -103,6 +103,13 @@ namespace Avalonia.PropertyStore
EffectiveValue? oldValue,
EffectiveValue? newValue);
+ ///
+ /// Coerces the property value.
+ ///
+ /// The associated value store.
+ /// The property to coerce.
+ public abstract void CoerceValue(ValueStore owner, AvaloniaProperty property);
+
///
/// Disposes the effective value, raising
/// where necessary.
diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
index 2b3865d23d..d9368b57da 100644
--- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
+++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
@@ -15,12 +16,31 @@ namespace Avalonia.PropertyStore
internal sealed class EffectiveValue : EffectiveValue
{
private T? _baseValue;
+ private UncommonFields? _uncommon;
- public EffectiveValue(T value, BindingPriority priority)
+ public EffectiveValue(
+ AvaloniaObject owner,
+ StyledPropertyBase property,
+ T value,
+ BindingPriority priority)
{
Value = value;
Priority = priority;
+ if (property.HasCoercion &&
+ property.GetMetadata(owner.GetType()) is { } metadata &&
+ metadata.CoerceValue is { } coerce)
+ {
+ _uncommon = new()
+ {
+ _coerce = coerce,
+ _uncoercedValue = value,
+ _uncoercedBaseValue = value,
+ };
+
+ value = coerce(owner, value);
+ }
+
if (priority >= BindingPriority.LocalValue && priority < BindingPriority.Inherited)
{
_baseValue = value;
@@ -88,19 +108,27 @@ namespace Avalonia.PropertyStore
var oldValue = Value;
var valueChanged = false;
var baseValueChanged = false;
+ var v = value;
+
+ if (_uncommon?._coerce is { } coerce)
+ v = coerce(owner.Owner, value);
if (priority <= Priority)
{
- valueChanged = !EqualityComparer.Default.Equals(Value, value);
- Value = value;
+ valueChanged = !EqualityComparer.Default.Equals(Value, v);
+ Value = v;
Priority = priority;
+ if (_uncommon is not null)
+ _uncommon._uncoercedValue = value;
}
if (priority <= BasePriority && priority >= BindingPriority.LocalValue)
{
- baseValueChanged = !EqualityComparer.Default.Equals(_baseValue, value);
- _baseValue = value;
+ baseValueChanged = !EqualityComparer.Default.Equals(_baseValue, v);
+ _baseValue = v;
BasePriority = priority;
+ if (_uncommon is not null)
+ _uncommon._uncoercedBaseValue = value;
}
if (valueChanged)
@@ -135,22 +163,36 @@ namespace Avalonia.PropertyStore
{
Debug.Assert(priority < BindingPriority.Inherited);
Debug.Assert(basePriority > BindingPriority.Animation);
+ Debug.Assert(priority <= basePriority);
var oldValue = Value;
var valueChanged = false;
var baseValueChanged = false;
+ var v = value;
+ var bv = baseValue;
- if (!EqualityComparer.Default.Equals(Value, value))
+ if (_uncommon?._coerce is { } coerce)
{
- Value = value;
+ v = coerce(owner.Owner, value);
+ bv = coerce(owner.Owner, baseValue);
+ }
+
+ if (priority != BindingPriority.Unset && !EqualityComparer.Default.Equals(Value, v))
+ {
+ Value = v;
valueChanged = true;
+ if (_uncommon is not null)
+ _uncommon._uncoercedValue = value;
}
- if (BasePriority == BindingPriority.Unset ||
- !EqualityComparer.Default.Equals(_baseValue, baseValue))
+ if (priority != BindingPriority.Unset &&
+ (BasePriority == BindingPriority.Unset ||
+ !EqualityComparer.Default.Equals(_baseValue, bv)))
{
- _baseValue = value;
+ _baseValue = v;
baseValueChanged = true;
+ if (_uncommon is not null)
+ _uncommon._uncoercedValue = baseValue;
}
Priority = priority;
@@ -193,6 +235,19 @@ namespace Avalonia.PropertyStore
}
}
+ public override void CoerceValue(ValueStore owner, AvaloniaProperty property)
+ {
+ if (_uncommon is null)
+ return;
+ SetAndRaise(
+ owner,
+ (StyledPropertyBase)property,
+ _uncommon._uncoercedValue!,
+ Priority,
+ _uncommon._uncoercedBaseValue!,
+ BasePriority);
+ }
+
public override void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property)
{
DisposeAndRaiseUnset(owner, (StyledPropertyBase)property);
@@ -216,5 +271,12 @@ namespace Avalonia.PropertyStore
{
return BasePriority != BindingPriority.Unset ? _baseValue : AvaloniaProperty.UnsetValue;
}
+
+ private class UncommonFields
+ {
+ public Func? _coerce;
+ public T? _uncoercedValue;
+ public T? _uncoercedBaseValue;
+ }
}
}
diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs
index aa9d7525a3..b5db37ca20 100644
--- a/src/Avalonia.Base/PropertyStore/ValueStore.cs
+++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs
@@ -189,6 +189,12 @@ namespace Avalonia.PropertyStore
return false;
}
+ public void CoerceValue(AvaloniaProperty property)
+ {
+ if (_effectiveValues is not null && _effectiveValues.TryGetValue(property, out var v))
+ v.CoerceValue(this, property);
+ }
+
public Optional GetBaseValue(StyledPropertyBase property)
{
if (TryGetEffectiveValue(property, out var v) &&
@@ -628,7 +634,7 @@ namespace Avalonia.PropertyStore
{
Debug.Assert(priority < BindingPriority.Inherited);
var defaultValue = property.GetDefaultValue(Owner.GetType());
- var effectiveValue = new EffectiveValue(defaultValue, BindingPriority.Unset);
+ var effectiveValue = new EffectiveValue(Owner, property, defaultValue, BindingPriority.Unset);
AddEffectiveValue(property, effectiveValue);
effectiveValue.SetAndRaise(this, property, value, priority);
}
diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs
index eed9df71e0..2dcd468f15 100644
--- a/src/Avalonia.Base/StyledPropertyBase.cs
+++ b/src/Avalonia.Base/StyledPropertyBase.cs
@@ -183,7 +183,7 @@ namespace Avalonia
internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o)
{
- return new EffectiveValue(GetDefaultValue(o.GetType()), BindingPriority.Unset);
+ return new EffectiveValue(o, this, GetDefaultValue(o.GetType()), BindingPriority.Unset);
}
///
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs
index e2e87d4dfa..7a35fc89b7 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs
@@ -73,7 +73,7 @@ namespace Avalonia.Base.UnitTests
var source1 = new Subject>();
var source2 = new Subject>();
- target.Bind(Class1.FooProperty, source1);
+ target.Bind(Class1.FooProperty, source1, BindingPriority.Style);
source1.OnNext(150);
target.Bind(Class1.FooProperty, source2);