From ac8cc99f16ce6491a859e3008ad460d8ab78012e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Aug 2017 00:54:26 +0200 Subject: [PATCH] Started adding MergedDictionaries. --- src/Avalonia.Controls/Control.cs | 13 +- .../Controls/IResourceDictionary.cs | 10 + .../Controls/IResourceProvider.cs | 2 +- .../Controls/ResourceDictionary.cs | 77 +++++++- .../ResourceDictionaryTests.cs | 175 ++++++++++++++++++ 5 files changed, 264 insertions(+), 13 deletions(-) create mode 100644 tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index a2fc188000..889edd48f8 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -284,7 +284,7 @@ namespace Avalonia.Controls if (_styles != null) { (_styles as ISetStyleParent)?.SetParent(null); - _styles.ResourcesChanged -= StyleResourcesChanged; + _styles.ResourcesChanged -= ThisResourcesChanged; } _styles = value; @@ -294,7 +294,7 @@ namespace Avalonia.Controls setParent.SetParent(this); } - _styles.ResourcesChanged += StyleResourcesChanged; + _styles.ResourcesChanged += ThisResourcesChanged; } } } @@ -323,7 +323,7 @@ namespace Avalonia.Controls if (_resources == null) { _resources = new ResourceDictionary(); - _resources.CollectionChanged += ResourceDictionaryChanged; + _resources.ResourcesChanged += ThisResourcesChanged; } return _resources; @@ -914,12 +914,7 @@ namespace Avalonia.Controls } } - private void ResourceDictionaryChanged(object sender, NotifyCollectionChangedEventArgs e) - { - ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); - } - - private void StyleResourcesChanged(object sender, ResourcesChangedEventArgs e) + private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e) { ((ILogical)this).NotifyResourcesChanged(e); } diff --git a/src/Avalonia.Styling/Controls/IResourceDictionary.cs b/src/Avalonia.Styling/Controls/IResourceDictionary.cs index bc76d8f60e..0ddeda0f3d 100644 --- a/src/Avalonia.Styling/Controls/IResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/IResourceDictionary.cs @@ -11,6 +11,16 @@ namespace Avalonia.Controls /// public interface IResourceDictionary : IDictionary { + /// + /// Raised when resources in the dictionary are changed. + /// + event EventHandler ResourcesChanged; + + /// + /// Gets a collection of child resource dictionaries. + /// + IList MergedDictionaries { get; } + /// /// Tries to find a resource within the dictionary. /// diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceProvider.cs index 180476b2e4..e133427350 100644 --- a/src/Avalonia.Styling/Controls/IResourceProvider.cs +++ b/src/Avalonia.Styling/Controls/IResourceProvider.cs @@ -8,7 +8,7 @@ namespace Avalonia.Controls public interface IResourceProvider { /// - /// Raised when the resources in the element are changed. + /// Raised when resources in the element are changed. /// event EventHandler ResourcesChanged; diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index bd08680a42..975d27c08b 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -2,7 +2,8 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; using Avalonia.Collections; namespace Avalonia.Controls @@ -10,9 +11,79 @@ namespace Avalonia.Controls /// /// An indexed dictionary of resources. /// - public class ResourceDictionary : AvaloniaDictionary, IResourceDictionary, IDictionary + public class ResourceDictionary : AvaloniaDictionary, IResourceDictionary { + private AvaloniaList _mergedDictionaries; + + public event EventHandler ResourcesChanged; + + public ResourceDictionary() + { + CollectionChanged += OnCollectionChanged; + } + + public IList MergedDictionaries + { + get + { + if (_mergedDictionaries == null) + { + _mergedDictionaries = new AvaloniaList(); + _mergedDictionaries.ResetBehavior = ResetBehavior.Remove; + _mergedDictionaries.ForEachItem( + x => + { + if (x.Count > 0) + { + OnResourcesChanged(); + } + + x.ResourcesChanged += MergedDictionaryResourcesChanged; + }, + x => + { + if (x.Count > 0) + { + OnResourcesChanged(); + } + + x.ResourcesChanged -= MergedDictionaryResourcesChanged; + }, + () => { }); + } + + return _mergedDictionaries; + } + } + /// - public bool TryGetResource(string key, out object value) => TryGetValue(key, out value); + public bool TryGetResource(string key, out object value) + { + if (TryGetValue(key, out value)) + { + return true; + } + + if (_mergedDictionaries != null) + { + for (var i = _mergedDictionaries.Count - 1; i >= 0; --i) + { + if (_mergedDictionaries[i].TryGetResource(key, out value)) + { + return true; + } + } + } + + return false; + } + + private void OnResourcesChanged() + { + ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + } + + private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => OnResourcesChanged(); + private void MergedDictionaryResourcesChanged(object sender, ResourcesChangedEventArgs e) => OnResourcesChanged(); } } diff --git a/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs b/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs new file mode 100644 index 0000000000..f31cbbac0a --- /dev/null +++ b/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs @@ -0,0 +1,175 @@ +// 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 Xunit; + +namespace Avalonia.Styling.UnitTests +{ + public class ResourceDictionaryTests + { + [Fact] + public void TryGetResource_Should_Find_Resource() + { + var target = new ResourceDictionary + { + { "foo", "bar" }, + }; + + Assert.True(target.TryGetResource("foo", out var result)); + Assert.Equal("bar", result); + } + + [Fact] + public void TryGetResource_Should_Find_Resource_From_Merged_Dictionary() + { + var target = new ResourceDictionary + { + MergedDictionaries = + { + new ResourceDictionary + { + { "foo", "bar" }, + } + } + }; + + Assert.True(target.TryGetResource("foo", out var result)); + Assert.Equal("bar", result); + } + + [Fact] + public void TryGetResource_Should_Find_Resource_From_Itself_Before_Merged_Dictionary() + { + var target = new ResourceDictionary + { + { "foo", "bar" }, + }; + + target.MergedDictionaries.Add(new ResourceDictionary + { + { "foo", "baz" }, + }); + + Assert.True(target.TryGetResource("foo", out var result)); + Assert.Equal("bar", result); + } + + [Fact] + public void TryGetResource_Should_Find_Resource_From_Later_Merged_Dictionary() + { + var target = new ResourceDictionary + { + MergedDictionaries = + { + new ResourceDictionary + { + { "foo", "bar" }, + }, + new ResourceDictionary + { + { "foo", "baz" }, + } + } + }; + + Assert.True(target.TryGetResource("foo", out var result)); + Assert.Equal("baz", result); + } + + [Fact] + public void ResourcesChanged_Should_Be_Raised_On_Resource_Add() + { + var target = new ResourceDictionary(); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.Add("foo", "bar"); + + Assert.True(raised); + } + + [Fact] + public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Add() + { + var target = new ResourceDictionary(); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.MergedDictionaries.Add(new ResourceDictionary + { + { "foo", "bar" }, + }); + + Assert.True(raised); + } + + [Fact] + public void ResourcesChanged_Should_Not_Be_Raised_On_Empty_MergedDictionary_Add() + { + var target = new ResourceDictionary(); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.MergedDictionaries.Add(new ResourceDictionary()); + + Assert.False(raised); + } + + [Fact] + public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Remove() + { + var target = new ResourceDictionary + { + MergedDictionaries = + { + new ResourceDictionary { { "foo", "bar" } }, + } + }; + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.MergedDictionaries.RemoveAt(0); + + Assert.True(raised); + } + + [Fact] + public void ResourcesChanged_Should_Not_Be_Raised_On_Empty_MergedDictionary_Remove() + { + var target = new ResourceDictionary + { + MergedDictionaries = + { + new ResourceDictionary(), + } + }; + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.MergedDictionaries.RemoveAt(0); + + Assert.False(raised); + } + + [Fact] + public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Resource_Add() + { + var target = new ResourceDictionary + { + MergedDictionaries = + { + new ResourceDictionary(), + } + }; + + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.MergedDictionaries[0].Add("foo", "bar"); + + Assert.True(raised); + } + } +}