From b5ee3077bcc7f289af825dc1b73ceece8ee94728 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 23 Oct 2017 16:32:37 -0500 Subject: [PATCH] Fix direct properties by delaying setting values until after any currently running property changed notifications for this property finish running. --- src/Avalonia.Base/AvaloniaObject.cs | 27 +++++++-- src/Avalonia.Base/DelayedSetter.cs | 56 +++++++++++++++++++ .../AvaloniaObjectTests_Direct.cs | 2 +- 3 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 src/Avalonia.Base/DelayedSetter.cs diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index efcbb57244..77d6c20608 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -510,6 +510,7 @@ namespace Avalonia { Contract.Requires(property != null); VerifyAccess(); + delayedSetter.SetNotifying(property, true); AvaloniaPropertyChangedEventArgs e = new AvaloniaPropertyChangedEventArgs( this, @@ -536,9 +537,12 @@ namespace Avalonia finally { property.Notifying?.Invoke(this, false); + delayedSetter.SetNotifying(property, false); } } + private DelayedSetter delayedSetter = new DelayedSetter(); + /// /// Sets the backing field for a direct avalonia property, raising the /// event if the value has changed. @@ -553,15 +557,28 @@ namespace Avalonia protected bool SetAndRaise(AvaloniaProperty property, ref T field, T value) { VerifyAccess(); - if (!object.Equals(field, value)) + if (!delayedSetter.IsNotifying(property)) { - var old = field; - field = value; - RaisePropertyChanged(property, old, value, BindingPriority.LocalValue); - return true; + if (!object.Equals(field, value)) + { + var old = field; + field = value; + RaisePropertyChanged(property, old, value, BindingPriority.LocalValue); + + if (delayedSetter.HasPendingSet(property)) + { + SetAndRaise(property, ref field, (T)delayedSetter.GetFirstPendingSet(property)); + } + return true; + } + else + { + return false; + } } else { + delayedSetter.RecordPendingSet(property, value); return false; } } diff --git a/src/Avalonia.Base/DelayedSetter.cs b/src/Avalonia.Base/DelayedSetter.cs new file mode 100644 index 0000000000..df0a8b85f4 --- /dev/null +++ b/src/Avalonia.Base/DelayedSetter.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Avalonia +{ + class DelayedSetter + { + private class SettingStatus + { + public bool Notifying { get; set; } + + private Queue pendingValues; + + public Queue PendingValues + { + get + { + return pendingValues ?? (pendingValues = new Queue()); + } + } + } + + private readonly Dictionary setRecords = new Dictionary(); + + public void SetNotifying(T property, bool notifying) + { + if (!setRecords.ContainsKey(property)) + { + setRecords[property] = new SettingStatus(); + } + setRecords[property].Notifying = notifying; + } + + public bool IsNotifying(T property) => setRecords.TryGetValue(property, out var value) && value.Notifying; + + public void RecordPendingSet(T property, object value) + { + if (!setRecords.ContainsKey(property)) + { + setRecords[property] = new SettingStatus(); + } + setRecords[property].PendingValues.Enqueue(value); + } + + public bool HasPendingSet(T property) + { + return setRecords.ContainsKey(property) && setRecords[property].PendingValues.Count != 0; + } + + public object GetFirstPendingSet(T property) + { + return setRecords[property].PendingValues.Dequeue(); + } + } +} diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index d20ec32893..4123e229dd 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -485,7 +485,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;