diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index bf1624eab3..f3a046ef80 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -776,19 +776,19 @@ namespace Avalonia
break;
}
- UpdateDataValidationCore(property, value.Type, value.Error);
- }
+ var metadata = property.GetMetadata(GetType());
- internal void UpdateDataValidationCore(AvaloniaProperty property,
- BindingValueType state,
- Exception? error)
- {
- if (property.GetMetadata(GetType()) is { EnableDataValidation: true })
+ if (metadata.EnableDataValidation == true)
{
- UpdateDataValidation(property, state, error);
+ UpdateDataValidation(property, value.Type, value.Error);
}
}
+ internal void OnUpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error)
+ {
+ UpdateDataValidation(property, state, error);
+ }
+
///
/// Gets a description of an observable that van be used in logs.
///
diff --git a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs
index 5908d9e535..24eb00b2fe 100644
--- a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs
+++ b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs
@@ -9,6 +9,7 @@ namespace Avalonia.PropertyStore
IDisposable
{
private readonly ValueStore _owner;
+ private readonly bool _hasDataValidation;
private IDisposable? _subscription;
private T? _defaultValue;
private bool _isDefaultValueInitialized;
@@ -17,6 +18,7 @@ namespace Avalonia.PropertyStore
{
_owner = owner;
Property = property;
+ _hasDataValidation = property.GetMetadata(owner.Owner.GetType()).EnableDataValidation ?? false;
}
public StyledProperty Property { get;}
@@ -51,7 +53,10 @@ namespace Avalonia.PropertyStore
if (property.ValidateValue?.Invoke(value) == false)
value = instance.GetCachedDefaultValue();
- owner.SetValue(property, value, BindingPriority.LocalValue);
+ owner.SetLocalValue(property, value);
+
+ if (instance._hasDataValidation)
+ owner.Owner.OnUpdateDataValidation(property, BindingValueType.Value, null);
}
if (Dispatcher.UIThread.CheckAccess())
@@ -74,23 +79,23 @@ namespace Avalonia.PropertyStore
{
var owner = instance._owner;
var property = instance.Property;
+ var originalType = value.Type;
LoggingUtils.LogIfNecessary(owner.Owner, property, value);
+ // Revert to the default value if the binding value fails validation, or if
+ // there was no value (though not if there was a data validation error).
+ if ((value.HasValue && property.ValidateValue?.Invoke(value.Value) == false) ||
+ (!value.HasValue && value.Type != BindingValueType.DataValidationError))
+ value = value.WithValue(instance.GetCachedDefaultValue());
+
if (value.HasValue)
- {
- var effectiveValue = value.Value;
- if (property.ValidateValue?.Invoke(effectiveValue) == false)
- effectiveValue = instance.GetCachedDefaultValue();
- owner.SetValue(property, effectiveValue, BindingPriority.LocalValue);
- }
- else
- {
- owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue);
- }
+ owner.SetLocalValue(property, value.Value);
+ if (instance._hasDataValidation)
+ owner.Owner.OnUpdateDataValidation(property, originalType, value.Error);
}
- if (value.Type is BindingValueType.DoNothing or BindingValueType.DataValidationError)
+ if (value.Type is BindingValueType.DoNothing)
return;
if (Dispatcher.UIThread.CheckAccess())
diff --git a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs
index 46e6ed810a..cda11faa1a 100644
--- a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs
+++ b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
using Avalonia.Threading;
@@ -8,6 +9,7 @@ namespace Avalonia.PropertyStore
IDisposable
{
private readonly ValueStore _owner;
+ private readonly bool _hasDataValidation;
private IDisposable? _subscription;
private T? _defaultValue;
private bool _isDefaultValueInitialized;
@@ -16,6 +18,7 @@ namespace Avalonia.PropertyStore
{
_owner = owner;
Property = property;
+ _hasDataValidation = property.GetMetadata(owner.Owner.GetType()).EnableDataValidation ?? false;
}
public StyledProperty Property { get; }
@@ -35,32 +38,28 @@ namespace Avalonia.PropertyStore
public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this);
public void OnError(Exception error) => OnCompleted();
+ [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
public void OnNext(object? value)
{
- static void Execute(LocalValueUntypedBindingObserver instance, object? value)
+ static void Execute(LocalValueUntypedBindingObserver instance, object? untypedValue)
{
var owner = instance._owner;
var property = instance.Property;
+ var value = BindingValue.FromUntyped(untypedValue, property.PropertyType);
+ var originalType = value.Type;
- if (value is BindingNotification n)
- {
- value = n.Value;
- LoggingUtils.LogIfNecessary(owner.Owner, property, n);
- }
+ LoggingUtils.LogIfNecessary(owner.Owner, property, value);
- if (value == AvaloniaProperty.UnsetValue)
- {
- owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue);
- }
- else if (UntypedValueUtils.TryConvertAndValidate(property, value, out var typedValue))
- {
- owner.SetValue(property, typedValue, BindingPriority.LocalValue);
- }
- else
- {
- owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue);
- LoggingUtils.LogInvalidValue(owner.Owner, property, typeof(T), value);
- }
+ // Revert to the default value if the binding value fails validation, or if
+ // there was no value (though not if there was a data validation error).
+ if ((value.HasValue && property.ValidateValue?.Invoke(value.Value) == false) ||
+ (!value.HasValue && value.Type != BindingValueType.DataValidationError))
+ value = value.WithValue(instance.GetCachedDefaultValue());
+
+ if (value.HasValue)
+ owner.SetLocalValue(property, value.Value);
+ if (instance._hasDataValidation)
+ owner.Owner.OnUpdateDataValidation(property, originalType, value.Error);
}
if (value == BindingOperations.DoNothing)
diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs
index ec6ed392c1..9efc91d44d 100644
--- a/src/Avalonia.Base/PropertyStore/ValueStore.cs
+++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs
@@ -193,18 +193,7 @@ namespace Avalonia.PropertyStore
}
else
{
- if (TryGetEffectiveValue(property, out var existing))
- {
- var effective = (EffectiveValue)existing;
- effective.SetLocalValueAndRaise(this, property, value);
- }
- else
- {
- var effectiveValue = CreateEffectiveValue(property);
- AddEffectiveValue(property, effectiveValue);
- effectiveValue.SetLocalValueAndRaise(this, property, value);
- }
-
+ SetLocalValue(property, value);
return null;
}
}
@@ -223,6 +212,21 @@ namespace Avalonia.PropertyStore
}
}
+ public void SetLocalValue(StyledProperty property, T value)
+ {
+ if (TryGetEffectiveValue(property, out var existing))
+ {
+ var effective = (EffectiveValue)existing;
+ effective.SetLocalValueAndRaise(this, property, value);
+ }
+ else
+ {
+ var effectiveValue = CreateEffectiveValue(property);
+ AddEffectiveValue(property, effectiveValue);
+ effectiveValue.SetLocalValueAndRaise(this, property, value);
+ }
+ }
+
public object? GetValue(AvaloniaProperty property)
{
if (_effectiveValues.TryGetValue(property, out var v))
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
index b6036bba8f..9f74d2fc08 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
@@ -888,7 +888,8 @@ namespace Avalonia.Base.UnitTests
var target = new Class1();
var source = new Subject