diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs index 4a00abf1ce..2fe3844a74 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs @@ -14,23 +14,18 @@ namespace Avalonia.PropertyStore { private static readonly IDisposable s_creating = Disposable.Empty; private static readonly IDisposable s_creatingQuiet = Disposable.Create(() => { }); - private readonly bool _hasDataValidation; private IDisposable? _subscription; private bool _hasValue; private TValue? _value; - private TValue? _defaultValue; - private bool _isDefaultValueInitialized; + private UncommonFields? _uncommon; protected BindingEntryBase( AvaloniaObject target, ValueFrame frame, AvaloniaProperty property, IObservable> source) + : this(target, frame, property, (object)source) { - Frame = frame; - Source = source; - Property = property; - _hasDataValidation = property.GetMetadata(target.GetType()).EnableDataValidation ?? false; } protected BindingEntryBase( @@ -38,11 +33,21 @@ namespace Avalonia.PropertyStore ValueFrame frame, AvaloniaProperty property, IObservable source) + : this(target, frame, property, (object)source) + { + } + + private BindingEntryBase( + AvaloniaObject target, + ValueFrame frame, + AvaloniaProperty property, + object source) { Frame = frame; - Source = source; Property = property; - _hasDataValidation = property.GetMetadata(target.GetType()).EnableDataValidation ?? false; + Source = source; + if (property.GetMetadata(target.GetType()).EnableDataValidation == true) + _uncommon = new() { _hasDataValidation = true }; } public bool HasValue @@ -74,6 +79,20 @@ namespace Avalonia.PropertyStore return _value!; } + public bool GetDataValidationState(out BindingValueType state, out Exception? error) + { + if (_uncommon?._hasDataValidation == true) + { + state = _uncommon._dataValidationState; + error = _uncommon._dataValidationError; + return true; + } + + state = BindingValueType.Value; + error = null; + return false; + } + public void Start() => Start(true); public void OnCompleted() => BindingCompleted(); @@ -85,9 +104,6 @@ namespace Avalonia.PropertyStore { _subscription?.Dispose(); _subscription = null; - - if (_hasDataValidation) - Frame.Owner?.Owner.OnUpdateDataValidation(Property, BindingValueType.UnsetValue, null); } object? IValueEntry.GetValue() @@ -132,6 +148,12 @@ namespace Avalonia.PropertyStore if (!value.HasValue && value.Type != BindingValueType.DataValidationError) value = value.WithValue(instance.GetCachedDefaultValue()); + if (instance._uncommon?._hasDataValidation == true) + { + instance._uncommon._dataValidationState = value.Type; + instance._uncommon._dataValidationError = value.Error; + } + if (value.HasValue && (!instance._hasValue || !EqualityComparer.Default.Equals(instance._value, value.Value))) { @@ -140,9 +162,6 @@ namespace Avalonia.PropertyStore if (instance._subscription is not null && instance._subscription != s_creatingQuiet) instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority); } - - if (instance._hasDataValidation) - owner.OnUpdateDataValidation(property, originalType, value.Error); } if (value.Type == BindingValueType.DoNothing) @@ -170,13 +189,23 @@ namespace Avalonia.PropertyStore private TValue GetCachedDefaultValue() { - if (!_isDefaultValueInitialized) + if (_uncommon?._isDefaultValueInitialized != true) { - _defaultValue = GetDefaultValue(Frame.Owner!.Owner.GetType()); - _isDefaultValueInitialized = true; + _uncommon ??= new(); + _uncommon._defaultValue = GetDefaultValue(Frame.Owner!.Owner.GetType()); + _uncommon._isDefaultValueInitialized = true; } - return _defaultValue!; + return _uncommon._defaultValue!; + } + + private class UncommonFields + { + public TValue? _defaultValue; + public bool _isDefaultValueInitialized; + public bool _hasDataValidation; + public BindingValueType _dataValidationState; + public Exception? _dataValidationError; } } } diff --git a/src/Avalonia.Base/PropertyStore/IValueEntry.cs b/src/Avalonia.Base/PropertyStore/IValueEntry.cs index 271d85f8bc..5898bef491 100644 --- a/src/Avalonia.Base/PropertyStore/IValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/IValueEntry.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Data; namespace Avalonia.PropertyStore { @@ -22,6 +23,16 @@ namespace Avalonia.PropertyStore /// object? GetValue(); + /// + /// Gets the data validation state if supported. + /// + /// The binding validation state. + /// The current binding error, if any. + /// + /// True if the entry supports data validation, otherwise false. + /// + bool GetDataValidationState(out BindingValueType state, out Exception? error); + /// /// Called when the value entry is removed from the value store. /// diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs index d8a353dc70..16b96eff5d 100644 --- a/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs +++ b/src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Data; namespace Avalonia.PropertyStore { @@ -27,5 +28,12 @@ namespace Avalonia.PropertyStore object? IValueEntry.GetValue() => _value; T IValueEntry.GetValue() => _value; + + bool IValueEntry.GetDataValidationState(out BindingValueType state, out Exception? error) + { + state = BindingValueType.Value; + error = null; + return false; + } } } diff --git a/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs b/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs index 7a39407ba2..7604c26244 100644 --- a/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs +++ b/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Data; using Avalonia.PropertyStore; namespace Avalonia.Styling @@ -19,6 +20,13 @@ namespace Avalonia.Styling public object? GetValue() => _value ??= _template.Build(); + bool IValueEntry.GetDataValidationState(out BindingValueType state, out Exception? error) + { + state = BindingValueType.Value; + error = null; + return false; + } + void IValueEntry.Unsubscribe() { } } } diff --git a/src/Avalonia.Base/Styling/Setter.cs b/src/Avalonia.Base/Styling/Setter.cs index 5586132301..9b009be6d2 100644 --- a/src/Avalonia.Base/Styling/Setter.cs +++ b/src/Avalonia.Base/Styling/Setter.cs @@ -90,6 +90,13 @@ namespace Avalonia.Styling object? IValueEntry.GetValue() => Value; + bool IValueEntry.GetDataValidationState(out BindingValueType state, out Exception? error) + { + state = BindingValueType.Value; + error = null; + return false; + } + private AvaloniaProperty EnsureProperty() { return Property ?? throw new InvalidOperationException("Setter.Property must be set.");