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