diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs index d5445b8f97..30f90754f5 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs +++ b/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; } } diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 19d80adaf5..ff1138ec1c 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/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 diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index be7e4dc6ca..6db339c4cd 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/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>("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)] diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs index 6f34865aa1..326199b3c2 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs +++ b/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>("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)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() {