diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index f0e385cf94..7b978175bd 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -561,16 +561,20 @@ namespace Avalonia VerifyAccess(); if (!directDelayedSetter.IsNotifying(property)) { + var valueChanged = false; if (!object.Equals(field, value)) { SetAndRaiseCore(property, ref field, value); - while (directDelayedSetter.HasPendingSet(property)) - { - SetAndRaiseCore(property, ref field, (T)directDelayedSetter.GetFirstPendingSet(property)); - } - return true; + valueChanged = true; + } + + while (directDelayedSetter.HasPendingSet(property)) + { + SetAndRaiseCore(property, ref field, (T)directDelayedSetter.GetFirstPendingSet(property)); + valueChanged = true; } + return valueChanged; } else if(!object.Equals(field, value)) { diff --git a/src/Avalonia.Base/Utilities/DelayedSetter.cs b/src/Avalonia.Base/Utilities/DelayedSetter.cs index a8eb7554be..4e1a39dd31 100644 --- a/src/Avalonia.Base/Utilities/DelayedSetter.cs +++ b/src/Avalonia.Base/Utilities/DelayedSetter.cs @@ -5,8 +5,16 @@ using System.Text; namespace Avalonia.Utilities { - class DelayedSetter + /// + /// A utility class to enable deferring assignment until after property-changed notifications are sent. + /// + /// The type of the object that represents the property. + /// The type of value with which to track the delayed assignment. + class DelayedSetter { + /// + /// Information on current setting/notification status of a property. + /// private class SettingStatus { public bool Notifying { get; set; } @@ -22,9 +30,14 @@ namespace Avalonia.Utilities } } - private readonly Dictionary setRecords = new Dictionary(); + private readonly Dictionary setRecords = new Dictionary(); - public IDisposable MarkNotifying(T property) + /// + /// Mark the property as currently notifying. + /// + /// The property to mark as notifying. + /// Returns a disposable that when disposed, marks the property as done notifying. + internal IDisposable MarkNotifying(TProperty property) { Contract.Requires(!IsNotifying(property)); @@ -37,10 +50,22 @@ namespace Avalonia.Utilities return Disposable.Create(() => setRecords[property].Notifying = false); } - public bool IsNotifying(T property) => setRecords.TryGetValue(property, out var value) && value.Notifying; + /// + /// Check if the property is currently notifying listeners. + /// + /// The property. + /// If the property is currently notifying listeners. + internal bool IsNotifying(TProperty property) + => setRecords.TryGetValue(property, out var value) && value.Notifying; - public void AddPendingSet(T property, TValue value) + /// + /// Add a pending assignment for the property. + /// + /// The property. + /// The value to assign. + internal void AddPendingSet(TProperty property, TValue value) { + Contract.Requires(IsNotifying(property)); if (!setRecords.ContainsKey(property)) { setRecords[property] = new SettingStatus(); @@ -48,17 +73,37 @@ namespace Avalonia.Utilities setRecords[property].PendingValues.Enqueue(value); } - public bool HasPendingSet(T property) + /// + /// Checks if there are any pending assignments for the property. + /// + /// The property to check. + /// If the property has any pending assignments. + internal bool HasPendingSet(TProperty property) { return setRecords.ContainsKey(property) && setRecords[property].PendingValues.Count != 0; } - public TValue GetFirstPendingSet(T property) + /// + /// Gets the first pending assignment for the property. + /// + /// The property to check. + /// The first pending assignment for the property. + internal TValue GetFirstPendingSet(TProperty property) { return setRecords[property].PendingValues.Dequeue(); } - public void SetAndNotify(T property, Action> setterCallback, TValue value, Predicate pendingSetCondition) + /// + /// Set the property and notify listeners while ensuring we don't get into a stack overflow as happens with #855 and #824 + /// + /// The property to set. + /// + /// A callback that actually sets the property. + /// The first parameter is the value to set, and the second is a wrapper that takes a callback that sends the property-changed notification. + /// + /// The value to try to set. + /// A predicate to filter what possible values should be added as pending sets (i.e. only values not equal to the current value). + public void SetAndNotify(TProperty property, Action> setterCallback, TValue value, Predicate pendingSetCondition) { Contract.Requires(setterCallback != null); if (!IsNotifying(property))