diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs index 3726fb7ae5..cc6f9e465c 100644 --- a/src/Avalonia.Base/PriorityValue.cs +++ b/src/Avalonia.Base/PriorityValue.cs @@ -30,6 +30,7 @@ namespace Avalonia private readonly SingleOrDictionary _levels = new SingleOrDictionary(); private object _value; private readonly Func _validate; + private static readonly DelayedSetter delayedSetter = new DelayedSetter(); /// /// Initializes a new instance of the class. @@ -234,51 +235,67 @@ namespace Avalonia /// The priority level that the value came from. private void UpdateValue(object value, int priority) { - var notification = value as BindingNotification; - object castValue; - - if (notification != null) - { - value = (notification.HasValue) ? notification.Value : null; - } - - if (TypeUtilities.TryConvertImplicit(_valueType, value, out castValue)) + if (!delayedSetter.IsNotifying(this)) { - var old = _value; + var notification = value as BindingNotification; + object castValue; - if (_validate != null && castValue != AvaloniaProperty.UnsetValue) + if (notification != null) { - castValue = _validate(castValue); + value = (notification.HasValue) ? notification.Value : null; } - ValuePriority = priority; - _value = castValue; - - if (notification?.HasValue == true) + if (!object.Equals(value, _value) && TypeUtilities.TryConvertImplicit(_valueType, value, out castValue)) { - notification.SetValue(castValue); - } + var old = _value; - if (notification == null || notification.HasValue) - { - Owner?.Changed(this, old, _value); - } + if (_validate != null && castValue != AvaloniaProperty.UnsetValue) + { + castValue = _validate(castValue); + } - if (notification != null) - { - Owner?.BindingNotificationReceived(this, notification); + ValuePriority = priority; + _value = castValue; + + if (notification?.HasValue == true) + { + notification.SetValue(castValue); + } + + if (notification == null || notification.HasValue) + { + using (delayedSetter.MarkNotifying(this)) + { + Owner?.Changed(this, old, _value); + } + + if (delayedSetter.HasPendingSet(this)) + { + var pendingSet = delayedSetter.GetFirstPendingSet(this); + UpdateValue(pendingSet.value, pendingSet.priority); + } + } + + if (notification != null) + { + Owner?.BindingNotificationReceived(this, notification); + } } + else + { + Logger.Error( + LogArea.Binding, + Owner, + "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})", + Property.Name, + _valueType, + value, + value?.GetType()); + } } else { - Logger.Error( - LogArea.Binding, - Owner, - "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})", - Property.Name, - _valueType, - value, - value?.GetType()); + delayedSetter.AddPendingSet(this, (value, priority)); } } } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index ff37509847..b266b44cba 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -428,7 +428,7 @@ namespace Avalonia.Base.UnitTests //here in real life stack overflow exception is thrown issue #855 and #824 target.DoubleValue = 51.001; - Assert.Equal(2, viewModel.SetterInvokedCount); + Assert.Equal(3, viewModel.SetterInvokedCount); double expected = 51;