diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs
index 8c5f9abb8d..725163bba8 100644
--- a/src/Avalonia.Controls/Application.cs
+++ b/src/Avalonia.Controls/Application.cs
@@ -149,7 +149,7 @@ namespace Avalonia
IStyleHost IStyleHost.StylingParent => null;
///
- bool IResourceNode.HasResources => _resources?.Count > 0;
+ bool IResourceProvider.HasResources => _resources?.Count > 0;
///
IResourceNode IResourceNode.ResourceParent => null;
@@ -182,7 +182,7 @@ namespace Avalonia
}
///
- bool IResourceNode.TryGetResource(string key, out object value)
+ bool IResourceProvider.TryGetResource(string key, out object value)
{
value = null;
return (_resources?.TryGetResource(key, out value) ?? false) ||
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index bde9bb6760..b6283ab7ee 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -401,7 +401,7 @@ namespace Avalonia.Controls
IAvaloniaReadOnlyList ILogical.LogicalChildren => LogicalChildren;
///
- bool IResourceNode.HasResources => _resources?.Count > 0 || Styles.HasResources;
+ bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources;
///
IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode;
@@ -491,7 +491,7 @@ namespace Avalonia.Controls
}
///
- bool IResourceNode.TryGetResource(string key, out object value)
+ bool IResourceProvider.TryGetResource(string key, out object value)
{
value = null;
return (_resources?.TryGetResource(key, out value) ?? false) ||
diff --git a/src/Avalonia.Styling/Controls/IResourceDictionary.cs b/src/Avalonia.Styling/Controls/IResourceDictionary.cs
index 0ddeda0f3d..e6da375544 100644
--- a/src/Avalonia.Styling/Controls/IResourceDictionary.cs
+++ b/src/Avalonia.Styling/Controls/IResourceDictionary.cs
@@ -9,28 +9,11 @@ namespace Avalonia.Controls
///
/// An indexed dictionary of resources.
///
- public interface IResourceDictionary : IDictionary
+ public interface IResourceDictionary : IResourceProvider, 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.
- ///
- /// The resource key.
- ///
- /// When this method returns, contains the value associated with the specified key,
- /// if the key is found; otherwise, null
- ///
- /// True if the resource if found, otherwise false.
- ///
- bool TryGetResource(string key, out object value);
+ IList MergedDictionaries { get; }
}
}
diff --git a/src/Avalonia.Styling/Controls/IResourceNode.cs b/src/Avalonia.Styling/Controls/IResourceNode.cs
index 6ff5fb8d2f..b6a6cdc3e3 100644
--- a/src/Avalonia.Styling/Controls/IResourceNode.cs
+++ b/src/Avalonia.Styling/Controls/IResourceNode.cs
@@ -3,35 +3,13 @@
namespace Avalonia.Controls
{
///
- /// Defines an element that can be queried for resources.
+ /// Represents resource provider in a tree.
///
- public interface IResourceNode
+ public interface IResourceNode : IResourceProvider
{
- ///
- /// Raised when resources in the element are changed.
- ///
- event EventHandler ResourcesChanged;
-
- ///
- /// Gets a value indicating whether the node has resources.
- ///
- bool HasResources { get; }
-
///
/// Gets the parent resource node, if any.
///
IResourceNode ResourceParent { get; }
-
- ///
- /// Tries to find a resource within the element.
- ///
- /// The resource key.
- ///
- /// When this method returns, contains the value associated with the specified key,
- /// if the key is found; otherwise, null
- ///
- /// True if the resource if found, otherwise false.
- ///
- bool TryGetResource(string key, out object value);
}
}
diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceProvider.cs
new file mode 100644
index 0000000000..2ca83ea2d2
--- /dev/null
+++ b/src/Avalonia.Styling/Controls/IResourceProvider.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Represents an object that can be queried for resources.
+ ///
+ public interface IResourceProvider
+ {
+ ///
+ /// Raised when resources in the provider are changed.
+ ///
+ event EventHandler ResourcesChanged;
+
+ ///
+ /// Gets a value indicating whether the element has resources.
+ ///
+ bool HasResources { get; }
+
+ ///
+ /// Tries to find a resource within the provider.
+ ///
+ /// The resource key.
+ ///
+ /// When this method returns, contains the value associated with the specified key,
+ /// if the key is found; otherwise, null
+ ///
+ /// True if the resource if found, otherwise false.
+ ///
+ bool TryGetResource(string key, out object value);
+ }
+}
diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs
index 30fd8056f1..ec0a59dad9 100644
--- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs
+++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.Linq;
using Avalonia.Collections;
namespace Avalonia.Controls
@@ -13,7 +14,7 @@ namespace Avalonia.Controls
///
public class ResourceDictionary : AvaloniaDictionary, IResourceDictionary
{
- private AvaloniaList _mergedDictionaries;
+ private AvaloniaList _mergedDictionaries;
///
/// Initializes a new instance of the class.
@@ -27,18 +28,18 @@ namespace Avalonia.Controls
public event EventHandler ResourcesChanged;
///
- public IList MergedDictionaries
+ public IList MergedDictionaries
{
get
{
if (_mergedDictionaries == null)
{
- _mergedDictionaries = new AvaloniaList();
+ _mergedDictionaries = new AvaloniaList();
_mergedDictionaries.ResetBehavior = ResetBehavior.Remove;
_mergedDictionaries.ForEachItem(
x =>
{
- if (x.Count > 0)
+ if (x.HasResources)
{
OnResourcesChanged();
}
@@ -47,7 +48,7 @@ namespace Avalonia.Controls
},
x =>
{
- if (x.Count > 0)
+ if (x.HasResources)
{
OnResourcesChanged();
}
@@ -61,6 +62,12 @@ namespace Avalonia.Controls
}
}
+ ///
+ bool IResourceProvider.HasResources
+ {
+ get => Count > 0 || (_mergedDictionaries?.Any(x => x.HasResources) ?? false);
+ }
+
///
public bool TryGetResource(string key, out object value)
{
diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs
index 6e79ee038e..4182ffada3 100644
--- a/src/Avalonia.Styling/Styling/Style.cs
+++ b/src/Avalonia.Styling/Styling/Style.cs
@@ -79,10 +79,10 @@ namespace Avalonia.Styling
public IList Setters { get; set; } = new List();
///
- bool IResourceNode.HasResources => _resources?.Count > 0;
+ IResourceNode IResourceNode.ResourceParent => _parent;
///
- IResourceNode IResourceNode.ResourceParent => _parent;
+ bool IResourceProvider.HasResources => _resources?.Count > 0;
///
/// Attaches the style to a control if the style's selector matches.
diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index 5c2d228e62..f04f148c5b 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -34,6 +34,7 @@
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/ResourceInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Data/ResourceInclude.cs
new file mode 100644
index 0000000000..035765fae0
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml/Data/ResourceInclude.cs
@@ -0,0 +1,63 @@
+using System;
+using System.ComponentModel;
+using Avalonia.Controls;
+using Portable.Xaml.ComponentModel;
+using Portable.Xaml.Markup;
+
+namespace Avalonia.Markup.Xaml.Data
+{
+ ///
+ /// Loads a resource dictionary from a specified URL.
+ ///
+ public class ResourceInclude : MarkupExtension, IResourceProvider
+ {
+ private Uri _baseUri;
+ private IResourceDictionary _loaded;
+
+ public event EventHandler ResourcesChanged;
+
+ ///
+ /// Gets the loaded resource dictionary.
+ ///
+ public IResourceDictionary Loaded
+ {
+ get
+ {
+ if (_loaded == null)
+ {
+ var loader = new AvaloniaXamlLoader();
+ _loaded = (IResourceDictionary)loader.Load(Source, _baseUri);
+
+ if (_loaded.HasResources)
+ {
+ ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
+ }
+ }
+
+ return _loaded;
+ }
+ }
+
+ ///
+ /// Gets or sets the source URL.
+ ///
+ public Uri Source { get; set; }
+
+ ///
+ bool IResourceProvider.HasResources => Loaded.HasResources;
+
+ ///
+ bool IResourceProvider.TryGetResource(string key, out object value)
+ {
+ return Loaded.TryGetResource(key, out value);
+ }
+
+ ///
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ var tdc = (ITypeDescriptorContext)serviceProvider;
+ _baseUri = tdc?.GetBaseUri();
+ return this;
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
index c1867da9c0..fb308e62b8 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
@@ -52,7 +52,7 @@ namespace Avalonia.Markup.Xaml.Styling
}
///
- bool IResourceNode.HasResources => Loaded.HasResources;
+ bool IResourceProvider.HasResources => Loaded.HasResources;
///
IResourceNode IResourceNode.ResourceParent => _parent;
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/ResourceIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/ResourceIncludeTests.cs
new file mode 100644
index 0000000000..6dc56e425c
--- /dev/null
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/ResourceIncludeTests.cs
@@ -0,0 +1,55 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.UnitTests;
+using Xunit;
+
+namespace Avalonia.Markup.Xaml.UnitTests.Data
+{
+ public class ResourceIncludeTests
+ {
+ public class StaticResourceExtensionTests
+ {
+ [Fact]
+ public void ResourceInclude_Loads_ResourceDictionary()
+ {
+ var includeXaml = @"
+
+ #ff506070
+
+";
+ using (StartWithResources(("test:include.xaml", includeXaml)))
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+
+
+
+";
+
+ var loader = new AvaloniaXamlLoader();
+ var userControl = (UserControl)loader.Load(xaml);
+ var border = userControl.FindControl("border");
+
+ var brush = (SolidColorBrush)border.Background;
+ Assert.Equal(0xff506070, brush.Color.ToUint32());
+ }
+ }
+
+ private IDisposable StartWithResources(params (string, string)[] assets)
+ {
+ var assetLoader = new MockAssetLoader(assets);
+ var services = new TestServices(assetLoader: assetLoader);
+ return UnitTestApplication.Start(services);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs
index c751f9e056..d55e34cbe6 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs
@@ -485,7 +485,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
Assert.Null(border.Background);
- userControl.Resources.MergedDictionaries[0].Add("brush", new SolidColorBrush(0xff506070));
+ ((IResourceDictionary)userControl.Resources.MergedDictionaries[0]).Add("brush", new SolidColorBrush(0xff506070));
var brush = (SolidColorBrush)border.Background;
Assert.NotNull(brush);
diff --git a/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs b/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs
index f31cbbac0a..1eb3cd9750 100644
--- a/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs
+++ b/tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs
@@ -167,7 +167,7 @@ namespace Avalonia.Styling.UnitTests
var raised = false;
target.ResourcesChanged += (_, __) => raised = true;
- target.MergedDictionaries[0].Add("foo", "bar");
+ ((IResourceDictionary)target.MergedDictionaries[0]).Add("foo", "bar");
Assert.True(raised);
}