瀏覽代碼

Correctly unsubscribe animation value entries.

Steven Kirk 3 年之前
父節點
當前提交
9b2d9be1fa

+ 34 - 10
src/Avalonia.Base/PropertyStore/EffectiveValue.cs

@@ -1,4 +1,5 @@
-using Avalonia.Data;
+using System.Diagnostics;
+using Avalonia.Data;
 
 namespace Avalonia.PropertyStore
 {
@@ -147,18 +148,41 @@ namespace Avalonia.PropertyStore
 
         protected void UpdateValueEntry(IValueEntry? entry, BindingPriority priority)
         {
-            if (priority <= Priority && entry != _valueEntry)
+            Debug.Assert(priority != BindingPriority.LocalValue);
+
+            if (priority <= BindingPriority.Animation)
             {
-                _valueEntry?.Unsubscribe();
-                _valueEntry = entry;
+                // If we've received an animation value and the current value is a non-animation
+                // value, then the current entry becomes our base entry.
+                if (Priority > BindingPriority.LocalValue && Priority < BindingPriority.Inherited)
+                {
+                    Debug.Assert(_valueEntry is not null);
+                    _baseValueEntry = _valueEntry;
+                    _valueEntry = null;
+                }
+
+                if (_valueEntry != entry)
+                {
+                    _valueEntry?.Unsubscribe();
+                    _valueEntry = entry;
+                }
             }
-
-            if (priority <= BasePriority &&
-                priority >= BindingPriority.LocalValue &&
-                entry != _baseValueEntry)
+            else if (Priority <= BindingPriority.Animation)
             {
-                _baseValueEntry?.Unsubscribe();
-                _baseValueEntry = entry;
+                // We've received a non-animation value and have an active animation value, so the
+                // new entry becomes our base entry.
+                if (_baseValueEntry != entry)
+                {
+                    _baseValueEntry?.Unsubscribe();
+                    _baseValueEntry = entry;
+                }
+            }
+            else if (_valueEntry != entry)
+            {
+                // Both the current value and the new value are non-animation values, so the new
+                // entry replaces the existing entry.
+                _valueEntry?.Unsubscribe();
+                _valueEntry = entry;
             }
         }
 

+ 1 - 1
src/Avalonia.Base/PropertyStore/ValueStore.cs

@@ -369,7 +369,7 @@ namespace Avalonia.PropertyStore
 
             if (TryGetEffectiveValue(property, out var existing))
             {
-                if (priority <= existing.Priority)
+                if (priority <= existing.BasePriority)
                     ReevaluateEffectiveValue(property, existing);
             }
             else

+ 15 - 0
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@@ -562,6 +562,21 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal(0, source.SubscriberCount);
         }
 
+        [Theory]
+        [InlineData(BindingPriority.LocalValue)]
+        [InlineData(BindingPriority.Style)]
+        public void Observable_Is_Not_Unsubscribed_When_Animation_Value_Is_Set(BindingPriority priority)
+        {
+            var source = new TestSubject<BindingValue<string>>("foo");
+            var target = new Class1();
+
+            target.Bind(Class1.FooProperty, source, priority);
+            Assert.Equal(1, source.SubscriberCount);
+
+            target.SetValue(Class1.FooProperty, "bar", BindingPriority.Animation);
+            Assert.Equal(1, source.SubscriberCount);
+        }
+
         [Theory]
         [InlineData(BindingPriority.LocalValue)]
         [InlineData(BindingPriority.Style)]

+ 20 - 0
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs

@@ -48,6 +48,26 @@ namespace Avalonia.Base.UnitTests
             Assert.False(change.IsEffectiveValueChange);
         }
 
+        [Fact]
+        public void OnPropertyChangedCore_Is_Called_On_Non_Effective_Property_Binding_Value_Change()
+        {
+            var target = new Class1();
+            var source = new BehaviorSubject<BindingValue<string>>("styled1");
+
+            target.Bind(Class1.FooProperty, source, BindingPriority.Style);
+            target.SetValue(Class1.FooProperty, "newvalue", BindingPriority.Animation);
+            source.OnNext("styled2");
+
+            Assert.Equal(3, target.CoreChanges.Count);
+
+            var change = (AvaloniaPropertyChangedEventArgs<string>)target.CoreChanges[2];
+            
+            Assert.Equal("styled2", change.NewValue.Value);
+            Assert.False(change.OldValue.HasValue);
+            Assert.Equal(BindingPriority.Style, change.Priority);
+            Assert.False(change.IsEffectiveValueChange);
+        }
+
         [Fact]
         public void OnPropertyChanged_Is_Called_Only_For_Effective_Value_Changes()
         {