diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs
index 4b7b7be900..fda97e259c 100644
--- a/src/Avalonia.Controls/Application.cs
+++ b/src/Avalonia.Controls/Application.cs
@@ -49,6 +49,9 @@ namespace Avalonia
OnExit += OnExiting;
}
+ ///
+ public event EventHandler ResourcesChanged;
+
///
/// Gets the current instance of the class.
///
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index b9e5d1c609..81acf1a204 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -154,6 +154,11 @@ namespace Avalonia.Controls
///
public event EventHandler Initialized;
+ ///
+ /// Occurs when a resource in this control or a parent control has changed.
+ ///
+ public event EventHandler ResourcesChanged;
+
///
/// Gets or sets the name of the control.
///
@@ -269,8 +274,22 @@ namespace Avalonia.Controls
///
public Styles Styles
{
- get { return _styles ?? (_styles = new Styles()); }
- set { _styles = value; }
+ get { return _styles ?? (Styles = new Styles()); }
+ set
+ {
+ Contract.Requires(value != null);
+
+ if (_styles != value)
+ {
+ if (_styles != null)
+ {
+ _styles.ResourcesChanged -= StyleResourcesChanged;
+ }
+
+ _styles = value;
+ _styles.ResourcesChanged += StyleResourcesChanged;
+ }
+ }
}
///
@@ -290,7 +309,19 @@ namespace Avalonia.Controls
///
/// Gets or sets the control's resource dictionary.
///
- public IResourceDictionary Resources => _resources ?? (_resources = new ResourceDictionary());
+ public IResourceDictionary Resources
+ {
+ get
+ {
+ if (_resources == null)
+ {
+ _resources = new ResourceDictionary();
+ _resources.CollectionChanged += ResourceDictionaryChanged;
+ }
+
+ return _resources;
+ }
+ }
///
/// Gets or sets a user-defined object attached to the control.
@@ -310,6 +341,32 @@ namespace Avalonia.Controls
internal set { SetValue(TemplatedParentProperty, value); }
}
+ ///
+ /// Gets the control's logical children.
+ ///
+ protected IAvaloniaList LogicalChildren
+ {
+ get
+ {
+ if (_logicalChildren == null)
+ {
+ var list = new AvaloniaList();
+ list.ResetBehavior = ResetBehavior.Remove;
+ list.Validate = ValidateLogicalChild;
+ list.CollectionChanged += LogicalChildrenCollectionChanged;
+ _logicalChildren = list;
+ }
+
+ return _logicalChildren;
+ }
+ }
+
+ ///
+ /// Gets the collection in a form that allows adding and removing
+ /// pseudoclasses.
+ ///
+ protected IPseudoClasses PseudoClasses => Classes;
+
///
/// Gets a value indicating whether the element is attached to a rooted logical tree.
///
@@ -398,32 +455,17 @@ namespace Avalonia.Controls
this.OnDetachedFromLogicalTreeCore(e);
}
- ///
- /// Gets the control's logical children.
- ///
- protected IAvaloniaList LogicalChildren
+ ///
+ void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e)
{
- get
- {
- if (_logicalChildren == null)
- {
- var list = new AvaloniaList();
- list.ResetBehavior = ResetBehavior.Remove;
- list.Validate = ValidateLogicalChild;
- list.CollectionChanged += LogicalChildrenCollectionChanged;
- _logicalChildren = list;
- }
+ ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
- return _logicalChildren;
+ foreach (var child in LogicalChildren)
+ {
+ child.NotifyResourcesChanged(e);
}
}
- ///
- /// Gets the collection in a form that allows adding and removing
- /// pseudoclasses.
- ///
- protected IPseudoClasses PseudoClasses => Classes;
-
///
bool IResourceProvider.TryGetResource(string key, out object value)
{
@@ -857,5 +899,15 @@ namespace Avalonia.Controls
}
}
}
+
+ private void ResourceDictionaryChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
+ }
+
+ private void StyleResourcesChanged(object sender, ResourcesChangedEventArgs e)
+ {
+ ((ILogical)this).NotifyResourcesChanged(e);
+ }
}
}
diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceProvider.cs
index 3e47bf9092..9308b7a18d 100644
--- a/src/Avalonia.Styling/Controls/IResourceProvider.cs
+++ b/src/Avalonia.Styling/Controls/IResourceProvider.cs
@@ -7,6 +7,11 @@ namespace Avalonia.Controls
///
public interface IResourceProvider
{
+ ///
+ /// Raised when the resources in the element are changed.
+ ///
+ event EventHandler ResourcesChanged;
+
///
/// Tries to find a resource within the element.
///
diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs
index 1e35afa771..bd08680a42 100644
--- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs
+++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs
@@ -1,4 +1,7 @@
-using System;
+// 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 System.Collections;
using Avalonia.Collections;
@@ -9,6 +12,7 @@ namespace Avalonia.Controls
///
public class ResourceDictionary : AvaloniaDictionary, IResourceDictionary, IDictionary
{
+ ///
public bool TryGetResource(string key, out object value) => TryGetValue(key, out value);
}
}
diff --git a/src/Avalonia.Styling/Controls/ResourcesChangedEventArgs.cs b/src/Avalonia.Styling/Controls/ResourcesChangedEventArgs.cs
new file mode 100644
index 0000000000..68c7a58ab9
--- /dev/null
+++ b/src/Avalonia.Styling/Controls/ResourcesChangedEventArgs.cs
@@ -0,0 +1,11 @@
+// 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;
+
+namespace Avalonia.Controls
+{
+ public class ResourcesChangedEventArgs : EventArgs
+ {
+ }
+}
diff --git a/src/Avalonia.Styling/LogicalTree/ILogical.cs b/src/Avalonia.Styling/LogicalTree/ILogical.cs
index 006a9f5cc1..a6e804567d 100644
--- a/src/Avalonia.Styling/LogicalTree/ILogical.cs
+++ b/src/Avalonia.Styling/LogicalTree/ILogical.cs
@@ -3,6 +3,7 @@
using System;
using Avalonia.Collections;
+using Avalonia.Controls;
namespace Avalonia.LogicalTree
{
@@ -55,5 +56,15 @@ namespace Avalonia.LogicalTree
/// this method yourself.
///
void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e);
+
+ ///
+ /// Notifies the control that a change has been made to its resources.
+ ///
+ /// 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);
}
}
diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs
index 5dc0409cf0..1ea99a42ac 100644
--- a/src/Avalonia.Styling/Styling/Style.cs
+++ b/src/Avalonia.Styling/Styling/Style.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Collections.Specialized;
using System.Reactive.Linq;
using Avalonia.Controls;
using Avalonia.Metadata;
@@ -16,8 +17,7 @@ namespace Avalonia.Styling
{
private static Dictionary> _applied =
new Dictionary>();
-
- private IResourceDictionary _resources;
+ private ResourceDictionary _resources;
///
/// Initializes a new instance of the class.
@@ -35,6 +35,9 @@ namespace Avalonia.Styling
Selector = selector(null);
}
+ ///
+ public event EventHandler ResourcesChanged;
+
///
/// Gets or sets a dictionary of style resources.
///
@@ -45,6 +48,7 @@ namespace Avalonia.Styling
if (_resources == null)
{
_resources = new ResourceDictionary();
+ _resources.CollectionChanged += ResourceDictionaryChanged;
}
return _resources;
@@ -151,5 +155,10 @@ namespace Avalonia.Styling
_applied.Remove(control);
}
+
+ private void ResourceDictionaryChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
+ }
}
}
diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs
index 690b636b1e..4ce41f6f46 100644
--- a/src/Avalonia.Styling/Styling/Styles.cs
+++ b/src/Avalonia.Styling/Styling/Styles.cs
@@ -1,6 +1,8 @@
// 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 System.Collections.Specialized;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls;
@@ -12,7 +14,19 @@ namespace Avalonia.Styling
///
public class Styles : AvaloniaList, IStyle
{
- private IResourceDictionary _resources;
+ private ResourceDictionary _resources;
+
+ public Styles()
+ {
+ ResetBehavior = ResetBehavior.Remove;
+ this.ForEachItem(
+ x => x.ResourcesChanged += SubResourceChanged,
+ x => x.ResourcesChanged -= SubResourceChanged,
+ () => { });
+ }
+
+ ///
+ public event EventHandler ResourcesChanged;
///
/// Gets or sets a dictionary of style resources.
@@ -24,6 +38,7 @@ namespace Avalonia.Styling
if (_resources == null)
{
_resources = new ResourceDictionary();
+ _resources.CollectionChanged += ResourceDictionaryChanged;
}
return _resources;
@@ -64,5 +79,15 @@ namespace Avalonia.Styling
value = null;
return false;
}
+
+ private void ResourceDictionaryChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
+ }
+
+ private void SubResourceChanged(object sender, ResourcesChangedEventArgs e)
+ {
+ ResourcesChanged?.Invoke(this, e);
+ }
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
index 4bb0913a2e..7921510944 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
@@ -3,6 +3,8 @@
using System;
using System.ComponentModel;
+using System.Reactive;
+using System.Reactive.Linq;
using Avalonia.Controls;
using Avalonia.Data;
using Portable.Xaml;
@@ -49,8 +51,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
if (control != null)
{
- var resource = control.FindResource(ResourceKey);
- return new InstancedBinding(resource);
+ var o = Observable.FromEventPattern(
+ x => control.ResourcesChanged += x,
+ x => control.ResourcesChanged -= x)
+ .StartWith((EventPattern)null)
+ .Select(x => control.FindResource(ResourceKey));
+
+ return new InstancedBinding(o);
}
return null;
diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
index a8fecdc6e1..556e2324cd 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
@@ -25,6 +25,9 @@ namespace Avalonia.Markup.Xaml.Styling
_baseUri = baseUri;
}
+ ///
+ public event EventHandler ResourcesChanged;
+
///
/// Gets or sets the source URL.
///
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs
index a697e18610..46a90635b2 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs
@@ -276,6 +276,82 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
+ [Fact]
+ public void DynamicResource_Tracks_Added_Resource()
+ {
+ var xaml = @"
+
+
+";
+
+ var loader = new AvaloniaXamlLoader();
+ var userControl = (UserControl)loader.Load(xaml);
+ var border = userControl.FindControl("border");
+
+ DelayedBinding.ApplyBindings(border);
+
+ Assert.Null(border.Background);
+
+ userControl.Resources.Add("brush", new SolidColorBrush(0xff506070));
+
+ var brush = (SolidColorBrush)border.Background;
+ Assert.NotNull(brush);
+ Assert.Equal(0xff506070, brush.Color.ToUint32());
+ }
+
+ [Fact]
+ public void DynamicResource_Tracks_Added_Style_Resource()
+ {
+ var xaml = @"
+
+
+";
+
+ var loader = new AvaloniaXamlLoader();
+ var userControl = (UserControl)loader.Load(xaml);
+ var border = userControl.FindControl("border");
+
+ DelayedBinding.ApplyBindings(border);
+
+ Assert.Null(border.Background);
+
+ userControl.Styles.Resources.Add("brush", new SolidColorBrush(0xff506070));
+
+ var brush = (SolidColorBrush)border.Background;
+ Assert.NotNull(brush);
+ Assert.Equal(0xff506070, brush.Color.ToUint32());
+ }
+
+ [Fact]
+ public void DynamicResource_Tracks_Added_Nested_Style_Resource()
+ {
+ var xaml = @"
+
+
+
+
+
+";
+
+ var loader = new AvaloniaXamlLoader();
+ var userControl = (UserControl)loader.Load(xaml);
+ var border = userControl.FindControl("border");
+
+ DelayedBinding.ApplyBindings(border);
+
+ Assert.Null(border.Background);
+
+ ((Style)userControl.Styles[0]).Resources.Add("brush", new SolidColorBrush(0xff506070));
+
+ var brush = (SolidColorBrush)border.Background;
+ Assert.NotNull(brush);
+ Assert.Equal(0xff506070, brush.Color.ToUint32());
+ }
+
[Fact]
public void DynamicResource_Can_Be_Found_Across_Xaml_Style_Files()
{