From 4f2c2159938a4edab0f7ccac33f0cdcf80885662 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 20 Feb 2020 14:27:33 +0100 Subject: [PATCH] Pass ResourceChanged messages to child styles. Fixes #3590. --- src/Avalonia.Styling/StyledElement.cs | 40 ++++++++++--- src/Avalonia.Styling/Styling/Styles.cs | 40 +++++++------ .../StyledElementTests.cs | 60 +++++++++++++++++++ .../Avalonia.Styling.UnitTests/StylesTests.cs | 26 ++++---- 4 files changed, 125 insertions(+), 41 deletions(-) diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index 120a53c664..e9e9ca4d5a 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -67,6 +67,7 @@ namespace Avalonia private Subject _styleDetach = new Subject(); private ITemplatedControl _templatedParent; private bool _dataContextUpdating; + private bool _notifyingResourcesChanged; /// /// Initializes static members of the class. @@ -235,6 +236,7 @@ namespace Avalonia } _styles.ResourcesChanged += ThisResourcesChanged; + NotifyResourcesChanged(new ResourcesChangedEventArgs()); } } } @@ -253,6 +255,7 @@ namespace Avalonia if (_resources != null) { + (_resources as ISetResourceParent)?.SetParent(null); hadResources = _resources.Count > 0; _resources.ResourcesChanged -= ThisResourcesChanged; } @@ -260,9 +263,14 @@ namespace Avalonia _resources = value; _resources.ResourcesChanged += ThisResourcesChanged; + if (value is ISetResourceParent setParent && setParent.ResourceParent == null) + { + setParent.SetParent(this); + } + if (hadResources || _resources.Count > 0) { - ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); + NotifyResourcesChanged(new ResourcesChangedEventArgs()); } } } @@ -407,10 +415,7 @@ namespace Avalonia } /// - void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e) - { - ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); - } + void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e) => NotifyResourcesChanged(e); /// bool IResourceProvider.TryGetResource(object key, out object value) @@ -456,7 +461,8 @@ namespace Avalonia { Parent.ResourcesChanged += ThisResourcesChanged; } - ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); + + NotifyResourcesChanged(new ResourcesChangedEventArgs()); if (Parent is ILogicalRoot || Parent?.IsAttachedToLogicalTree == true || this is ILogicalRoot) { @@ -721,9 +727,29 @@ namespace Avalonia } } + private void NotifyResourcesChanged(ResourcesChangedEventArgs e) + { + if (_notifyingResourcesChanged) + { + return; + } + + try + { + _notifyingResourcesChanged = true; + (_resources as ISetResourceParent)?.ParentResourcesChanged(e); + (_styles as ISetResourceParent)?.ParentResourcesChanged(e); + ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + } + finally + { + _notifyingResourcesChanged = false; + } + } + private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e) { - ((ILogical)this).NotifyResourcesChanged(e); + NotifyResourcesChanged(e); } } } diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index fd38c39650..1929c24017 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -20,6 +20,7 @@ namespace Avalonia.Styling private IResourceDictionary _resources; private AvaloniaList _styles = new AvaloniaList(); private Dictionary> _cache; + private bool _notifyingResourcesChanged; public Styles() { @@ -38,7 +39,7 @@ namespace Avalonia.Styling ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); } - x.ResourcesChanged += SubResourceChanged; + x.ResourcesChanged += NotifyResourcesChanged; _cache = null; }, x => @@ -54,7 +55,7 @@ namespace Avalonia.Styling ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); } - x.ResourcesChanged -= SubResourceChanged; + x.ResourcesChanged -= NotifyResourcesChanged; _cache = null; }, () => { }); @@ -90,11 +91,11 @@ namespace Avalonia.Styling if (_resources != null) { hadResources = _resources.Count > 0; - _resources.ResourcesChanged -= ResourceDictionaryChanged; + _resources.ResourcesChanged -= NotifyResourcesChanged; } _resources = value; - _resources.ResourcesChanged += ResourceDictionaryChanged; + _resources.ResourcesChanged += NotifyResourcesChanged; if (hadResources || _resources.Count > 0) { @@ -261,34 +262,35 @@ namespace Avalonia.Styling /// void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e) { - ResourcesChanged?.Invoke(this, e); + NotifyResourcesChanged(e); } - private void ResourceDictionaryChanged(object sender, ResourcesChangedEventArgs e) + private void NotifyResourcesChanged(object sender, ResourcesChangedEventArgs e) { - foreach (var child in this) - { - (child as ISetResourceParent)?.ParentResourcesChanged(e); - } - - ResourcesChanged?.Invoke(this, e); + NotifyResourcesChanged(e); } - private void SubResourceChanged(object sender, ResourcesChangedEventArgs e) + private void NotifyResourcesChanged(ResourcesChangedEventArgs e) { - var foundSource = false; + if (_notifyingResourcesChanged) + { + return; + } - foreach (var child in this) + try { - if (foundSource) + _notifyingResourcesChanged = true; + foreach (var child in this) { (child as ISetResourceParent)?.ParentResourcesChanged(e); } - foundSource |= child == sender; + ResourcesChanged?.Invoke(this, e); + } + finally + { + _notifyingResourcesChanged = false; } - - ResourcesChanged?.Invoke(this, e); } } } diff --git a/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs b/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs index 3a676529c7..d349d9c3e0 100644 --- a/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs +++ b/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs @@ -489,6 +489,36 @@ namespace Avalonia.Styling.UnitTests called); } + [Fact] + public void Resources_Parent_Is_Set() + { + var target = new TestControl(); + + Assert.Same(target, ((IResourceNode)target.Resources).ResourceParent); + } + + [Fact] + public void Assigned_Resources_Parent_Is_Set() + { + var resources = new ResourceDictionary(); + var target = new TestControl { Resources = resources }; + + Assert.Same(target, ((IResourceNode)resources).ResourceParent); + } + + [Fact] + public void Assigning_Resources_Raises_ResourcesChanged() + { + var resources = new ResourceDictionary { { "foo", "bar" } }; + var target = new TestControl(); + var raised = 0; + + target.ResourcesChanged += (s, e) => ++raised; + target.Resources = resources; + + Assert.Equal(1, raised); + } + [Fact] public void Changing_Parent_Notifies_Resources_ParentResourcesChanged() { @@ -505,6 +535,36 @@ namespace Avalonia.Styling.UnitTests Times.Once); } + [Fact] + public void Styles_Parent_Is_Set() + { + var target = new TestControl(); + + Assert.Same(target, ((IResourceNode)target.Styles).ResourceParent); + } + + [Fact] + public void Assigned_Styles_Parent_Is_Set() + { + var styles = new Styles(); + var target = new TestControl { Styles = styles }; + + Assert.Same(target, ((IResourceNode)styles).ResourceParent); + } + + [Fact] + public void Assigning_Styles_Raises_ResourcesChanged() + { + var styles = new Styles { Resources = { { "foo", "bar" } } }; + var target = new TestControl(); + var raised = 0; + + target.ResourcesChanged += (s, e) => ++raised; + target.Styles = styles; + + Assert.Equal(1, raised); + } + [Fact] public void Changing_Parent_Notifies_Styles_ParentResourcesChanged() { diff --git a/tests/Avalonia.Styling.UnitTests/StylesTests.cs b/tests/Avalonia.Styling.UnitTests/StylesTests.cs index 82b6b81759..e921763495 100644 --- a/tests/Avalonia.Styling.UnitTests/StylesTests.cs +++ b/tests/Avalonia.Styling.UnitTests/StylesTests.cs @@ -3,6 +3,7 @@ using System; using Avalonia.Controls; +using Moq; using Xunit; namespace Avalonia.Styling.UnitTests @@ -76,7 +77,7 @@ namespace Avalonia.Styling.UnitTests } [Fact] - public void Adding_Resource_To_Younger_Sibling_Style_Should_Raise_ResourceChanged() + public void Adding_Resource_To_Sibling_Style_Should_Raise_ResourceChanged() { Style style1; Style style2; @@ -95,25 +96,20 @@ namespace Avalonia.Styling.UnitTests } [Fact] - public void Adding_Resource_To_Older_Sibling_Style_Should_Raise_ResourceChanged() + public void ParentResourcesChanged_Should_Be_Propagated_To_Children() { - Style style1; - Style style2; - var target = new Styles - { - (style1 = new Style()), - (style2 = new Style()), - }; - - var raised = false; + var childStyle = new Mock(); + var setResourceParent = childStyle.As(); + var target = new Styles { childStyle.Object }; - style1.ResourcesChanged += (_, __) => raised = true; - style2.Resources.Add("foo", "bar"); + setResourceParent.ResetCalls(); + ((ISetResourceParent)target).ParentResourcesChanged(new ResourcesChangedEventArgs()); - Assert.False(raised); + setResourceParent.Verify(x => x.ParentResourcesChanged( + It.IsAny()), + Times.Once); } - [Fact] public void Finds_Resource_In_Merged_Dictionary() {