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
+
+
+ ";
+
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var button = window.FindControl("button");
+
+ var brush = Assert.IsType(button.Background);
+ Assert.Equal(Colors.Red, brush.Color);
+
+ window.Resources["Red"] = Colors.Green;
+
+ Assert.Equal(Colors.Green, brush.Color);
+ }
+ }
+
+ private IDisposable StyledWindow(params (string, string)[] assets)
+ {
+ var services = TestServices.StyledWindow.With(
+ assetLoader: new MockAssetLoader(assets),
+ theme: () => new Styles
+ {
+ WindowStyle(),
+ });
+
+ return UnitTestApplication.Start(services);
+ }
+
+ private Style WindowStyle()
+ {
+ return new Style(x => x.OfType())
+ {
+ Setters =
+ {
+ new Setter(
+ Window.TemplateProperty,
+ new FuncControlTemplate((x, scope) =>
+ new ContentPresenter
+ {
+ Name = "PART_ContentPresenter",
+ [!ContentPresenter.ContentProperty] = x[!Window.ContentProperty],
+ }.RegisterInNameScope(scope)))
+ }
+ };
+ }
+ }
+}
diff --git a/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs b/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs
index 1eb3cd9750..56316b0cea 100644
--- a/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs
+++ b/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs
@@ -3,6 +3,7 @@
using System;
using Avalonia.Controls;
+using Moq;
using Xunit;
namespace Avalonia.Styling.UnitTests
@@ -136,7 +137,7 @@ namespace Avalonia.Styling.UnitTests
}
[Fact]
- public void ResourcesChanged_Should_Not_Be_Raised_On_Empty_MergedDictionary_Remove()
+ public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Resource_Add()
{
var target = new ResourceDictionary
{
@@ -145,31 +146,45 @@ namespace Avalonia.Styling.UnitTests
new ResourceDictionary(),
}
};
+
var raised = false;
target.ResourcesChanged += (_, __) => raised = true;
- target.MergedDictionaries.RemoveAt(0);
+ ((IResourceDictionary)target.MergedDictionaries[0]).Add("foo", "bar");
- Assert.False(raised);
+ Assert.True(raised);
}
[Fact]
- public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Resource_Add()
+ public void MergedDictionary_ParentResourcesChanged_Should_Be_Called_On_Resource_Add()
{
- var target = new ResourceDictionary
- {
- MergedDictionaries =
- {
- new ResourceDictionary(),
- }
- };
+ var target = new ResourceDictionary();
+ var merged = new Mock();
- var raised = false;
+ target.MergedDictionaries.Add(merged.Object);
+ merged.ResetCalls();
- target.ResourcesChanged += (_, __) => raised = true;
- ((IResourceDictionary)target.MergedDictionaries[0]).Add("foo", "bar");
+ target.Add("foo", "bar");
- Assert.True(raised);
+ merged.Verify(
+ x => x.ParentResourcesChanged(It.IsAny()),
+ Times.Once);
+ }
+
+ [Fact]
+ public void MergedDictionary_ParentResourcesChanged_Should_Be_Called_On_NotifyResourceChanged()
+ {
+ var target = new ResourceDictionary();
+ var merged = new Mock();
+
+ target.MergedDictionaries.Add(merged.Object);
+ merged.ResetCalls();
+
+ ((ISetResourceParent)target).ParentResourcesChanged(new ResourcesChangedEventArgs());
+
+ merged.Verify(
+ x => x.ParentResourcesChanged(It.IsAny()),
+ Times.Once);
}
}
}