From fec2823537e88bf79be32ca1d0be1348b63c03a5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 00:34:17 +0200 Subject: [PATCH 01/42] StyleResources -> ResourceDictionary. And move it to the `Avalonia.Controls` namespace. --- .../StyleResources.cs => Controls/ResourceDictionary.cs} | 6 +++--- src/Avalonia.Styling/Styling/Style.cs | 7 ++++--- tests/Avalonia.Styling.UnitTests/ResourceTests.cs | 8 ++++---- 3 files changed, 11 insertions(+), 10 deletions(-) rename src/Avalonia.Styling/{Styling/StyleResources.cs => Controls/ResourceDictionary.cs} (95%) diff --git a/src/Avalonia.Styling/Styling/StyleResources.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs similarity index 95% rename from src/Avalonia.Styling/Styling/StyleResources.cs rename to src/Avalonia.Styling/Controls/ResourceDictionary.cs index e447c6adfd..96f523c83e 100644 --- a/src/Avalonia.Styling/Styling/StyleResources.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -2,12 +2,12 @@ using System.Collections; using System.Collections.Generic; -namespace Avalonia.Styling +namespace Avalonia.Controls { /// - /// Holds resources for a . + /// An indexed dictionary of resources. /// - public class StyleResources : IDictionary, IDictionary + public class ResourceDictionary : IDictionary, IDictionary { private Dictionary _inner = new Dictionary(); diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 3dfd9118af..d7df465722 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reactive.Linq; +using Avalonia.Controls; using Avalonia.Metadata; namespace Avalonia.Styling @@ -16,7 +17,7 @@ namespace Avalonia.Styling private static Dictionary> _applied = new Dictionary>(); - private StyleResources _resources; + private ResourceDictionary _resources; /// /// Initializes a new instance of the class. @@ -37,13 +38,13 @@ namespace Avalonia.Styling /// /// Gets or sets a dictionary of style resources. /// - public StyleResources Resources + public ResourceDictionary Resources { get { if (_resources == null) { - _resources = new StyleResources(); + _resources = new ResourceDictionary(); } return _resources; diff --git a/tests/Avalonia.Styling.UnitTests/ResourceTests.cs b/tests/Avalonia.Styling.UnitTests/ResourceTests.cs index a2535e0fb5..1efd043f6a 100644 --- a/tests/Avalonia.Styling.UnitTests/ResourceTests.cs +++ b/tests/Avalonia.Styling.UnitTests/ResourceTests.cs @@ -20,7 +20,7 @@ namespace Avalonia.Styling.UnitTests { new Style { - Resources = new StyleResources + Resources = new ResourceDictionary { { "Foo", "foo resource" }, { "Bar", "overridden" }, @@ -33,14 +33,14 @@ namespace Avalonia.Styling.UnitTests { new Style { - Resources = new StyleResources + Resources = new ResourceDictionary { { "Bar", "again overridden" }, } }, new Style { - Resources = new StyleResources + Resources = new ResourceDictionary { { "Bar", "bar resource" }, } @@ -64,7 +64,7 @@ namespace Avalonia.Styling.UnitTests { new Style { - Resources = new StyleResources + Resources = new ResourceDictionary { { "Foo", "foo" }, } From 39fc0ccdb5c23d2c973d05d307c2da0ef3b87d64 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 00:58:56 +0200 Subject: [PATCH 02/42] Added IResourceDictionary. --- .../Controls/IResourceDictionary.cs | 13 +++++++++++++ src/Avalonia.Styling/Controls/ResourceDictionary.cs | 2 +- src/Avalonia.Styling/Styling/Style.cs | 7 ++----- 3 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 src/Avalonia.Styling/Controls/IResourceDictionary.cs diff --git a/src/Avalonia.Styling/Controls/IResourceDictionary.cs b/src/Avalonia.Styling/Controls/IResourceDictionary.cs new file mode 100644 index 0000000000..9891249568 --- /dev/null +++ b/src/Avalonia.Styling/Controls/IResourceDictionary.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Avalonia.Controls +{ + /// + /// An indexed dictionary of resources. + /// + public interface IResourceDictionary : IDictionary + { + } +} diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index 96f523c83e..125bb0dcdf 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -7,7 +7,7 @@ namespace Avalonia.Controls /// /// An indexed dictionary of resources. /// - public class ResourceDictionary : IDictionary, IDictionary + public class ResourceDictionary : IResourceDictionary, IDictionary { private Dictionary _inner = new Dictionary(); diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index d7df465722..637e583a25 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -17,7 +17,7 @@ namespace Avalonia.Styling private static Dictionary> _applied = new Dictionary>(); - private ResourceDictionary _resources; + private IResourceDictionary _resources; /// /// Initializes a new instance of the class. @@ -38,7 +38,7 @@ namespace Avalonia.Styling /// /// Gets or sets a dictionary of style resources. /// - public ResourceDictionary Resources + public IResourceDictionary Resources { get { @@ -52,15 +52,12 @@ namespace Avalonia.Styling set { - var resources = Resources; if (!Equals(resources, value)) { foreach (var i in value) { resources[i.Key] = i.Value; - //resources.Add(i.Key, i.Value); - //(resources as IDictionary).Add(i); } } } From b5df0cdcee6f0a7c6c56b767daf9dc60ed0dd82f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 01:03:02 +0200 Subject: [PATCH 03/42] Remove Style.Resources setter. We don't need it. --- src/Avalonia.Styling/Styling/Style.cs | 12 ------------ tests/Avalonia.Styling.UnitTests/ResourceTests.cs | 8 ++++---- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 637e583a25..80238889c8 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -49,18 +49,6 @@ namespace Avalonia.Styling return _resources; } - - set - { - var resources = Resources; - if (!Equals(resources, value)) - { - foreach (var i in value) - { - resources[i.Key] = i.Value; - } - } - } } /// diff --git a/tests/Avalonia.Styling.UnitTests/ResourceTests.cs b/tests/Avalonia.Styling.UnitTests/ResourceTests.cs index 1efd043f6a..bfef1bd762 100644 --- a/tests/Avalonia.Styling.UnitTests/ResourceTests.cs +++ b/tests/Avalonia.Styling.UnitTests/ResourceTests.cs @@ -20,7 +20,7 @@ namespace Avalonia.Styling.UnitTests { new Style { - Resources = new ResourceDictionary + Resources = { { "Foo", "foo resource" }, { "Bar", "overridden" }, @@ -33,14 +33,14 @@ namespace Avalonia.Styling.UnitTests { new Style { - Resources = new ResourceDictionary + Resources = { { "Bar", "again overridden" }, } }, new Style { - Resources = new ResourceDictionary + Resources = { { "Bar", "bar resource" }, } @@ -64,7 +64,7 @@ namespace Avalonia.Styling.UnitTests { new Style { - Resources = new ResourceDictionary + Resources = { { "Foo", "foo" }, } From 84aa27162f33bc80d60a66ad4eca57a1435d2ad4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 02:59:41 +0200 Subject: [PATCH 04/42] Made a start adding Control/Application.Resources. --- samples/ControlCatalog/MainWindow.xaml.cs | 2 +- src/Avalonia.Controls/Application.cs | 19 +++- src/Avalonia.Controls/Control.cs | 15 ++++ src/Avalonia.Controls/ControlExtensions.cs | 29 +++++++ src/Avalonia.Controls/IControl.cs | 2 +- .../Presenters/TextPresenter.cs | 2 +- src/Avalonia.Styling/Avalonia.Styling.csproj | 1 + .../Controls/IResourceDictionary.cs | 17 +++- .../Controls/IResourceHost.cs | 22 +++++ .../Controls/ResourceDictionary.cs | 2 + src/Avalonia.Styling/Styling/IStyle.cs | 13 +-- src/Avalonia.Styling/Styling/IStyleHost.cs | 1 - src/Avalonia.Styling/Styling/Style.cs | 22 +---- .../Styling/StyleExtensions.cs | 39 --------- src/Avalonia.Styling/Styling/Styles.cs | 40 ++++++--- .../Data/StyleResourceBinding.cs | 7 +- .../Styling/StyleInclude.cs | 14 +-- .../ControlTests_Resources.cs | 87 +++++++++++++++++++ .../FullLayoutTests.cs | 4 + .../Xaml/BasicTests.cs | 6 +- .../Xaml/StyleTests.cs | 12 +-- .../ResourceTests.cs | 78 ----------------- tests/Avalonia.UnitTests/TestRoot.cs | 4 + 23 files changed, 249 insertions(+), 189 deletions(-) create mode 100644 src/Avalonia.Styling/Controls/IResourceHost.cs delete mode 100644 src/Avalonia.Styling/Styling/StyleExtensions.cs create mode 100644 tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs delete mode 100644 tests/Avalonia.Styling.UnitTests/ResourceTests.cs diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index 413794dfa2..0d24967482 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -19,7 +19,7 @@ namespace ControlCatalog // so we must refer to this resource DLL statically. For // now I am doing that here. But we need a better solution!! var theme = new Avalonia.Themes.Default.DefaultTheme(); - theme.FindResource("Button"); + theme.TryGetResource("Button"); AvaloniaXamlLoader.Load(this); } } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 3d13608226..0d89ad65b1 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -29,7 +29,7 @@ namespace Avalonia /// method. /// - Tracks the lifetime of the application. /// - public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IApplicationLifecycle + public class Application : IApplicationLifecycle, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceHost { /// /// The application-global data templates. @@ -39,6 +39,7 @@ namespace Avalonia private readonly Lazy _clipboard = new Lazy(() => (IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))); private readonly Styler _styler = new Styler(); + private ResourceDictionary _resources; /// /// Initializes a new instance of the class. @@ -100,6 +101,11 @@ namespace Avalonia /// public IClipboard Clipboard => _clipboard.Value; + /// + /// Gets the application's global resource dictionary. + /// + public IResourceDictionary Resources => _resources ?? (_resources = new ResourceDictionary()); + /// /// Gets the application's global styles. /// @@ -142,13 +148,20 @@ namespace Avalonia { OnExit?.Invoke(this, EventArgs.Empty); } - + + /// + bool IResourceHost.TryGetResource(string key, out object value) + { + value = null; + return _resources?.TryGetResource(key, out value) ?? + Styles.TryGetResource(key, out value); + } + /// /// Sent when the application is exiting. /// public event EventHandler OnExit; - /// /// Called when the application is exiting. /// diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index eca5967a58..ab5d9a7f06 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -97,6 +97,7 @@ namespace Avalonia.Controls private bool _isAttachedToLogicalTree; private IAvaloniaList _logicalChildren; private INameScope _nameScope; + private ResourceDictionary _resources; private Styles _styles; private bool _styled; private Subject _styleDetach = new Subject(); @@ -286,6 +287,11 @@ namespace Avalonia.Controls set { SetValue(ContextMenuProperty, value); } } + /// + /// Gets or sets the control's resource dictionary. + /// + public IResourceDictionary Resources => _resources ?? (_resources = new ResourceDictionary()); + /// /// Gets or sets a user-defined object attached to the control. /// @@ -418,6 +424,15 @@ namespace Avalonia.Controls /// protected IPseudoClasses PseudoClasses => Classes; + /// + bool IResourceHost.TryGetResource(string key, out object value) + { + value = null; + return _resources?.TryGetResource(key, out value) ?? + _styles?.TryGetResource(key, out value) ?? + false; + } + /// /// Sets the control's logical parent. /// diff --git a/src/Avalonia.Controls/ControlExtensions.cs b/src/Avalonia.Controls/ControlExtensions.cs index 60a940627f..d0b8b75e7b 100644 --- a/src/Avalonia.Controls/ControlExtensions.cs +++ b/src/Avalonia.Controls/ControlExtensions.cs @@ -81,6 +81,35 @@ namespace Avalonia.Controls .FirstOrDefault(x => x != null); } + /// + /// Finds the specified resource by searching up the logical tree and then global styles. + /// + /// The control. + /// The resource key. + /// The resource, or null if not found. + public static object FindResource(this IControl control, string key) + { + Contract.Requires(control != null); + Contract.Requires(key != null); + + var current = control as IStyleHost; + + while (current != null) + { + if (current is IResourceHost host) + { + if (host.TryGetResource(key, out var value)) + { + return value; + } + } + + current = current.StylingParent; + } + + return null; + } + /// /// Adds or removes a pseudoclass depending on a boolean value. /// diff --git a/src/Avalonia.Controls/IControl.cs b/src/Avalonia.Controls/IControl.cs index 3f5bd3fcac..02973bb38d 100644 --- a/src/Avalonia.Controls/IControl.cs +++ b/src/Avalonia.Controls/IControl.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls /// /// Interface for Avalonia controls. /// - public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IStyleable, IStyleHost + public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IResourceHost, IStyleable, IStyleHost { /// /// Occurs when the control has finished initialization. diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index a2b5f3f8b4..5f6b3ad4c8 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -115,7 +115,7 @@ namespace Avalonia.Controls.Presenters if (_highlightBrush == null) { - _highlightBrush = (IBrush)this.FindStyleResource("HighlightBrush"); + _highlightBrush = (IBrush)this.FindResource("HighlightBrush"); } foreach (var rect in rects) diff --git a/src/Avalonia.Styling/Avalonia.Styling.csproj b/src/Avalonia.Styling/Avalonia.Styling.csproj index 6bf37b522b..6b606616bb 100644 --- a/src/Avalonia.Styling/Avalonia.Styling.csproj +++ b/src/Avalonia.Styling/Avalonia.Styling.csproj @@ -2,6 +2,7 @@ netstandard1.3 false + Avalonia true diff --git a/src/Avalonia.Styling/Controls/IResourceDictionary.cs b/src/Avalonia.Styling/Controls/IResourceDictionary.cs index 9891249568..bc76d8f60e 100644 --- a/src/Avalonia.Styling/Controls/IResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/IResourceDictionary.cs @@ -1,5 +1,7 @@ -using System; -using System.Collections; +// 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.Generic; namespace Avalonia.Controls @@ -9,5 +11,16 @@ namespace Avalonia.Controls /// public interface IResourceDictionary : IDictionary { + /// + /// 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); } } diff --git a/src/Avalonia.Styling/Controls/IResourceHost.cs b/src/Avalonia.Styling/Controls/IResourceHost.cs new file mode 100644 index 0000000000..6cee7083e0 --- /dev/null +++ b/src/Avalonia.Styling/Controls/IResourceHost.cs @@ -0,0 +1,22 @@ +using System; + +namespace Avalonia.Controls +{ + /// + /// Defines an element that can be queried for resources. + /// + public interface IResourceHost + { + /// + /// 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/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index 125bb0dcdf..58dee17775 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -55,6 +55,8 @@ namespace Avalonia.Controls public bool TryGetValue(string key, out object value) => _inner.TryGetValue(key, out value); + public bool TryGetResource(string key, out object value) => _inner.TryGetValue(key, out value); + bool ICollection>.Contains(KeyValuePair item) { return ((IDictionary)_inner).Contains(item); diff --git a/src/Avalonia.Styling/Styling/IStyle.cs b/src/Avalonia.Styling/Styling/IStyle.cs index 7b8510fe2d..70f6c60d14 100644 --- a/src/Avalonia.Styling/Styling/IStyle.cs +++ b/src/Avalonia.Styling/Styling/IStyle.cs @@ -1,12 +1,14 @@ // 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 Avalonia.Controls; + namespace Avalonia.Styling { /// /// Defines the interface for styles. /// - public interface IStyle + public interface IStyle : IResourceHost { /// /// Attaches the style to a control if the style's selector matches. @@ -16,14 +18,5 @@ namespace Avalonia.Styling /// The control that contains this style. May be null. /// void Attach(IStyleable control, IStyleHost container); - - /// - /// Tries to find a named resource within the style. - /// - /// The resource name. - /// - /// The resource if found, otherwise . - /// - object FindResource(string name); } } diff --git a/src/Avalonia.Styling/Styling/IStyleHost.cs b/src/Avalonia.Styling/Styling/IStyleHost.cs index 8422f18b46..466edc4423 100644 --- a/src/Avalonia.Styling/Styling/IStyleHost.cs +++ b/src/Avalonia.Styling/Styling/IStyleHost.cs @@ -17,6 +17,5 @@ namespace Avalonia.Styling /// Gets the parent style host element. /// IStyleHost StylingParent { get; } - } } diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 80238889c8..5dc0409cf0 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -98,25 +98,11 @@ namespace Avalonia.Styling } } - /// - /// Tries to find a named resource within the style. - /// - /// The resource name. - /// - /// The resource if found, otherwise . - /// - public object FindResource(string name) + /// + public bool TryGetResource(string key, out object result) { - object result = null; - - if (_resources?.TryGetValue(name, out result) == true) - { - return result; - } - else - { - return AvaloniaProperty.UnsetValue; - } + result = null; + return _resources?.TryGetResource(key, out result) ?? false; } /// diff --git a/src/Avalonia.Styling/Styling/StyleExtensions.cs b/src/Avalonia.Styling/Styling/StyleExtensions.cs deleted file mode 100644 index e1073335a0..0000000000 --- a/src/Avalonia.Styling/Styling/StyleExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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.Styling -{ - public static class StyleExtensions - { - /// - /// Tries to find a named style resource. - /// - /// The control from which to find the resource. - /// The resource name. - /// - /// The resource if found, otherwise . - /// - public static object FindStyleResource(this IStyleHost control, string name) - { - Contract.Requires(control != null); - Contract.Requires(name != null); - Contract.Requires(!string.IsNullOrWhiteSpace(name)); - - while (control != null) - { - var result = control.Styles.FindResource(name); - - if (result != AvaloniaProperty.UnsetValue) - { - return result; - } - - control = control.StylingParent; - } - - return AvaloniaProperty.UnsetValue; - } - } -} diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 770ef8344f..75b8fd0c0c 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -3,6 +3,7 @@ using System.Linq; using Avalonia.Collections; +using Avalonia.Controls; namespace Avalonia.Styling { @@ -11,6 +12,24 @@ namespace Avalonia.Styling /// public class Styles : AvaloniaList, IStyle { + private IResourceDictionary _resources; + + /// + /// Gets or sets a dictionary of style resources. + /// + public IResourceDictionary Resources + { + get + { + if (_resources == null) + { + _resources = new ResourceDictionary(); + } + + return _resources; + } + } + /// /// Attaches the style to a control if the style's selector matches. /// @@ -26,26 +45,19 @@ namespace Avalonia.Styling } } - /// - /// Tries to find a named resource within the style. - /// - /// The resource name. - /// - /// The resource if found, otherwise . - /// - public object FindResource(string name) + /// + public bool TryGetResource(string key, out object value) { - foreach (var style in this.Reverse()) + for (var i = Count - 1; i >= 0; --i) { - var result = style.FindResource(name); - - if (result != AvaloniaProperty.UnsetValue) + if (this[i].TryGetResource(key, out value)) { - return result; + return true; } } - return AvaloniaProperty.UnsetValue; + value = null; + return false; } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs b/src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs index c1a895f797..787aebbdc6 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs @@ -46,11 +46,14 @@ namespace Avalonia.Markup.Xaml.Data if (host != null) { - resource = host.FindStyleResource(Name); + resource = host.FindResource(Name); } else if (style != null) { - resource = style.FindResource(Name); + if (!style.TryGetResource(Name, out resource)) + { + resource = AvaloniaProperty.UnsetValue; + } } if (resource != AvaloniaProperty.UnsetValue) diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index 0f4824d493..a8fecdc6e1 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -3,6 +3,7 @@ using Avalonia.Styling; using System; +using Avalonia.Controls; namespace Avalonia.Markup.Xaml.Styling { @@ -55,16 +56,7 @@ namespace Avalonia.Markup.Xaml.Styling } } - /// - /// Tries to find a named resource within the style. - /// - /// The resource name. - /// - /// The resource if found, otherwise . - /// - public object FindResource(string name) - { - return Loaded.FindResource(name); - } + /// + public bool TryGetResource(string key, out object value) => Loaded.TryGetResource(key, out value); } } \ No newline at end of file diff --git a/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs b/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs new file mode 100644 index 0000000000..361806c0a8 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs @@ -0,0 +1,87 @@ +// 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.Styling; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + public class ControlTests_Resources + { + [Fact] + public void FindResource_Should_Find_Control_Resource() + { + var target = new Control + { + Resources = + { + { "foo", "foo-value" }, + } + }; + + Assert.Equal("foo-value", target.FindResource("foo")); + } + + [Fact] + public void FindResource_Should_Find_Control_Resource_In_Parent() + { + Control target; + + var root = new Decorator + { + Resources = + { + { "foo", "foo-value" }, + }, + Child = target = new Control(), + }; + + Assert.Equal("foo-value", target.FindResource("foo")); + } + + [Fact] + public void FindResource_Should_Find_Application_Resource() + { + Control target; + + var app = new Application + { + Resources = + { + { "foo", "foo-value" }, + }, + }; + + var root = new TestRoot + { + Child = target = new Control(), + StylingParent = app, + }; + + Assert.Equal("foo-value", target.FindResource("foo")); + } + + [Fact] + public void FindResource_Should_Find_Style_Resource() + { + var target = new Control + { + Styles = + { + new Style + { + Resources = + { + { "foo", "foo-value" }, + } + } + } + }; + + Assert.Equal("foo-value", target.FindResource("foo")); + } + + } +} diff --git a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs index 6b7c73da2a..006306a3e4 100644 --- a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs +++ b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs @@ -162,6 +162,10 @@ namespace Avalonia.Layout.UnitTests private void RegisterServices() { var globalStyles = new Mock(); + var globalStylesResources = globalStyles.As(); + var outObj = (object)10; + globalStylesResources.Setup(x => x.TryGetResource("FontSizeNormal", out outObj)).Returns(true); + var renderInterface = new Mock(); renderInterface.Setup(x => x.CreateFormattedText( diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 318a98ad43..ce370790c3 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -480,13 +480,13 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.True(style.Resources.Count > 0); - var brush = style.FindResource("Brush") as SolidColorBrush; + style.TryGetResource("Brush", out var brush); Assert.NotNull(brush); - Assert.Equal(Colors.White, brush.Color); + Assert.Equal(Colors.White, ((SolidColorBrush)brush).Color); - var d = (double)style.FindResource("Double"); + style.TryGetResource("Double", out var d); Assert.Equal(10.0, d); } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs index 0c5a89b827..65fc9eaddd 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs @@ -141,7 +141,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml var loader = new AvaloniaXamlLoader(); var window = (Window)loader.Load(xaml); - var brush = (ISolidColorBrush)window.FindStyleResource("brush"); + var brush = (ISolidColorBrush)window.FindResource("brush"); var button = window.FindControl IStyleHost IStyleHost.StylingParent => null; + /// + bool IResourceProvider.HasResources => _resources?.Count > 0; + /// /// Initializes the application by loading XAML etc. /// diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 81acf1a204..c93742b7f6 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -382,6 +382,9 @@ namespace Avalonia.Controls /// IAvaloniaReadOnlyList ILogical.LogicalChildren => LogicalChildren; + /// + bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources; + /// IAvaloniaReadOnlyList IStyleable.Classes => Classes; diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceProvider.cs index 9308b7a18d..e3ea2e1aab 100644 --- a/src/Avalonia.Styling/Controls/IResourceProvider.cs +++ b/src/Avalonia.Styling/Controls/IResourceProvider.cs @@ -12,6 +12,11 @@ namespace Avalonia.Controls /// event EventHandler ResourcesChanged; + /// + /// Gets a value indicating whether the provider has resources. + /// + bool HasResources { get; } + /// /// Tries to find a resource within the element. /// diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 1ea99a42ac..b095f38035 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -66,6 +66,9 @@ namespace Avalonia.Styling [Content] public IList Setters { get; set; } = new List(); + /// + bool IResourceProvider.HasResources => _resources?.Count > 0; + /// /// Attaches the style to a control if the style's selector matches. /// diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 4ce41f6f46..47647d726e 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -20,14 +20,33 @@ namespace Avalonia.Styling { ResetBehavior = ResetBehavior.Remove; this.ForEachItem( - x => x.ResourcesChanged += SubResourceChanged, - x => x.ResourcesChanged -= SubResourceChanged, + x => + { + if (x.HasResources) + { + ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + } + + x.ResourcesChanged += SubResourceChanged; + }, + x => + { + if (x.HasResources) + { + ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + } + + x.ResourcesChanged -= SubResourceChanged; + }, () => { }); } /// public event EventHandler ResourcesChanged; + /// + public bool HasResources => _resources?.Count > 0 || this.Any(x => x.HasResources); + /// /// Gets or sets a dictionary of style resources. /// diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index 556e2324cd..8e571af4a0 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -50,6 +50,9 @@ namespace Avalonia.Markup.Xaml.Styling } } + /// + bool IResourceProvider.HasResources => Loaded.HasResources; + /// public void Attach(IStyleable control, IStyleHost container) { diff --git a/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs b/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs index 4fa00ab171..561cad87c7 100644 --- a/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs +++ b/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; using Avalonia.Styling; using Avalonia.UnitTests; using Xunit; @@ -142,5 +144,112 @@ namespace Avalonia.Controls.UnitTests Assert.Equal("foo-value", target.FindResource("foo")); } + + [Fact] + public void Adding_Resource_Should_Call_Raise_ResourceChanged_On_Logical_Children() + { + Border child; + + var target = new ContentControl + { + Content = child = new Border(), + Template = ContentControlTemplate(), + }; + + var raisedOnTarget = false; + var raisedOnPresenter = false; + var raisedOnChild = false; + + target.Measure(Size.Infinity); + target.ResourcesChanged += (_, __) => raisedOnTarget = true; + target.Presenter.ResourcesChanged += (_, __) => raisedOnPresenter = true; + child.ResourcesChanged += (_, __) => raisedOnChild = true; + + target.Resources.Add("foo", "bar"); + + Assert.True(raisedOnTarget); + Assert.False(raisedOnPresenter); + Assert.True(raisedOnChild); + } + + [Fact] + public void Adding_Resource_To_Styles_Should_Raise_ResourceChanged() + { + var target = new Decorator(); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.Styles.Resources.Add("foo", "bar"); + + Assert.True(raised); + } + + [Fact] + public void Adding_Resource_To_Nested_Style_Should_Raise_ResourceChanged() + { + Style style; + var target = new Decorator + { + Styles = + { + (style = new Style()), + } + }; + + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + style.Resources.Add("foo", "bar"); + + Assert.True(raised); + } + + [Fact] + public void Adding_Style_With_Resource_Should_Raise_ResourceChanged() + { + Style style = new Style + { + Resources = { { "foo", "bar" } }, + }; + + var target = new Decorator(); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.Styles.Add(style); + + Assert.True(raised); + } + + [Fact] + public void Removing_Style_With_Resource_Should_Raise_ResourceChanged() + { + var target = new Decorator + { + Styles = + { + new Style + { + Resources = { { "foo", "bar" } }, + } + } + }; + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.Styles.Clear(); + + Assert.True(raised); + } + + private IControlTemplate ContentControlTemplate() + { + return new FuncControlTemplate(x => + new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty], + }); + } } } From 0e155bd2d4925800ad6fefe32cb4d6e891ba4d4a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Aug 2017 02:50:49 +0200 Subject: [PATCH 22/42] Make dynamic resources work in more situations. Fixes #492 in a fashion: `DynamicResource` now works for this scenario. --- src/Avalonia.Controls/Application.cs | 3 + src/Avalonia.Controls/Control.cs | 10 ++ src/Avalonia.Controls/ControlExtensions.cs | 29 ----- .../Controls/IResourceProvider.cs | 5 + .../Controls/ResourceProviderExtensions.cs | 49 ++++++++ src/Avalonia.Styling/LogicalTree/ILogical.cs | 2 +- .../Styling/ISetStyleParent.cs | 32 +++++ src/Avalonia.Styling/Styling/Style.cs | 23 +++- src/Avalonia.Styling/Styling/Styles.cs | 56 ++++++++- .../DynamicResourceExtension.cs | 16 +-- .../Styling/StyleInclude.cs | 25 +++- .../ControlTests_Resources.cs | 38 ------ .../DynamicResourceExtensionTests.cs | 4 - .../SelectorTests_Child.cs | 5 + .../SelectorTests_Descendent.cs | 5 + .../Avalonia.Styling.UnitTests/StylesTests.cs | 115 ++++++++++++++++++ 16 files changed, 329 insertions(+), 88 deletions(-) create mode 100644 src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs create mode 100644 src/Avalonia.Styling/Styling/ISetStyleParent.cs create mode 100644 tests/Avalonia.Styling.UnitTests/StylesTests.cs diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 4533855b73..ce15c0b9e3 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -128,6 +128,9 @@ namespace Avalonia /// bool IResourceProvider.HasResources => _resources?.Count > 0; + /// + IResourceProvider IResourceProvider.ResourceParent => null; + /// /// Initializes the application by loading XAML etc. /// diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index c93742b7f6..a40b59d60a 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -283,10 +283,17 @@ namespace Avalonia.Controls { if (_styles != null) { + (_styles as ISetStyleParent)?.SetParent(null); _styles.ResourcesChanged -= StyleResourcesChanged; } _styles = value; + + if (value is ISetStyleParent setParent && setParent.ResourceParent == null) + { + setParent.SetParent(this); + } + _styles.ResourcesChanged += StyleResourcesChanged; } } @@ -385,6 +392,9 @@ namespace Avalonia.Controls /// bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources; + /// + IResourceProvider IResourceProvider.ResourceParent => ((IStyleHost)this).StylingParent as IResourceProvider; + /// IAvaloniaReadOnlyList IStyleable.Classes => Classes; diff --git a/src/Avalonia.Controls/ControlExtensions.cs b/src/Avalonia.Controls/ControlExtensions.cs index 87c774dfcf..60a940627f 100644 --- a/src/Avalonia.Controls/ControlExtensions.cs +++ b/src/Avalonia.Controls/ControlExtensions.cs @@ -81,35 +81,6 @@ namespace Avalonia.Controls .FirstOrDefault(x => x != null); } - /// - /// Finds the specified resource by searching up the logical tree and then global styles. - /// - /// The control. - /// The resource key. - /// The resource, or if not found. - public static object FindResource(this IControl control, string key) - { - Contract.Requires(control != null); - Contract.Requires(key != null); - - var current = control as IStyleHost; - - while (current != null) - { - if (current is IResourceProvider host) - { - if (host.TryGetResource(key, out var value)) - { - return value; - } - } - - current = current.StylingParent; - } - - return AvaloniaProperty.UnsetValue; - } - /// /// Adds or removes a pseudoclass depending on a boolean value. /// diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceProvider.cs index e3ea2e1aab..180476b2e4 100644 --- a/src/Avalonia.Styling/Controls/IResourceProvider.cs +++ b/src/Avalonia.Styling/Controls/IResourceProvider.cs @@ -17,6 +17,11 @@ namespace Avalonia.Controls /// bool HasResources { get; } + /// + /// Gets the parent resource provider, if any. + /// + IResourceProvider ResourceParent { get; } + /// /// Tries to find a resource within the element. /// diff --git a/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs new file mode 100644 index 0000000000..45e16438d0 --- /dev/null +++ b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Reactive.Linq; +using System.Text; + +namespace Avalonia.Controls +{ + public static class ResourceProviderExtensions + { + /// + /// Finds the specified resource by searching up the logical tree and then global styles. + /// + /// The control. + /// The resource key. + /// The resource, or if not found. + public static object FindResource(this IResourceProvider control, string key) + { + Contract.Requires(control != null); + Contract.Requires(key != null); + + var current = control; + + while (current != null) + { + if (current is IResourceProvider host) + { + if (host.TryGetResource(key, out var value)) + { + return value; + } + } + + current = current.ResourceParent; + } + + return AvaloniaProperty.UnsetValue; + } + + public static IObservable GetResourceObservable(this IResourceProvider target, string key) + { + return Observable.FromEventPattern( + x => target.ResourcesChanged += x, + x => target.ResourcesChanged -= x) + .StartWith((EventPattern)null) + .Select(x => target.FindResource(key)); + } + } +} diff --git a/src/Avalonia.Styling/LogicalTree/ILogical.cs b/src/Avalonia.Styling/LogicalTree/ILogical.cs index a6e804567d..8ee3c9ea4f 100644 --- a/src/Avalonia.Styling/LogicalTree/ILogical.cs +++ b/src/Avalonia.Styling/LogicalTree/ILogical.cs @@ -58,7 +58,7 @@ namespace Avalonia.LogicalTree void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e); /// - /// Notifies the control that a change has been made to its resources. + /// Notifies the control that a change has been made to resources that apply to it. /// /// The event args. /// diff --git a/src/Avalonia.Styling/Styling/ISetStyleParent.cs b/src/Avalonia.Styling/Styling/ISetStyleParent.cs new file mode 100644 index 0000000000..da5b34798e --- /dev/null +++ b/src/Avalonia.Styling/Styling/ISetStyleParent.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Controls; + +namespace Avalonia.Styling +{ + /// + /// Defines an interface through which a 's parent can be set. + /// + /// + /// You should not usually need to use this interface - it is for internal use only. + /// + public interface ISetStyleParent : IStyle + { + /// + /// Sets the style parent. + /// + /// The parent. + void SetParent(IResourceProvider parent); + + /// + /// Notifies the style that a change has been made to resources that apply to it. + /// + /// 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 b095f38035..54b53868da 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -13,10 +13,11 @@ namespace Avalonia.Styling /// /// Defines a style. /// - public class Style : IStyle + public class Style : IStyle, ISetStyleParent { private static Dictionary> _applied = new Dictionary>(); + private IResourceProvider _parent; private ResourceDictionary _resources; /// @@ -69,6 +70,9 @@ namespace Avalonia.Styling /// bool IResourceProvider.HasResources => _resources?.Count > 0; + /// + IResourceProvider IResourceProvider.ResourceParent => _parent; + /// /// Attaches the style to a control if the style's selector matches. /// @@ -128,6 +132,23 @@ namespace Avalonia.Styling } } + /// + void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e) + { + ResourcesChanged?.Invoke(this, e); + } + + /// + void ISetStyleParent.SetParent(IResourceProvider parent) + { + if (_parent != null && parent != null) + { + throw new InvalidOperationException("The Style already has a parent."); + } + + _parent = parent; + } + private static List GetSubscriptions(IStyleable control) { List subscriptions; diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 47647d726e..43a542d460 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -12,8 +12,9 @@ namespace Avalonia.Styling /// /// A style that consists of a number of child styles. /// - public class Styles : AvaloniaList, IStyle + public class Styles : AvaloniaList, IStyle, ISetStyleParent { + private IResourceProvider _parent; private ResourceDictionary _resources; public Styles() @@ -22,6 +23,12 @@ namespace Avalonia.Styling this.ForEachItem( x => { + if (x.ResourceParent == null && x is ISetStyleParent setParent) + { + setParent.SetParent(this); + setParent.NotifyResourcesChanged(new ResourcesChangedEventArgs()); + } + if (x.HasResources) { ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); @@ -31,6 +38,12 @@ namespace Avalonia.Styling }, x => { + if (x.ResourceParent == this && x is ISetStyleParent setParent) + { + setParent.SetParent(null); + setParent.NotifyResourcesChanged(new ResourcesChangedEventArgs()); + } + if (x.HasResources) { ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); @@ -64,6 +77,9 @@ namespace Avalonia.Styling } } + /// + IResourceProvider IResourceProvider.ResourceParent => _parent; + /// /// Attaches the style to a control if the style's selector matches. /// @@ -99,13 +115,49 @@ namespace Avalonia.Styling return false; } + /// + void ISetStyleParent.SetParent(IResourceProvider parent) + { + if (_parent != null && parent != null) + { + throw new InvalidOperationException("The Style already has a parent."); + } + + _parent = parent; + } + + /// + void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e) + { + ResourcesChanged?.Invoke(this, e); + } + private void ResourceDictionaryChanged(object sender, NotifyCollectionChangedEventArgs e) { - ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + var ev = new ResourcesChangedEventArgs(); + + foreach (var child in this) + { + (child as ISetStyleParent)?.NotifyResourcesChanged(ev); + } + + ResourcesChanged?.Invoke(this, ev); } private void SubResourceChanged(object sender, ResourcesChangedEventArgs e) { + var foundSource = false; + + foreach (var child in this) + { + if (foundSource) + { + (child as ISetStyleParent)?.NotifyResourcesChanged(e); + } + + foundSource |= child == sender; + } + 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 7921510944..905bf16d1f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -15,7 +15,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions { public class DynamicResourceExtension : MarkupExtension, IBinding { - private IControl _anchor; + private IResourceProvider _anchor; public DynamicResourceExtension() { @@ -33,9 +33,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions var context = (ITypeDescriptorContext)serviceProvider; var provideTarget = context.GetService(); - if (!(provideTarget.TargetObject is IControl)) + if (!(provideTarget.TargetObject is IResourceProvider)) { - _anchor = GetAnchor(context); + _anchor = GetAnchor(context); } return this; @@ -47,17 +47,11 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions object anchor, bool enableDataValidation) { - var control = target as IControl ?? _anchor as IControl; + var control = target as IResourceProvider ?? _anchor; if (control != null) { - 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 new InstancedBinding(control.GetResourceObservable(ResourceKey)); } return null; diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index 8e571af4a0..a121cd8c1b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -10,16 +10,16 @@ namespace Avalonia.Markup.Xaml.Styling /// /// Includes a style from a URL. /// - public class StyleInclude : IStyle + public class StyleInclude : IStyle, ISetStyleParent { private Uri _baseUri; private IStyle _loaded; + private IResourceProvider _parent; /// /// Initializes a new instance of the class. /// /// - public StyleInclude(Uri baseUri) { _baseUri = baseUri; @@ -44,6 +44,7 @@ namespace Avalonia.Markup.Xaml.Styling { var loader = new AvaloniaXamlLoader(); _loaded = (IStyle)loader.Load(Source, _baseUri); + (_loaded as ISetStyleParent)?.SetParent(this); } return _loaded; @@ -53,6 +54,9 @@ namespace Avalonia.Markup.Xaml.Styling /// bool IResourceProvider.HasResources => Loaded.HasResources; + /// + IResourceProvider IResourceProvider.ResourceParent => _parent; + /// public void Attach(IStyleable control, IStyleHost container) { @@ -64,5 +68,22 @@ namespace Avalonia.Markup.Xaml.Styling /// public bool TryGetResource(string key, out object value) => Loaded.TryGetResource(key, out value); + + /// + void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e) + { + (Loaded as ISetStyleParent)?.NotifyResourcesChanged(e); + } + + /// + void ISetStyleParent.SetParent(IResourceProvider parent) + { + if (_parent != null && parent != null) + { + throw new InvalidOperationException("The Style already has a parent."); + } + + _parent = parent; + } } } \ No newline at end of file diff --git a/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs b/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs index 561cad87c7..6a6cb48001 100644 --- a/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs +++ b/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs @@ -204,44 +204,6 @@ namespace Avalonia.Controls.UnitTests Assert.True(raised); } - [Fact] - public void Adding_Style_With_Resource_Should_Raise_ResourceChanged() - { - Style style = new Style - { - Resources = { { "foo", "bar" } }, - }; - - var target = new Decorator(); - var raised = false; - - target.ResourcesChanged += (_, __) => raised = true; - target.Styles.Add(style); - - Assert.True(raised); - } - - [Fact] - public void Removing_Style_With_Resource_Should_Raise_ResourceChanged() - { - var target = new Decorator - { - Styles = - { - new Style - { - Resources = { { "foo", "bar" } }, - } - } - }; - var raised = false; - - target.ResourcesChanged += (_, __) => raised = true; - target.Styles.Clear(); - - Assert.True(raised); - } - private IControlTemplate ContentControlTemplate() { return new FuncControlTemplate(x => diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index 46a90635b2..a8e93b29f0 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -360,8 +360,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'> Red - Green - Blue "; var style2Xaml = @" @@ -369,8 +367,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'> - - "; using (StyledWindow( diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs index 50b4828e73..352efe5358 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs @@ -154,6 +154,11 @@ namespace Avalonia.Styling.UnitTests { throw new NotImplementedException(); } + + public void NotifyResourcesChanged(ResourcesChangedEventArgs e) + { + throw new NotImplementedException(); + } } public class TestLogical1 : TestLogical diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs index 7cf8c3dd1c..c413904c8f 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs @@ -184,6 +184,11 @@ namespace Avalonia.Styling.UnitTests { throw new NotImplementedException(); } + + public void NotifyResourcesChanged(ResourcesChangedEventArgs e) + { + throw new NotImplementedException(); + } } public class TestLogical1 : TestLogical diff --git a/tests/Avalonia.Styling.UnitTests/StylesTests.cs b/tests/Avalonia.Styling.UnitTests/StylesTests.cs new file mode 100644 index 0000000000..c033dad0c6 --- /dev/null +++ b/tests/Avalonia.Styling.UnitTests/StylesTests.cs @@ -0,0 +1,115 @@ +// 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 Xunit; + +namespace Avalonia.Styling.UnitTests +{ + public class StylesTests + { + [Fact] + public void Adding_Style_With_Resources_Should_Raise_ResourceChanged() + { + var style = new Style + { + Resources = { { "foo", "bar" } }, + }; + + var target = new Styles(); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.Add(style); + + Assert.True(raised); + } + + [Fact] + public void Removing_Style_With_Resources_Should_Raise_ResourceChanged() + { + var target = new Styles + { + new Style + { + Resources = { { "foo", "bar" } }, + } + }; + + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.Clear(); + + Assert.True(raised); + } + + [Fact] + public void Adding_Style_Without_Resources_Should_Not_Raise_ResourceChanged() + { + var style = new Style(); + var target = new Styles(); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + target.Add(style); + + Assert.False(raised); + } + + [Fact] + public void Adding_Resource_Should_Raise_Child_ResourceChanged() + { + Style child; + var target = new Styles + { + (child = new Style()), + }; + + var raised = false; + + child.ResourcesChanged += (_, __) => raised = true; + target.Resources.Add("foo", "bar"); + + Assert.True(raised); + } + + [Fact] + public void Adding_Resource_To_Younger_Sibling_Style_Should_Raise_ResourceChanged() + { + Style style1; + Style style2; + var target = new Styles + { + (style1 = new Style()), + (style2 = new Style()), + }; + + var raised = false; + + style2.ResourcesChanged += (_, __) => raised = true; + style1.Resources.Add("foo", "bar"); + + Assert.True(raised); + } + + [Fact] + public void Adding_Resource_To_Older_Sibling_Style_Should_Raise_ResourceChanged() + { + Style style1; + Style style2; + var target = new Styles + { + (style1 = new Style()), + (style2 = new Style()), + }; + + var raised = false; + + style1.ResourcesChanged += (_, __) => raised = true; + style2.Resources.Add("foo", "bar"); + + Assert.False(raised); + } + } +} From 71e8b4fc69415c2aa3a90dccd3fe225e2a66fc2b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Aug 2017 11:21:57 +0200 Subject: [PATCH 23/42] Update dynamic resources on set parent. --- src/Avalonia.Controls/Control.cs | 1 + .../DynamicResourceExtensionTests.cs | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index a40b59d60a..c9a3b2940d 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -521,6 +521,7 @@ namespace Avalonia.Controls } _parent = (IControl)parent; + ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot) { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index a8e93b29f0..1a036546b4 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -393,6 +393,38 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void Control_Property_Is_Updated_When_Parent_Is_Changed() + { + var xaml = @" + + + #ff506070 + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + DelayedBinding.ApplyBindings(border); + + var brush = (SolidColorBrush)border.Background; + Assert.Equal(0xff506070, brush.Color.ToUint32()); + + userControl.Content = null; + + Assert.Null(border.Background); + + userControl.Content = border; + + brush = (SolidColorBrush)border.Background; + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + private IDisposable StyledWindow(params (string, string)[] assets) { var services = TestServices.StyledWindow.With( From a0bda15576424eaa9b0b649685c7bdd50d028f4e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Aug 2017 11:30:27 +0200 Subject: [PATCH 24/42] More resource extension tests. One failing. --- .../DynamicResourceExtensionTests.cs | 22 +++++++++ .../StaticResourceExtensionTests.cs | 47 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index 1a036546b4..f9c78eb80c 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -40,6 +40,28 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(0xff506070, brush.Color.ToUint32()); } + [Fact] + public void DynamicResource_Can_Be_Assigned_To_Attached_Property() + { + var xaml = @" + + + 5 + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + DelayedBinding.ApplyBindings(border); + + Assert.Equal(5, Grid.GetColumn(border)); + } + [Fact] public void DynamicResource_From_Style_Can_Be_Assigned_To_Property() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs index cccbf4d0b5..fc30b0dd6d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs @@ -37,6 +37,27 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(0xff506070, brush.Color.ToUint32()); } + + [Fact] + public void StaticResource_Can_Be_Assigned_To_Attached_Property() + { + var xaml = @" + + + 5 + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + Assert.Equal(5, Grid.GetColumn(border)); + } + [Fact] public void StaticResource_From_Style_Can_Be_Assigned_To_Property() { @@ -290,6 +311,32 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed() + { + var xaml = @" + + + #ff506070 + + + +"; + + 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()); + + userControl.Content = null; + + brush = (SolidColorBrush)border.Background; + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + private IDisposable StyledWindow(params (string, string)[] assets) { var services = TestServices.StyledWindow.With( From 9a74e8409ea1c5305c4b5ea481dfc4b359e6159a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Aug 2017 13:19:23 +0200 Subject: [PATCH 25/42] Ensure properties registered. When calling `AvaloniaPropertyRegistry.GetAttached`. --- src/Avalonia.Base/AvaloniaPropertyRegistry.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index 190b3ee2be..ec1643427b 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -47,6 +47,9 @@ namespace Avalonia { Dictionary inner; + // Ensure the type's static ctor has been run. + RuntimeHelpers.RunClassConstructor(ownerType.TypeHandle); + if (_attached.TryGetValue(ownerType, out inner)) { return inner.Values; From fcee846228d4c0d957a24913cf3ecdb52935ff02 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Aug 2017 19:27:59 +0200 Subject: [PATCH 26/42] Added tests for #1020 Passing. Closes #1020. --- .../DynamicResourceExtensionTests.cs | 25 +++++++++++++++++++ .../StaticResourceExtensionTests.cs | 23 ++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index f9c78eb80c..7de60a1029 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -298,6 +298,31 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(0xff506070, brush.Color.ToUint32()); } + + [Fact] + public void DynamicResource_Can_Be_Assigned_To_ItemTemplate_Property() + { + var xaml = @" + + + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var listBox = userControl.FindControl("listBox"); + + DelayedBinding.ApplyBindings(listBox); + + Assert.NotNull(listBox.ItemTemplate); + } + [Fact] public void DynamicResource_Tracks_Added_Resource() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs index fc30b0dd6d..4f14f1eba8 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs @@ -37,7 +37,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(0xff506070, brush.Color.ToUint32()); } - [Fact] public void StaticResource_Can_Be_Assigned_To_Attached_Property() { @@ -311,6 +310,28 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void StaticResource_Can_Be_Assigned_To_ItemTemplate_Property() + { + var xaml = @" + + + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var listBox = userControl.FindControl("listBox"); + + Assert.NotNull(listBox.ItemTemplate); + } + [Fact] public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed() { From 2c1efe3773b95a7c7ec59b97c4e75192a96b72dc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Aug 2017 22:57:04 +0200 Subject: [PATCH 27/42] Static resource as binding converter Enable using a `StaticResource` as a binding converter. Fixes #818. --- .../DynamicResourceExtension.cs | 7 +++-- .../StaticResourceExtension.cs | 27 +++++++++++-------- .../StaticResourceExtensionTests.cs | 27 +++++++++++++++++++ .../MarkupExtensions/TestValueConverter.cs | 20 ++++++++++++++ 4 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/TestValueConverter.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs index 905bf16d1f..7937be60aa 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -3,7 +3,7 @@ using System; using System.ComponentModel; -using System.Reactive; +using System.Linq; using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Data; @@ -62,7 +62,10 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions var schemaContext = context.GetService()?.SchemaContext; var ambientProvider = context.GetService(); var xamlType = schemaContext.GetXamlType(typeof(T)); - return ambientProvider.GetFirstAmbientValue(xamlType) as T; + + // We override XamlType.CanAssignTo in BindingXamlType so the results we get back + // from GetAllAmbientValues aren't necessarily of the correct type. + return ambientProvider.GetAllAmbientValues(xamlType).OfType().FirstOrDefault(); } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index f92eca25bf..e3cd7b062b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -32,7 +32,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions var schemaContext = context.GetService()?.SchemaContext; var ambientProvider = context.GetService(); var resourceProviderType = schemaContext.GetXamlType(typeof(IResourceProvider)); - var resourceProviders = ambientProvider.GetAllAmbientValues(resourceProviderType); + var ambientValues = ambientProvider.GetAllAmbientValues(resourceProviderType); // Look upwards though the ambient context for IResourceProviders which might be able // to give us the resource. @@ -43,18 +43,23 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions // // StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File // - foreach (IResourceProvider resourceProvider in resourceProviders) + foreach (var ambientValue in ambientValues) { - if (resourceProvider is IControl control && control.StylingParent != null) + // We override XamlType.CanAssignTo in BindingXamlType so the results we get back + // from GetAllAmbientValues aren't necessarily of the correct type. + if (ambientValue is IResourceProvider resourceProvider) { - // If we've got to a control that has a StylingParent then it's probably - // a top level control and its StylingParent is pointing to the global - // styles. If this is case just do a FindResource on it. - return control.FindResource(ResourceKey); - } - else if (resourceProvider.TryGetResource(ResourceKey, out var value)) - { - return value; + if (resourceProvider is IControl control && control.StylingParent != null) + { + // If we've got to a control that has a StylingParent then it's probably + // a top level control and its StylingParent is pointing to the global + // styles. If this is case just do a FindResource on it. + return control.FindResource(ResourceKey); + } + else if (resourceProvider.TryGetResource(ResourceKey, out var value)) + { + return value; + } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs index 4f14f1eba8..ed3ab3cc52 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs @@ -332,6 +332,33 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.NotNull(listBox.ItemTemplate); } + [Fact] + public void StaticResource_Can_Be_Assigned_To_Converter() + { + using (StyledWindow()) + { + var xaml = @" + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + window.DataContext = "foo"; + window.ApplyTemplate(); + + Assert.Equal("foobar", textBlock.Text); + } + } + [Fact] public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/TestValueConverter.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/TestValueConverter.cs new file mode 100644 index 0000000000..57570d8f5c --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/TestValueConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; + +namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions +{ + public class TestValueConverter : IValueConverter + { + public string Append { get; set; } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value.ToString() + Append; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} From ea626a0e19cbec2c43b2d9fd859a9db2235a5376 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Aug 2017 23:48:14 +0200 Subject: [PATCH 28/42] Updated ncrunch config --- .../Avalonia.Direct2D1.RenderTests.v3.ncrunchproject | 1 + ...onia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject | 9 +++++++++ .ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject | 1 + src/Avalonia.Controls/Control.cs | 2 +- 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 .ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject b/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject index 235da29767..a8c3abe8f2 100644 --- a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject @@ -1,6 +1,7 @@  1000 + True True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject new file mode 100644 index 0000000000..15d9efad87 --- /dev/null +++ b/.ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject @@ -0,0 +1,9 @@ + + + + + Avalonia.Markup.UnitTests.Data.Plugins.DataAnnotationsValidationPluginTests.Produces_Aggregate_BindingNotificationsx + + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject b/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject index 235da29767..a8c3abe8f2 100644 --- a/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject @@ -1,6 +1,7 @@  1000 + True True \ No newline at end of file diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index c9a3b2940d..a2fc188000 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -292,7 +292,7 @@ namespace Avalonia.Controls if (value is ISetStyleParent setParent && setParent.ResourceParent == null) { setParent.SetParent(this); - } + } _styles.ResourcesChanged += StyleResourcesChanged; } From ac8cc99f16ce6491a859e3008ad460d8ab78012e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Aug 2017 00:54:26 +0200 Subject: [PATCH 29/42] 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); + } + } +} From e81b22b9d244ca4c10aa2db7d1e0df7833eed8f2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Aug 2017 00:55:06 +0200 Subject: [PATCH 30/42] IResourceProvider -> IResourceNode --- src/Avalonia.Controls/Application.cs | 8 ++++---- src/Avalonia.Controls/Control.cs | 6 +++--- src/Avalonia.Controls/IControl.cs | 2 +- .../Controls/{IResourceProvider.cs => IResourceNode.cs} | 4 ++-- .../Controls/ResourceProviderExtensions.cs | 6 +++--- src/Avalonia.Styling/Styling/ISetStyleParent.cs | 2 +- src/Avalonia.Styling/Styling/IStyle.cs | 2 +- src/Avalonia.Styling/Styling/Style.cs | 8 ++++---- src/Avalonia.Styling/Styling/Styles.cs | 6 +++--- .../MarkupExtensions/DynamicResourceExtension.cs | 8 ++++---- .../MarkupExtensions/StaticResourceExtension.cs | 4 ++-- src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs | 8 ++++---- tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs | 2 +- 13 files changed, 33 insertions(+), 33 deletions(-) rename src/Avalonia.Styling/Controls/{IResourceProvider.cs => IResourceNode.cs} (92%) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index ce15c0b9e3..5102c9a952 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -29,7 +29,7 @@ namespace Avalonia /// method. /// - Tracks the lifetime of the application. /// - public class Application : IApplicationLifecycle, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceProvider + public class Application : IApplicationLifecycle, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode { /// /// The application-global data templates. @@ -126,10 +126,10 @@ namespace Avalonia IStyleHost IStyleHost.StylingParent => null; /// - bool IResourceProvider.HasResources => _resources?.Count > 0; + bool IResourceNode.HasResources => _resources?.Count > 0; /// - IResourceProvider IResourceProvider.ResourceParent => null; + IResourceNode IResourceNode.ResourceParent => null; /// /// Initializes the application by loading XAML etc. @@ -159,7 +159,7 @@ namespace Avalonia } /// - bool IResourceProvider.TryGetResource(string key, out object value) + bool IResourceNode.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 889edd48f8..a05dccec58 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -390,10 +390,10 @@ namespace Avalonia.Controls IAvaloniaReadOnlyList ILogical.LogicalChildren => LogicalChildren; /// - bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources; + bool IResourceNode.HasResources => _resources?.Count > 0 || Styles.HasResources; /// - IResourceProvider IResourceProvider.ResourceParent => ((IStyleHost)this).StylingParent as IResourceProvider; + IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode; /// IAvaloniaReadOnlyList IStyleable.Classes => Classes; @@ -480,7 +480,7 @@ namespace Avalonia.Controls } /// - bool IResourceProvider.TryGetResource(string key, out object value) + bool IResourceNode.TryGetResource(string key, out object value) { value = null; return (_resources?.TryGetResource(key, out value) ?? false) || diff --git a/src/Avalonia.Controls/IControl.cs b/src/Avalonia.Controls/IControl.cs index 37ad12cf91..a5730bf398 100644 --- a/src/Avalonia.Controls/IControl.cs +++ b/src/Avalonia.Controls/IControl.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls /// /// Interface for Avalonia controls. /// - public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IResourceProvider, IStyleable, IStyleHost + public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IResourceNode, IStyleable, IStyleHost { /// /// Occurs when the control has finished initialization. diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceNode.cs similarity index 92% rename from src/Avalonia.Styling/Controls/IResourceProvider.cs rename to src/Avalonia.Styling/Controls/IResourceNode.cs index e133427350..54cf39e3b5 100644 --- a/src/Avalonia.Styling/Controls/IResourceProvider.cs +++ b/src/Avalonia.Styling/Controls/IResourceNode.cs @@ -5,7 +5,7 @@ namespace Avalonia.Controls /// /// Defines an element that can be queried for resources. /// - public interface IResourceProvider + public interface IResourceNode { /// /// Raised when resources in the element are changed. @@ -20,7 +20,7 @@ namespace Avalonia.Controls /// /// Gets the parent resource provider, if any. /// - IResourceProvider ResourceParent { get; } + IResourceNode ResourceParent { get; } /// /// Tries to find a resource within the element. diff --git a/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs index 45e16438d0..c96e8ea7f3 100644 --- a/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs +++ b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls /// The control. /// The resource key. /// The resource, or if not found. - public static object FindResource(this IResourceProvider control, string key) + public static object FindResource(this IResourceNode control, string key) { Contract.Requires(control != null); Contract.Requires(key != null); @@ -23,7 +23,7 @@ namespace Avalonia.Controls while (current != null) { - if (current is IResourceProvider host) + if (current is IResourceNode host) { if (host.TryGetResource(key, out var value)) { @@ -37,7 +37,7 @@ namespace Avalonia.Controls return AvaloniaProperty.UnsetValue; } - public static IObservable GetResourceObservable(this IResourceProvider target, string key) + public static IObservable GetResourceObservable(this IResourceNode target, string key) { return Observable.FromEventPattern( x => target.ResourcesChanged += x, diff --git a/src/Avalonia.Styling/Styling/ISetStyleParent.cs b/src/Avalonia.Styling/Styling/ISetStyleParent.cs index da5b34798e..9f5855b401 100644 --- a/src/Avalonia.Styling/Styling/ISetStyleParent.cs +++ b/src/Avalonia.Styling/Styling/ISetStyleParent.cs @@ -17,7 +17,7 @@ namespace Avalonia.Styling /// Sets the style parent. /// /// The parent. - void SetParent(IResourceProvider parent); + void SetParent(IResourceNode parent); /// /// Notifies the style that a change has been made to resources that apply to it. diff --git a/src/Avalonia.Styling/Styling/IStyle.cs b/src/Avalonia.Styling/Styling/IStyle.cs index aa8980ddde..5f12763825 100644 --- a/src/Avalonia.Styling/Styling/IStyle.cs +++ b/src/Avalonia.Styling/Styling/IStyle.cs @@ -8,7 +8,7 @@ namespace Avalonia.Styling /// /// Defines the interface for styles. /// - public interface IStyle : IResourceProvider + public interface IStyle : IResourceNode { /// /// Attaches the style to a control if the style's selector matches. diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 54b53868da..a0d4c8c087 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -17,7 +17,7 @@ namespace Avalonia.Styling { private static Dictionary> _applied = new Dictionary>(); - private IResourceProvider _parent; + private IResourceNode _parent; private ResourceDictionary _resources; /// @@ -68,10 +68,10 @@ namespace Avalonia.Styling public IList Setters { get; set; } = new List(); /// - bool IResourceProvider.HasResources => _resources?.Count > 0; + bool IResourceNode.HasResources => _resources?.Count > 0; /// - IResourceProvider IResourceProvider.ResourceParent => _parent; + IResourceNode IResourceNode.ResourceParent => _parent; /// /// Attaches the style to a control if the style's selector matches. @@ -139,7 +139,7 @@ namespace Avalonia.Styling } /// - void ISetStyleParent.SetParent(IResourceProvider parent) + void ISetStyleParent.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 43a542d460..767d5ce463 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -14,7 +14,7 @@ namespace Avalonia.Styling /// public class Styles : AvaloniaList, IStyle, ISetStyleParent { - private IResourceProvider _parent; + private IResourceNode _parent; private ResourceDictionary _resources; public Styles() @@ -78,7 +78,7 @@ namespace Avalonia.Styling } /// - IResourceProvider IResourceProvider.ResourceParent => _parent; + IResourceNode IResourceNode.ResourceParent => _parent; /// /// Attaches the style to a control if the style's selector matches. @@ -116,7 +116,7 @@ namespace Avalonia.Styling } /// - void ISetStyleParent.SetParent(IResourceProvider parent) + void ISetStyleParent.SetParent(IResourceNode parent) { if (_parent != null && parent != null) { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs index 7937be60aa..5e421b7e73 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -15,7 +15,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions { public class DynamicResourceExtension : MarkupExtension, IBinding { - private IResourceProvider _anchor; + private IResourceNode _anchor; public DynamicResourceExtension() { @@ -33,9 +33,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions var context = (ITypeDescriptorContext)serviceProvider; var provideTarget = context.GetService(); - if (!(provideTarget.TargetObject is IResourceProvider)) + if (!(provideTarget.TargetObject is IResourceNode)) { - _anchor = GetAnchor(context); + _anchor = GetAnchor(context); } return this; @@ -47,7 +47,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions object anchor, bool enableDataValidation) { - var control = target as IResourceProvider ?? _anchor; + var control = target as IResourceNode ?? _anchor; if (control != null) { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index e3cd7b062b..4764677ede 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -31,7 +31,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions var context = (ITypeDescriptorContext)serviceProvider; var schemaContext = context.GetService()?.SchemaContext; var ambientProvider = context.GetService(); - var resourceProviderType = schemaContext.GetXamlType(typeof(IResourceProvider)); + var resourceProviderType = schemaContext.GetXamlType(typeof(IResourceNode)); var ambientValues = ambientProvider.GetAllAmbientValues(resourceProviderType); // Look upwards though the ambient context for IResourceProviders which might be able @@ -47,7 +47,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions { // We override XamlType.CanAssignTo in BindingXamlType so the results we get back // from GetAllAmbientValues aren't necessarily of the correct type. - if (ambientValue is IResourceProvider resourceProvider) + if (ambientValue is IResourceNode resourceProvider) { if (resourceProvider is IControl control && control.StylingParent != null) { diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index a121cd8c1b..c1867da9c0 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -14,7 +14,7 @@ namespace Avalonia.Markup.Xaml.Styling { private Uri _baseUri; private IStyle _loaded; - private IResourceProvider _parent; + private IResourceNode _parent; /// /// Initializes a new instance of the class. @@ -52,10 +52,10 @@ namespace Avalonia.Markup.Xaml.Styling } /// - bool IResourceProvider.HasResources => Loaded.HasResources; + bool IResourceNode.HasResources => Loaded.HasResources; /// - IResourceProvider IResourceProvider.ResourceParent => _parent; + IResourceNode IResourceNode.ResourceParent => _parent; /// public void Attach(IStyleable control, IStyleHost container) @@ -76,7 +76,7 @@ namespace Avalonia.Markup.Xaml.Styling } /// - void ISetStyleParent.SetParent(IResourceProvider parent) + void ISetStyleParent.SetParent(IResourceNode parent) { if (_parent != null && parent != null) { diff --git a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs index 8bf6102f27..98aaa29261 100644 --- a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs +++ b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs @@ -162,7 +162,7 @@ namespace Avalonia.Layout.UnitTests private void RegisterServices() { var globalStyles = new Mock(); - var globalStylesResources = globalStyles.As(); + var globalStylesResources = globalStyles.As(); var outObj = (object)10; globalStylesResources.Setup(x => x.TryGetResource("FontSizeNormal", out outObj)).Returns(true); From 0a6216a35226afb3c8884346220f41b1867f3ba2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Aug 2017 01:15:12 +0200 Subject: [PATCH 31/42] Doc comments. --- src/Avalonia.Styling/Controls/IResourceNode.cs | 4 ++-- src/Avalonia.Styling/Controls/ResourceDictionary.cs | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Styling/Controls/IResourceNode.cs b/src/Avalonia.Styling/Controls/IResourceNode.cs index 54cf39e3b5..6ff5fb8d2f 100644 --- a/src/Avalonia.Styling/Controls/IResourceNode.cs +++ b/src/Avalonia.Styling/Controls/IResourceNode.cs @@ -13,12 +13,12 @@ namespace Avalonia.Controls event EventHandler ResourcesChanged; /// - /// Gets a value indicating whether the provider has resources. + /// Gets a value indicating whether the node has resources. /// bool HasResources { get; } /// - /// Gets the parent resource provider, if any. + /// Gets the parent resource node, if any. /// IResourceNode ResourceParent { get; } diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index 975d27c08b..30fd8056f1 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -15,13 +15,18 @@ namespace Avalonia.Controls { private AvaloniaList _mergedDictionaries; - public event EventHandler ResourcesChanged; - + /// + /// Initializes a new instance of the class. + /// public ResourceDictionary() { CollectionChanged += OnCollectionChanged; } + /// + public event EventHandler ResourcesChanged; + + /// public IList MergedDictionaries { get From 91387a74e1ff158b67314c9273842147b958315b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Aug 2017 02:00:39 +0200 Subject: [PATCH 32/42] Start testing MergedDictionaries. --- src/Avalonia.Controls/Application.cs | 27 ++++++++- src/Avalonia.Controls/Control.cs | 23 ++++++-- .../Properties/AssemblyInfo.cs | 2 + src/Avalonia.Styling/Styling/Style.cs | 27 ++++++--- src/Avalonia.Styling/Styling/Styles.cs | 32 ++++++---- .../StaticResourceExtensionTests.cs | 58 +++++++++++++++++++ 6 files changed, 141 insertions(+), 28 deletions(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 5102c9a952..8c5f9abb8d 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -39,7 +39,7 @@ namespace Avalonia private readonly Lazy _clipboard = new Lazy(() => (IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))); private readonly Styler _styler = new Styler(); - private ResourceDictionary _resources; + private IResourceDictionary _resources; /// /// Initializes a new instance of the class. @@ -107,7 +107,30 @@ namespace Avalonia /// /// Gets the application's global resource dictionary. /// - public IResourceDictionary Resources => _resources ?? (_resources = new ResourceDictionary()); + public IResourceDictionary Resources + { + get => _resources ?? (Resources = new ResourceDictionary()); + set + { + Contract.Requires(value != null); + + var hadResources = false; + + if (_resources != null) + { + hadResources = _resources.Count > 0; + _resources.ResourcesChanged -= ResourcesChanged; + } + + _resources = value; + _resources.ResourcesChanged += ResourcesChanged; + + if (hadResources || _resources.Count > 0) + { + ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + } + } + } /// /// Gets the application's global styles. diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index a05dccec58..bde9bb6760 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -97,7 +97,7 @@ namespace Avalonia.Controls private bool _isAttachedToLogicalTree; private IAvaloniaList _logicalChildren; private INameScope _nameScope; - private ResourceDictionary _resources; + private IResourceDictionary _resources; private Styles _styles; private bool _styled; private Subject _styleDetach = new Subject(); @@ -318,15 +318,26 @@ namespace Avalonia.Controls /// public IResourceDictionary Resources { - get + get => _resources ?? (Resources = new ResourceDictionary()); + set { - if (_resources == null) + Contract.Requires(value != null); + + var hadResources = false; + + if (_resources != null) { - _resources = new ResourceDictionary(); - _resources.ResourcesChanged += ThisResourcesChanged; + hadResources = _resources.Count > 0; + _resources.ResourcesChanged -= ThisResourcesChanged; } - return _resources; + _resources = value; + _resources.ResourcesChanged += ThisResourcesChanged; + + if (hadResources || _resources.Count > 0) + { + ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); + } } } diff --git a/src/Avalonia.Styling/Properties/AssemblyInfo.cs b/src/Avalonia.Styling/Properties/AssemblyInfo.cs index b53681aeed..0a639139f7 100644 --- a/src/Avalonia.Styling/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Styling/Properties/AssemblyInfo.cs @@ -6,5 +6,7 @@ using System.Runtime.CompilerServices; using Avalonia.Metadata; [assembly: AssemblyTitle("Avalonia.Styling")] +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.LogicalTree")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Styling")] [assembly: InternalsVisibleTo("Avalonia.Styling.UnitTests")] \ No newline at end of file diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index a0d4c8c087..6e79ee038e 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -18,7 +18,7 @@ namespace Avalonia.Styling private static Dictionary> _applied = new Dictionary>(); private IResourceNode _parent; - private ResourceDictionary _resources; + private IResourceDictionary _resources; /// /// Initializes a new instance of the class. @@ -44,15 +44,26 @@ namespace Avalonia.Styling /// public IResourceDictionary Resources { - get + get => _resources ?? (Resources = new ResourceDictionary()); + set { - if (_resources == null) + Contract.Requires(value != null); + + var hadResources = false; + + if (_resources != null) { - _resources = new ResourceDictionary(); - _resources.CollectionChanged += ResourceDictionaryChanged; + hadResources = _resources.Count > 0; + _resources.ResourcesChanged -= ResourceDictionaryChanged; } - return _resources; + _resources = value; + _resources.ResourcesChanged += ResourceDictionaryChanged; + + if (hadResources || _resources.Count > 0) + { + ((ISetStyleParent)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); + } } } @@ -180,9 +191,9 @@ namespace Avalonia.Styling _applied.Remove(control); } - private void ResourceDictionaryChanged(object sender, NotifyCollectionChangedEventArgs e) + private void ResourceDictionaryChanged(object sender, ResourcesChangedEventArgs e) { - ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + ResourcesChanged?.Invoke(this, e); } } } diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 767d5ce463..714e7f6def 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -2,7 +2,6 @@ // 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; @@ -15,7 +14,7 @@ namespace Avalonia.Styling public class Styles : AvaloniaList, IStyle, ISetStyleParent { private IResourceNode _parent; - private ResourceDictionary _resources; + private IResourceDictionary _resources; public Styles() { @@ -65,15 +64,26 @@ namespace Avalonia.Styling /// public IResourceDictionary Resources { - get + get => _resources ?? (Resources = new ResourceDictionary()); + set { - if (_resources == null) + Contract.Requires(value != null); + + var hadResources = false; + + if (_resources != null) { - _resources = new ResourceDictionary(); - _resources.CollectionChanged += ResourceDictionaryChanged; + hadResources = _resources.Count > 0; + _resources.ResourcesChanged -= ResourceDictionaryChanged; } - return _resources; + _resources = value; + _resources.ResourcesChanged += ResourceDictionaryChanged; + + if (hadResources || _resources.Count > 0) + { + ((ISetStyleParent)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); + } } } @@ -132,16 +142,14 @@ namespace Avalonia.Styling ResourcesChanged?.Invoke(this, e); } - private void ResourceDictionaryChanged(object sender, NotifyCollectionChangedEventArgs e) + private void ResourceDictionaryChanged(object sender, ResourcesChangedEventArgs e) { - var ev = new ResourcesChangedEventArgs(); - foreach (var child in this) { - (child as ISetStyleParent)?.NotifyResourcesChanged(ev); + (child as ISetStyleParent)?.NotifyResourcesChanged(e); } - ResourcesChanged?.Invoke(this, ev); + ResourcesChanged?.Invoke(this, e); } private void SubResourceChanged(object sender, ResourcesChangedEventArgs e) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs index ed3ab3cc52..862ce2b3c0 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs @@ -104,6 +104,64 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void StaticResource_From_MergedDictionary_Can_Be_Assigned_To_Property() + { + var xaml = @" + + + + + + #ff506070 + + + + + + +"; + + 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()); + } + + [Fact] + public void StaticResource_From_MergedDictionary_In_Style_Can_Be_Assigned_To_Property() + { + 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()); + } + [Fact] public void StaticResource_From_Application_Can_Be_Assigned_To_Property_In_UserControl() { From 56c06be403c58d456f54131d7e86923f9374c9e8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 25 Aug 2017 08:30:57 +0200 Subject: [PATCH 33/42] DynamicResource merged dictionary tests. --- .../DynamicResourceExtensionTests.cs | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs index 7de60a1029..c751f9e056 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs @@ -76,6 +76,68 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + DelayedBinding.ApplyBindings(border); + + var brush = (SolidColorBrush)border.Background; + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + + [Fact] + public void DynamicResource_From_MergedDictionary_Can_Be_Assigned_To_Property() + { + var xaml = @" + + + + + + #ff506070 + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + DelayedBinding.ApplyBindings(border); + + var brush = (SolidColorBrush)border.Background; + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + + [Fact] + public void DynamicResource_From_MergedDictionary_In_Style_Can_Be_Assigned_To_Property() + { + var xaml = @" + + + + + "; @@ -399,6 +461,99 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(0xff506070, brush.Color.ToUint32()); } + [Fact] + public void DynamicResource_Tracks_Added_MergedResource() + { + 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.MergedDictionaries[0].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_MergedResource_Dictionary() + { + var xaml = @" + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + DelayedBinding.ApplyBindings(border); + + Assert.Null(border.Background); + + var dictionary = new ResourceDictionary + { + { "brush", new SolidColorBrush(0xff506070) }, + }; + + userControl.Resources.MergedDictionaries.Add(dictionary); + + var brush = (SolidColorBrush)border.Background; + Assert.NotNull(brush); + Assert.Equal(0xff506070, brush.Color.ToUint32()); + } + + [Fact] + public void DynamicResource_Tracks_Added_Style_MergedResource_Dictionary() + { + var xaml = @" + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var userControl = (UserControl)loader.Load(xaml); + var border = userControl.FindControl("border"); + + DelayedBinding.ApplyBindings(border); + + Assert.Null(border.Background); + + var dictionary = new ResourceDictionary + { + { "brush", new SolidColorBrush(0xff506070) }, + }; + + ((Style)userControl.Styles[0]).Resources.MergedDictionaries.Add(dictionary); + + 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() { From a72ce3671d6b6917186813e5cec5d136c1d3f7ef Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Aug 2017 19:01:21 +0200 Subject: [PATCH 34/42] Added ResourceInclude. --- src/Avalonia.Controls/Application.cs | 4 +- src/Avalonia.Controls/Control.cs | 4 +- .../Controls/IResourceDictionary.cs | 21 +------ .../Controls/IResourceNode.cs | 26 +------- .../Controls/IResourceProvider.cs | 32 ++++++++++ .../Controls/ResourceDictionary.cs | 17 +++-- src/Avalonia.Styling/Styling/Style.cs | 4 +- .../Avalonia.Markup.Xaml.csproj | 1 + .../Data/ResourceInclude.cs | 63 +++++++++++++++++++ .../Styling/StyleInclude.cs | 2 +- .../Data/ResourceIncludeTests.cs | 55 ++++++++++++++++ .../DynamicResourceExtensionTests.cs | 2 +- .../ResourceDictionaryTests.cs | 2 +- 13 files changed, 176 insertions(+), 57 deletions(-) create mode 100644 src/Avalonia.Styling/Controls/IResourceProvider.cs create mode 100644 src/Markup/Avalonia.Markup.Xaml/Data/ResourceInclude.cs create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Data/ResourceIncludeTests.cs 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); } From e54f48b63ce7335472eb3626174991c37c0272f9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Aug 2017 19:14:32 +0200 Subject: [PATCH 35/42] React to application resources changing. --- src/Avalonia.Controls/TopLevel.cs | 24 +++++++++++++++++-- .../TopLevelTests.cs | 17 +++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index f8db0e2a5b..1af347ab4e 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -10,9 +10,11 @@ using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Layout; using Avalonia.Logging; +using Avalonia.LogicalTree; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Styling; +using Avalonia.Utilities; using Avalonia.VisualTree; using JetBrains.Annotations; @@ -26,7 +28,13 @@ namespace Avalonia.Controls /// It handles scheduling layout, styling and rendering as well as /// tracking the widget's . /// - public abstract class TopLevel : ContentControl, IInputRoot, ILayoutRoot, IRenderRoot, ICloseable, IStyleRoot + public abstract class TopLevel : ContentControl, + IInputRoot, + ILayoutRoot, + IRenderRoot, + ICloseable, + IStyleRoot, + IWeakSubscriber { /// /// Defines the property. @@ -100,7 +108,6 @@ namespace Avalonia.Controls impl.Resized = HandleResized; impl.ScalingChanged = HandleScalingChanged; - _keyboardNavigationHandler?.SetOwner(this); _accessKeyHandler?.SetOwner(this); styler?.ApplyStyles(this); @@ -116,6 +123,14 @@ namespace Avalonia.Controls { _applicationLifecycle.OnExit += OnApplicationExiting; } + + if (((IStyleHost)this).StylingParent is IResourceProvider applicationResources) + { + WeakSubscriptionManager.Subscribe( + applicationResources, + nameof(IResourceProvider.ResourcesChanged), + this); + } } /// @@ -165,6 +180,11 @@ namespace Avalonia.Controls /// IMouseDevice IInputRoot.MouseDevice => PlatformImpl?.MouseDevice; + void IWeakSubscriber.OnEvent(object sender, ResourcesChangedEventArgs e) + { + ((ILogical)this).NotifyResourcesChanged(e); + } + /// /// Gets or sets a value indicating whether access keys are shown in the window. /// diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index da30336be6..da0719893f 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -219,6 +219,23 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Adding_Resource_To_Application_Should_Raise_ResourcesChanged() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var impl = new Mock(); + impl.SetupAllProperties(); + var target = new TestTopLevel(impl.Object); + var raised = false; + + target.ResourcesChanged += (_, __) => raised = true; + Application.Current.Resources.Add("foo", "bar"); + + Assert.True(raised); + } + } + private FuncControlTemplate CreateTemplate() { return new FuncControlTemplate(x => From c47e44192bf9626f8ef3ab597ee388dd2e19638c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Aug 2017 19:56:24 +0200 Subject: [PATCH 36/42] Make resource dictionary keys objects. For parity with other XAML frameworks. --- src/Avalonia.Styling/Controls/IResourceDictionary.cs | 2 +- src/Avalonia.Styling/Controls/ResourceDictionary.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Styling/Controls/IResourceDictionary.cs b/src/Avalonia.Styling/Controls/IResourceDictionary.cs index e6da375544..d7c86b7d74 100644 --- a/src/Avalonia.Styling/Controls/IResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/IResourceDictionary.cs @@ -9,7 +9,7 @@ namespace Avalonia.Controls /// /// An indexed dictionary of resources. /// - public interface IResourceDictionary : IResourceProvider, IDictionary + public interface IResourceDictionary : IResourceProvider, IDictionary { /// /// Gets a collection of child resource dictionaries. diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index ec0a59dad9..74a861b36b 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls /// /// An indexed dictionary of resources. /// - public class ResourceDictionary : AvaloniaDictionary, IResourceDictionary + public class ResourceDictionary : AvaloniaDictionary, IResourceDictionary { private AvaloniaList _mergedDictionaries; From c244ac809dd423290affdde201bd57cf1431b021 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Aug 2017 21:13:47 +0200 Subject: [PATCH 37/42] Fixed malformed doc comment. --- src/Avalonia.Styling/Controls/IResourceProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceProvider.cs index 2ca83ea2d2..eec783623c 100644 --- a/src/Avalonia.Styling/Controls/IResourceProvider.cs +++ b/src/Avalonia.Styling/Controls/IResourceProvider.cs @@ -23,7 +23,8 @@ namespace Avalonia.Controls /// The resource key. /// /// When this method returns, contains the value associated with the specified key, - /// if the key is found; otherwise, null + /// if the key is found; otherwise, null. + /// /// /// True if the resource if found, otherwise false. /// From fe957d8093276e45c46a34dcd616fda5e91fe525 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Aug 2017 23:27:34 +0200 Subject: [PATCH 38/42] Fix compile error. --- src/Avalonia.Base/Collections/AvaloniaDictionary.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs index 9f2a271092..724442c5f2 100644 --- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs +++ b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs @@ -62,8 +62,6 @@ namespace Avalonia.Collections public object SyncRoot => ((IDictionary)_inner).SyncRoot; - public object this[object key] { get => ((IDictionary)_inner)[key]; set => ((IDictionary)_inner)[key] = value; } - /// /// Gets or sets the named resource. /// @@ -102,6 +100,8 @@ namespace Avalonia.Collections } } + object IDictionary.this[object key] { get => ((IDictionary)_inner)[key]; set => ((IDictionary)_inner)[key] = value; } + /// public void Add(TKey key, TValue value) { From 1cf3e6bfc6ef9d42c5212518b9e653bb9e8a46c0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 2 Sep 2017 00:37:37 +0200 Subject: [PATCH 39/42] Use discard in TryGetResource. --- samples/ControlCatalog/MainWindow.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index f6894e4d8a..63f9ab1d0d 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -19,7 +19,7 @@ namespace ControlCatalog // so we must refer to this resource DLL statically. For // now I am doing that here. But we need a better solution!! var theme = new Avalonia.Themes.Default.DefaultTheme(); - theme.TryGetResource("Button", out var button); + theme.TryGetResource("Button", out _); AvaloniaXamlLoader.Load(this); } } From 814a3a5ee6fe1c0d753919c376b490cc0acbc1a9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 2 Sep 2017 00:37:55 +0200 Subject: [PATCH 40/42] Make `IDictionary` members explicit. And use expression bodied members where possible. --- .../Collections/AvaloniaDictionary.cs | 57 +++++-------------- 1 file changed, 14 insertions(+), 43 deletions(-) diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs index 724442c5f2..b90dccf74e 100644 --- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs +++ b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs @@ -52,15 +52,15 @@ namespace Avalonia.Collections /// public ICollection Values => _inner.Values; - public bool IsFixedSize => ((IDictionary)_inner).IsFixedSize; + bool IDictionary.IsFixedSize => ((IDictionary)_inner).IsFixedSize; ICollection IDictionary.Keys => ((IDictionary)_inner).Keys; ICollection IDictionary.Values => ((IDictionary)_inner).Values; - public bool IsSynchronized => ((IDictionary)_inner).IsSynchronized; + bool ICollection.IsSynchronized => ((IDictionary)_inner).IsSynchronized; - public object SyncRoot => ((IDictionary)_inner).SyncRoot; + object ICollection.SyncRoot => ((IDictionary)_inner).SyncRoot; /// /// Gets or sets the named resource. @@ -131,10 +131,7 @@ namespace Avalonia.Collections } /// - public bool ContainsKey(TKey key) - { - return _inner.ContainsKey(key); - } + public bool ContainsKey(TKey key) => _inner.ContainsKey(key); /// public void CopyTo(KeyValuePair[] array, int arrayIndex) @@ -143,21 +140,16 @@ namespace Avalonia.Collections } /// - public IEnumerator> GetEnumerator() - { - return _inner.GetEnumerator(); - } + public IEnumerator> GetEnumerator() => _inner.GetEnumerator(); /// public bool Remove(TKey key) { - TValue value; - - if (_inner.TryGetValue(key, out value)) + if (_inner.TryGetValue(key, out TValue value)) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count")); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); - + if (CollectionChanged != null) { var e = new NotifyCollectionChangedEventArgs( @@ -176,22 +168,13 @@ namespace Avalonia.Collections } /// - public bool TryGetValue(TKey key, out TValue value) - { - return _inner.TryGetValue(key, out value); - } + public bool TryGetValue(TKey key, out TValue value) => _inner.TryGetValue(key, out value); /// - IEnumerator IEnumerable.GetEnumerator() - { - return _inner.GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); /// - void ICollection.CopyTo(Array array, int index) - { - ((ICollection)_inner).CopyTo(array, index); - } + void ICollection.CopyTo(Array array, int index) => ((ICollection)_inner).CopyTo(array, index); /// void ICollection>.Add(KeyValuePair item) @@ -212,28 +195,16 @@ namespace Avalonia.Collections } /// - void IDictionary.Add(object key, object value) - { - Add((TKey)key, (TValue)value); - } + void IDictionary.Add(object key, object value) => Add((TKey)key, (TValue)value); /// - bool IDictionary.Contains(object key) - { - return ((IDictionary)_inner).Contains(key); - } + bool IDictionary.Contains(object key) => ((IDictionary) _inner).Contains(key); /// - IDictionaryEnumerator IDictionary.GetEnumerator() - { - return ((IDictionary)_inner).GetEnumerator(); - } + IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)_inner).GetEnumerator(); /// - void IDictionary.Remove(object key) - { - Remove((TKey)key); - } + void IDictionary.Remove(object key) => Remove((TKey)key); private void NotifyAdd(TKey key, TValue value) { From bf4bb7d54c2373e15cbfa30b97ad2d00b1e9fb18 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 2 Sep 2017 01:01:46 +0200 Subject: [PATCH 41/42] Added TryFindResource extension method. `FindResource` can be ambiguous because it returns `AvaloniaProperty.UnsetValue` in the case of a resource not being found, which is a valid resource value. `TryFindResource` removes this ambiguity. `FindResource` has been left in for API compatibility with other frameworks. --- .../Controls/ResourceProviderExtensions.cs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs index c96e8ea7f3..1f25fa132d 100644 --- a/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs +++ b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.Reactive; using System.Reactive.Linq; -using System.Text; namespace Avalonia.Controls { @@ -15,6 +13,23 @@ namespace Avalonia.Controls /// The resource key. /// The resource, or if not found. public static object FindResource(this IResourceNode control, string key) + { + if (control.TryFindResource(key, out var value)) + { + return value; + } + + return AvaloniaProperty.UnsetValue; + } + + /// + /// Tries to the specified resource by searching up the logical tree and then global styles. + /// + /// The control. + /// The resource key. + /// On return, contains the resource if found, otherwise null. + /// True if the resource was found; otherwise false. + public static bool TryFindResource(this IResourceNode control, string key, out object value) { Contract.Requires(control != null); Contract.Requires(key != null); @@ -25,16 +40,17 @@ namespace Avalonia.Controls { if (current is IResourceNode host) { - if (host.TryGetResource(key, out var value)) + if (host.TryGetResource(key, out value)) { - return value; + return true; } } current = current.ResourceParent; } - return AvaloniaProperty.UnsetValue; + value = null; + return false; } public static IObservable GetResourceObservable(this IResourceNode target, string key) From d1c85d7a3a06ec108359fc6110bd5b159f29aa34 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 6 Sep 2017 23:45:47 +0200 Subject: [PATCH 42/42] Prevent possible null ref warnings. --- .../MarkupExtensions/DynamicResourceExtension.cs | 2 +- .../MarkupExtensions/StaticResourceExtension.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs index 5e421b7e73..231778be09 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -59,7 +59,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions private T GetAnchor(ITypeDescriptorContext context) where T : class { - var schemaContext = context.GetService()?.SchemaContext; + var schemaContext = context.GetService().SchemaContext; var ambientProvider = context.GetService(); var xamlType = schemaContext.GetXamlType(typeof(T)); diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index 4764677ede..9089a13656 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -29,7 +29,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions public override object ProvideValue(IServiceProvider serviceProvider) { var context = (ITypeDescriptorContext)serviceProvider; - var schemaContext = context.GetService()?.SchemaContext; + var schemaContext = context.GetService().SchemaContext; var ambientProvider = context.GetService(); var resourceProviderType = schemaContext.GetXamlType(typeof(IResourceNode)); var ambientValues = ambientProvider.GetAllAmbientValues(resourceProviderType);