diff --git a/src/Avalonia.Styling/Styling/ISetStyleParent.cs b/src/Avalonia.Styling/Controls/ISetResourceParent.cs similarity index 61% rename from src/Avalonia.Styling/Styling/ISetStyleParent.cs rename to src/Avalonia.Styling/Controls/ISetResourceParent.cs index bca3d9d714..a1264adc34 100644 --- a/src/Avalonia.Styling/Styling/ISetStyleParent.cs +++ b/src/Avalonia.Styling/Controls/ISetResourceParent.cs @@ -1,29 +1,27 @@ -using Avalonia.Controls; - -namespace Avalonia.Styling +namespace Avalonia.Controls { /// - /// Defines an interface through which a 's parent can be set. + /// Defines an interface through which an 's parent can be set. /// /// /// You should not usually need to use this interface - it is for internal use only. /// - public interface ISetStyleParent : IStyle + public interface ISetResourceParent : IResourceNode { /// - /// Sets the style parent. + /// Sets the resource parent. /// /// The parent. void SetParent(IResourceNode parent); /// - /// Notifies the style that a change has been made to resources that apply to it. + /// Notifies the resource node that a change has been made to the resources in its parent. /// /// The event args. /// /// This method will be called automatically by the framework, you should not need to call /// this method yourself. /// - void NotifyResourcesChanged(ResourcesChangedEventArgs e); + void ParentResourcesChanged(ResourcesChangedEventArgs e); } } diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index 901e27b7b7..acc2db1ff7 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -12,8 +12,12 @@ namespace Avalonia.Controls /// /// An indexed dictionary of resources. /// - public class ResourceDictionary : AvaloniaDictionary, IResourceDictionary + public class ResourceDictionary : AvaloniaDictionary, + IResourceDictionary, + IResourceNode, + ISetResourceParent { + private IResourceNode _parent; private AvaloniaList _mergedDictionaries; /// @@ -39,6 +43,12 @@ namespace Avalonia.Controls _mergedDictionaries.ForEachItem( x => { + if (x is ISetResourceParent setParent) + { + setParent.SetParent(this); + setParent.ParentResourcesChanged(new ResourcesChangedEventArgs()); + } + if (x.HasResources) { OnResourcesChanged(); @@ -48,11 +58,18 @@ namespace Avalonia.Controls }, x => { + if (x is ISetResourceParent setParent) + { + setParent.SetParent(null); + setParent.ParentResourcesChanged(new ResourcesChangedEventArgs()); + } + if (x.HasResources) { OnResourcesChanged(); } + (x as ISetResourceParent)?.SetParent(null); x.ResourcesChanged -= MergedDictionaryResourcesChanged; }, () => { }); @@ -68,6 +85,27 @@ namespace Avalonia.Controls get => Count > 0 || (_mergedDictionaries?.Any(x => x.HasResources) ?? false); } + /// + IResourceNode IResourceNode.ResourceParent => _parent; + + /// + void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e) + { + NotifyMergedDictionariesResourcesChanged(e); + ResourcesChanged?.Invoke(this, e); + } + + /// + void ISetResourceParent.SetParent(IResourceNode parent) + { + if (_parent != null && parent != null) + { + throw new InvalidOperationException("The ResourceDictionary already has a parent."); + } + + _parent = parent; + } + /// public bool TryGetResource(object key, out object value) { @@ -95,7 +133,27 @@ namespace Avalonia.Controls ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); } - private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => OnResourcesChanged(); + private void NotifyMergedDictionariesResourcesChanged(ResourcesChangedEventArgs e) + { + if (_mergedDictionaries != null) + { + for (var i = _mergedDictionaries.Count - 1; i >= 0; --i) + { + if (_mergedDictionaries[i] is ISetResourceParent merged) + { + merged.ParentResourcesChanged(e); + } + } + } + } + + private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + var ev = new ResourcesChangedEventArgs(); + NotifyMergedDictionariesResourcesChanged(ev); + OnResourcesChanged(); + } + private void MergedDictionaryResourcesChanged(object sender, ResourcesChangedEventArgs e) => OnResourcesChanged(); } } diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index cf3c4dc855..9b1cfc6838 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -223,13 +223,13 @@ namespace Avalonia { if (_styles != null) { - (_styles as ISetStyleParent)?.SetParent(null); + (_styles as ISetResourceParent)?.SetParent(null); _styles.ResourcesChanged -= ThisResourcesChanged; } _styles = value; - if (value is ISetStyleParent setParent && setParent.ResourceParent == null) + if (value is ISetResourceParent setParent && setParent.ResourceParent == null) { setParent.SetParent(this); } diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 3ce82b4160..99ee8d8563 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -14,7 +14,7 @@ namespace Avalonia.Styling /// /// Defines a style. /// - public class Style : AvaloniaObject, IStyle, ISetStyleParent + public class Style : AvaloniaObject, IStyle, ISetResourceParent { private static Dictionary _applied = new Dictionary(); @@ -59,16 +59,16 @@ namespace Avalonia.Styling if (_resources != null) { - hadResources = _resources.Count > 0; + hadResources = _resources.HasResources; _resources.ResourcesChanged -= ResourceDictionaryChanged; } _resources = value; _resources.ResourcesChanged += ResourceDictionaryChanged; - if (hadResources || _resources.Count > 0) + if (hadResources || _resources.HasResources) { - ((ISetStyleParent)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); + ((ISetResourceParent)this).ParentResourcesChanged(new ResourcesChangedEventArgs()); } } } @@ -194,13 +194,13 @@ namespace Avalonia.Styling } /// - void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e) + void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e) { ResourcesChanged?.Invoke(this, e); } /// - void ISetStyleParent.SetParent(IResourceNode parent) + void ISetResourceParent.SetParent(IResourceNode parent) { if (_parent != null && parent != null) { diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index a4563110a9..0226288998 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -14,7 +14,7 @@ namespace Avalonia.Styling /// /// A style that consists of a number of child styles. /// - public class Styles : AvaloniaObject, IAvaloniaList, IStyle, ISetStyleParent + public class Styles : AvaloniaObject, IAvaloniaList, IStyle, ISetResourceParent { private IResourceNode _parent; private IResourceDictionary _resources; @@ -27,10 +27,10 @@ namespace Avalonia.Styling _styles.ForEachItem( x => { - if (x.ResourceParent == null && x is ISetStyleParent setParent) + if (x.ResourceParent == null && x is ISetResourceParent setParent) { setParent.SetParent(this); - setParent.NotifyResourcesChanged(new ResourcesChangedEventArgs()); + setParent.ParentResourcesChanged(new ResourcesChangedEventArgs()); } if (x.HasResources) @@ -43,10 +43,10 @@ namespace Avalonia.Styling }, x => { - if (x.ResourceParent == this && x is ISetStyleParent setParent) + if (x.ResourceParent == this && x is ISetResourceParent setParent) { setParent.SetParent(null); - setParent.NotifyResourcesChanged(new ResourcesChangedEventArgs()); + setParent.ParentResourcesChanged(new ResourcesChangedEventArgs()); } if (x.HasResources) @@ -98,7 +98,7 @@ namespace Avalonia.Styling if (hadResources || _resources.Count > 0) { - ((ISetStyleParent)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); + ((ISetResourceParent)this).ParentResourcesChanged(new ResourcesChangedEventArgs()); } } } @@ -246,7 +246,7 @@ namespace Avalonia.Styling IEnumerator IEnumerable.GetEnumerator() => _styles.GetEnumerator(); /// - void ISetStyleParent.SetParent(IResourceNode parent) + void ISetResourceParent.SetParent(IResourceNode parent) { if (_parent != null && parent != null) { @@ -257,7 +257,7 @@ namespace Avalonia.Styling } /// - void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e) + void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e) { ResourcesChanged?.Invoke(this, e); } @@ -266,7 +266,7 @@ namespace Avalonia.Styling { foreach (var child in this) { - (child as ISetStyleParent)?.NotifyResourcesChanged(e); + (child as ISetResourceParent)?.ParentResourcesChanged(e); } ResourcesChanged?.Invoke(this, e); @@ -280,7 +280,7 @@ namespace Avalonia.Styling { if (foundSource) { - (child as ISetStyleParent)?.NotifyResourcesChanged(e); + (child as ISetResourceParent)?.ParentResourcesChanged(e); } foundSource |= child == sender; diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs index 3525628a79..0d56942645 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs @@ -7,8 +7,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions /// /// Loads a resource dictionary from a specified URL. /// - public class ResourceInclude :IResourceProvider + public class ResourceInclude : IResourceNode, ISetResourceParent { + private IResourceNode _parent; private Uri _baseUri; private IResourceDictionary _loaded; @@ -26,6 +27,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions var loader = new AvaloniaXamlLoader(); _loaded = (IResourceDictionary)loader.Load(Source, _baseUri); + (_loaded as ISetResourceParent)?.SetParent(this); + _loaded.ResourcesChanged += ResourcesChanged; + if (_loaded.HasResources) { ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); @@ -44,12 +48,32 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions /// bool IResourceProvider.HasResources => Loaded.HasResources; + /// + IResourceNode IResourceNode.ResourceParent => _parent; + /// bool IResourceProvider.TryGetResource(object key, out object value) { return Loaded.TryGetResource(key, out value); } + /// + void ISetResourceParent.SetParent(IResourceNode parent) + { + if (_parent != null && parent != null) + { + throw new InvalidOperationException("The ResourceInclude already has a parent."); + } + + _parent = parent; + } + + /// + void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e) + { + (_loaded as ISetResourceParent)?.ParentResourcesChanged(e); + } + public ResourceInclude ProvideValue(IServiceProvider serviceProvider) { var tdc = (ITypeDescriptorContext)serviceProvider; diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index 7acee50d80..41eab79ed8 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -10,7 +10,7 @@ namespace Avalonia.Markup.Xaml.Styling /// /// Includes a style from a URL. /// - public class StyleInclude : IStyle, ISetStyleParent + public class StyleInclude : IStyle, ISetResourceParent { private Uri _baseUri; private IStyle _loaded; @@ -53,7 +53,7 @@ namespace Avalonia.Markup.Xaml.Styling { var loader = new AvaloniaXamlLoader(); _loaded = (IStyle)loader.Load(Source, _baseUri); - (_loaded as ISetStyleParent)?.SetParent(this); + (_loaded as ISetResourceParent)?.SetParent(this); } return _loaded; @@ -89,13 +89,13 @@ namespace Avalonia.Markup.Xaml.Styling public bool TryGetResource(object key, out object value) => Loaded.TryGetResource(key, out value); /// - void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e) + void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e) { - (Loaded as ISetStyleParent)?.NotifyResourcesChanged(e); + (Loaded as ISetResourceParent)?.ParentResourcesChanged(e); } /// - void ISetStyleParent.SetParent(IResourceNode parent) + void ISetResourceParent.SetParent(IResourceNode parent) { if (_parent != null && parent != null) { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs new file mode 100644 index 0000000000..d0cdef3c0b --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -0,0 +1,123 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; +using Avalonia.Media; +using Avalonia.Styling; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml +{ + public class ResourceDictionaryTests : XamlTestBase + { + [Fact] + public void StaticResource_Works_In_ResourceDictionary() + { + using (StyledWindow()) + { + var xaml = @" + + Red + +"; + var loader = new AvaloniaXamlLoader(); + var resources = (ResourceDictionary)loader.Load(xaml); + var brush = (SolidColorBrush)resources["RedBrush"]; + + Assert.Equal(Colors.Red, brush.Color); + } + } + + [Fact] + public void DynamicResource_Works_In_ResourceDictionary() + { + using (StyledWindow()) + { + var xaml = @" + + Red + +"; + var loader = new AvaloniaXamlLoader(); + var resources = (ResourceDictionary)loader.Load(xaml); + var brush = (SolidColorBrush)resources["RedBrush"]; + + Assert.Equal(Colors.Red, brush.Color); + } + } + + [Fact] + public void DynamicResource_Finds_Resource_In_Parent_Dictionary() + { + var dictionaryXaml = @" + + +"; + + using (StyledWindow(assets: ("test:dict.xaml", dictionaryXaml))) + { + var xaml = @" + + + + + + + + Red + +