diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 42761ab92e..c58555f75c 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -596,32 +596,21 @@ namespace Avalonia BindingPriority priority, bool isEffectiveValue) { - if (isEffectiveValue) - property.Notifying?.Invoke(this, true); - - try - { - var e = new AvaloniaPropertyChangedEventArgs( - this, - property, - oldValue, - newValue, - priority, - isEffectiveValue); + var e = new AvaloniaPropertyChangedEventArgs( + this, + property, + oldValue, + newValue, + priority, + isEffectiveValue); - OnPropertyChangedCore(e); + OnPropertyChangedCore(e); - if (isEffectiveValue) - { - property.NotifyChanged(e); - _propertyChanged?.Invoke(this, e); - _inpcChanged?.Invoke(this, new PropertyChangedEventArgs(property.Name)); - } - } - finally + if (isEffectiveValue) { - if (isEffectiveValue) - property.Notifying?.Invoke(this, false); + property.NotifyChanged(e); + _propertyChanged?.Invoke(this, e); + _inpcChanged?.Invoke(this, new PropertyChangedEventArgs(property.Name)); } } diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index d9368b57da..88d47c3dda 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -133,6 +133,7 @@ namespace Avalonia.PropertyStore if (valueChanged) { + using var notifying = PropertyNotifying.Start(owner.Owner, property); owner.Owner.RaisePropertyChanged(property, oldValue, Value, Priority, true); if (property.Inherits) owner.OnInheritedEffectiveValueChanged(property, oldValue, this); @@ -200,6 +201,7 @@ namespace Avalonia.PropertyStore if (valueChanged) { + using var notifying = PropertyNotifying.Start(owner.Owner, property); owner.Owner.RaisePropertyChanged(property, oldValue, Value, Priority, true); if (property.Inherits) owner.OnInheritedEffectiveValueChanged(property, oldValue, this); diff --git a/src/Avalonia.Base/PropertyStore/PropertyNotifying.cs b/src/Avalonia.Base/PropertyStore/PropertyNotifying.cs new file mode 100644 index 0000000000..f508059a74 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/PropertyNotifying.cs @@ -0,0 +1,35 @@ +using System; +using System.Diagnostics; + +namespace Avalonia.PropertyStore +{ + /// + /// Raises where necessary. + /// + /// + /// Uses the disposable pattern to ensure that the closing Notifying call is made even in the + /// presence of exceptions. + /// + internal readonly struct PropertyNotifying : IDisposable + { + private readonly AvaloniaObject _owner; + private readonly AvaloniaProperty _property; + + private PropertyNotifying(AvaloniaObject owner, AvaloniaProperty property) + { + Debug.Assert(property.Notifying is not null); + _owner = owner; + _property = property; + _property.Notifying!(owner, true); + } + + public void Dispose() => _property.Notifying!(_owner, false); + + public static PropertyNotifying? Start(AvaloniaObject owner, AvaloniaProperty property) + { + if (property.Notifying is null) + return null; + return new PropertyNotifying(owner, property); + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 2b194ffbe6..c062cc6ce5 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -476,6 +476,8 @@ namespace Avalonia.PropertyStore if (_effectiveValues is not null && _effectiveValues.ContainsKey(property)) return; + using var notifying = PropertyNotifying.Start(Owner, property); + Owner.RaisePropertyChanged( property, oldValue, @@ -769,6 +771,8 @@ namespace Avalonia.PropertyStore if (_effectiveValues?.ContainsKey(property) == true) return; + using var notifying = PropertyNotifying.Start(Owner, property); + // Raise PropertyChanged on this object if necessary. (oldValue ?? newValue!).RaiseInheritedValueChanged(Owner, property, oldValue, newValue); diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests.cs b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests.cs index cf2f946b15..c01e22347b 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests.cs @@ -470,6 +470,57 @@ namespace Avalonia.Base.UnitTests.Styling called); } + + [Fact] + public void DataContext_Notifications_Should_Be_Called_In_Correct_Order_When_Setting_Parent() + { + var root = new TestStackPanel + { + Name = "root", + DataContext = "foo", + }; + + var children = new[] + { + new TestControl + { + Name = "a1", + Child = new TestControl + { + Name = "b1", + } + }, + new TestControl + { + Name = "a2", + DataContext = "foo", + }, + }; + + var called = new List(); + + foreach (IDataContextEvents c in new[] { children[0], children[0].Child, children[1] }) + { + c.DataContextBeginUpdate += (s, e) => called.Add("begin " + ((StyledElement)s).Name); + c.DataContextChanged += (s, e) => called.Add("changed " + ((StyledElement)s).Name); + c.DataContextEndUpdate += (s, e) => called.Add("end " + ((StyledElement)s).Name); + } + + root.Children.AddRange(children); + + Assert.Equal( + new[] + { + "begin a1", + "begin b1", + "changed a1", + "changed b1", + "end b1", + "end a1", + }, + called); + } + [Fact] public void Resources_Owner_Is_Set() {