diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 85dccd53cb..a3d5803ab0 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -528,7 +528,7 @@ namespace Avalonia return false; } - DeferredSetter setter = Values.GetDeferredSetter(property); + DeferredSetter setter = Values.GetDirectDeferredSetter(property); return setter.SetAndNotify(this, property, ref field, value); } diff --git a/src/Avalonia.Base/IPriorityValueOwner.cs b/src/Avalonia.Base/IPriorityValueOwner.cs index 540b1bf19b..1d6e5e59ad 100644 --- a/src/Avalonia.Base/IPriorityValueOwner.cs +++ b/src/Avalonia.Base/IPriorityValueOwner.cs @@ -29,6 +29,13 @@ namespace Avalonia /// The notification. void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification); + /// + /// Returns deferred setter for given non-direct property. + /// + /// Property. + /// Deferred setter for given property. + DeferredSetter GetNonDirectDeferredSetter(AvaloniaProperty property); + /// /// Logs a binding error. /// @@ -40,7 +47,5 @@ namespace Avalonia /// Ensures that the current thread is the UI thread. /// void VerifyAccess(); - - DeferredSetter Setter { get; } } } diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs index 89a893577f..4996420fe7 100644 --- a/src/Avalonia.Base/PriorityValue.cs +++ b/src/Avalonia.Base/PriorityValue.cs @@ -30,7 +30,9 @@ namespace Avalonia private readonly SingleOrDictionary _levels = new SingleOrDictionary(); private readonly Func _validate; + private readonly SetAndNotifyCallback<(object, int)> _setAndNotifyCallback; private (object value, int priority) _value; + private DeferredSetter _setter; /// /// Initializes a new instance of the class. @@ -50,6 +52,7 @@ namespace Avalonia _valueType = valueType; _value = (AvaloniaProperty.UnsetValue, int.MaxValue); _validate = validate; + _setAndNotifyCallback = SetAndNotify; } /// @@ -242,22 +245,22 @@ namespace Avalonia /// The priority level that the value came from. private void UpdateValue(object value, int priority) { - Owner.Setter.SetAndNotify(Property, - ref _value, - UpdateCore, - (value, priority)); - } + var newValue = (value, priority); + + if (newValue == _value) + { + return; + } - private bool UpdateCore( - object update, - ref (object value, int priority) backing, - Action notify) - => UpdateCore(((object, int))update, ref backing, notify); + if (_setter == null) + { + _setter = Owner.GetNonDirectDeferredSetter(Property); + } + + _setter.SetAndNotifyCallback(Property, _setAndNotifyCallback, ref _value, newValue); + } - private bool UpdateCore( - (object value, int priority) update, - ref (object value, int priority) backing, - Action notify) + private void SetAndNotify(AvaloniaProperty property, ref (object value, int priority) backing, (object value, int priority) update) { var val = update.value; var notification = val as BindingNotification; @@ -286,7 +289,7 @@ namespace Avalonia if (notification == null || notification.HasValue) { - notify(() => Owner?.Changed(Property, ValuePriority, old, Value)); + Owner?.Changed(Property, ValuePriority, old, Value); } if (notification != null) @@ -305,7 +308,6 @@ namespace Avalonia val, val?.GetType()); } - return true; } } } diff --git a/src/Avalonia.Base/Utilities/DeferredSetter.cs b/src/Avalonia.Base/Utilities/DeferredSetter.cs index 54458d6e6a..fd7a66fb52 100644 --- a/src/Avalonia.Base/Utilities/DeferredSetter.cs +++ b/src/Avalonia.Base/Utilities/DeferredSetter.cs @@ -5,6 +5,15 @@ using System; namespace Avalonia.Utilities { + /// + /// Callback invoked when deferred setter wants to set a value. + /// + /// Value type. + /// Property being set. + /// Backing field reference. + /// New value. + internal delegate void SetAndNotifyCallback(AvaloniaProperty property, ref TValue backing, TValue value); + /// /// A utility class to enable deferring assignment until after property-changed notifications are sent. /// Used to fix #855. @@ -61,6 +70,35 @@ namespace Avalonia.Utilities return false; } + public bool SetAndNotifyCallback(AvaloniaProperty property, SetAndNotifyCallback setAndNotifyCallback, ref TValue backing, TValue value) + where TValue : TSetRecord + { + if (!_isNotifying) + { + using (new NotifyDisposable(this)) + { + setAndNotifyCallback(property, ref backing, value); + } + + if (!_pendingValues.Empty) + { + using (new NotifyDisposable(this)) + { + while (!_pendingValues.Empty) + { + setAndNotifyCallback(property, ref backing, (TValue) _pendingValues.Dequeue()); + } + } + } + + return true; + } + + _pendingValues.Enqueue(value); + + return false; + } + /// /// Disposable that marks the property as currently notifying. /// When disposed, marks the property as done notifying. diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs index 8dfabc71a2..1bdbd4ca7c 100644 --- a/src/Avalonia.Base/ValueStore.cs +++ b/src/Avalonia.Base/ValueStore.cs @@ -178,7 +178,7 @@ namespace Avalonia return value; } - public DeferredSetter GetDeferredSetter(AvaloniaProperty property) + private DeferredSetter GetDeferredSetter(AvaloniaProperty property) { if (_deferredSetters.TryGetValue(property, out var deferredSetter)) { @@ -192,15 +192,14 @@ namespace Avalonia return newDeferredSetter; } - private DeferredSetter _deferredSetter; + public DeferredSetter GetNonDirectDeferredSetter(AvaloniaProperty property) + { + return GetDeferredSetter(property); + } - public DeferredSetter Setter + public DeferredSetter GetDirectDeferredSetter(AvaloniaProperty property) { - get - { - return _deferredSetter ?? - (_deferredSetter = new DeferredSetter()); - } + return GetDeferredSetter(property); } } } diff --git a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs b/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs index 2f1b7862a7..63e1790cce 100644 --- a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs +++ b/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs @@ -307,7 +307,7 @@ namespace Avalonia.Base.UnitTests private static Mock GetMockOwner() { var owner = new Mock(); - owner.SetupGet(o => o.Setter).Returns(new DeferredSetter()); + owner.Setup(o => o.GetNonDirectDeferredSetter(It.IsAny())).Returns(new DeferredSetter()); return owner; } }