Просмотр исходного кода

Fix direct properties by delaying setting values until after any currently running property changed notifications for this property finish running.

Jeremy Koritzinsky 8 лет назад
Родитель
Сommit
b5ee3077bc

+ 22 - 5
src/Avalonia.Base/AvaloniaObject.cs

@@ -510,6 +510,7 @@ namespace Avalonia
         {
             Contract.Requires<ArgumentNullException>(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<AvaloniaProperty> delayedSetter = new DelayedSetter<AvaloniaProperty>();
+
         /// <summary>
         /// Sets the backing field for a direct avalonia property, raising the 
         /// <see cref="PropertyChanged"/> event if the value has changed.
@@ -553,15 +557,28 @@ namespace Avalonia
         protected bool SetAndRaise<T>(AvaloniaProperty<T> 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;
             }
         }

+ 56 - 0
src/Avalonia.Base/DelayedSetter.cs

@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia
+{
+    class DelayedSetter<T>
+    {
+        private class SettingStatus
+        {
+            public bool Notifying { get; set; }
+
+            private Queue<object> pendingValues;
+            
+            public Queue<object> PendingValues
+            {
+                get
+                {
+                    return pendingValues ?? (pendingValues = new Queue<object>());
+                }
+            }
+        }
+
+        private readonly Dictionary<T, SettingStatus> setRecords = new Dictionary<T, SettingStatus>();
+
+        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();
+        }
+    }
+}

+ 1 - 1
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;