浏览代码

Made a start adding Control/Application.Resources.

Steven Kirk 8 年之前
父节点
当前提交
84aa27162f

+ 1 - 1
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);
         }
     }

+ 16 - 3
src/Avalonia.Controls/Application.cs

@@ -29,7 +29,7 @@ namespace Avalonia
     /// method.
     /// - Tracks the lifetime of the application.
     /// </remarks>
-    public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IApplicationLifecycle
+    public class Application : IApplicationLifecycle, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceHost
     {
         /// <summary>
         /// The application-global data templates.
@@ -39,6 +39,7 @@ namespace Avalonia
         private readonly Lazy<IClipboard> _clipboard =
             new Lazy<IClipboard>(() => (IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard)));
         private readonly Styler _styler = new Styler();
+        private ResourceDictionary _resources;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="Application"/> class.
@@ -100,6 +101,11 @@ namespace Avalonia
         /// </summary>
         public IClipboard Clipboard => _clipboard.Value;
 
+        /// <summary>
+        /// Gets the application's global resource dictionary.
+        /// </summary>
+        public IResourceDictionary Resources => _resources ?? (_resources = new ResourceDictionary());
+
         /// <summary>
         /// Gets the application's global styles.
         /// </summary>
@@ -142,13 +148,20 @@ namespace Avalonia
         {
             OnExit?.Invoke(this, EventArgs.Empty);
         }
-        
+
+        /// <inheritdoc/>
+        bool IResourceHost.TryGetResource(string key, out object value)
+        {
+            value = null;
+            return _resources?.TryGetResource(key, out value) ??
+                   Styles.TryGetResource(key, out value);
+        }
+
         /// <summary>
         /// Sent when the application is exiting.
         /// </summary>
         public event EventHandler OnExit;
 
-
         /// <summary>
         /// Called when the application is exiting.
         /// </summary>

+ 15 - 0
src/Avalonia.Controls/Control.cs

@@ -97,6 +97,7 @@ namespace Avalonia.Controls
         private bool _isAttachedToLogicalTree;
         private IAvaloniaList<ILogical> _logicalChildren;
         private INameScope _nameScope;
+        private ResourceDictionary _resources;
         private Styles _styles;
         private bool _styled;
         private Subject<IStyleable> _styleDetach = new Subject<IStyleable>();
@@ -286,6 +287,11 @@ namespace Avalonia.Controls
             set { SetValue(ContextMenuProperty, value); }
         }
 
+        /// <summary>
+        /// Gets or sets the control's resource dictionary.
+        /// </summary>
+        public IResourceDictionary Resources => _resources ?? (_resources = new ResourceDictionary());
+
         /// <summary>
         /// Gets or sets a user-defined object attached to the control.
         /// </summary>
@@ -418,6 +424,15 @@ namespace Avalonia.Controls
         /// </summary>
         protected IPseudoClasses PseudoClasses => Classes;
 
+        /// <inheritdoc/>
+        bool IResourceHost.TryGetResource(string key, out object value)
+        {
+            value = null;
+            return _resources?.TryGetResource(key, out value) ?? 
+                   _styles?.TryGetResource(key, out value) ??
+                   false;
+        }
+
         /// <summary>
         /// Sets the control's logical parent.
         /// </summary>

+ 29 - 0
src/Avalonia.Controls/ControlExtensions.cs

@@ -81,6 +81,35 @@ namespace Avalonia.Controls
                 .FirstOrDefault(x => x != null);
         }
 
+        /// <summary>
+        /// Finds the specified resource by searching up the logical tree and then global styles.
+        /// </summary>
+        /// <param name="control">The control.</param>
+        /// <param name="key">The resource key.</param>
+        /// <returns>The resource, or null if not found.</returns>
+        public static object FindResource(this IControl control, string key)
+        {
+            Contract.Requires<ArgumentNullException>(control != null);
+            Contract.Requires<ArgumentNullException>(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;
+        }
+
         /// <summary>
         /// Adds or removes a pseudoclass depending on a boolean value.
         /// </summary>

+ 1 - 1
src/Avalonia.Controls/IControl.cs

@@ -14,7 +14,7 @@ namespace Avalonia.Controls
     /// <summary>
     /// Interface for Avalonia controls.
     /// </summary>
-    public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IStyleable, IStyleHost
+    public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IResourceHost, IStyleable, IStyleHost
     {
         /// <summary>
         /// Occurs when the control has finished initialization.

+ 1 - 1
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)

+ 1 - 0
src/Avalonia.Styling/Avalonia.Styling.csproj

@@ -2,6 +2,7 @@
   <PropertyGroup>
     <TargetFramework>netstandard1.3</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <RootNamespace>Avalonia</RootNamespace>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
     <DebugSymbols>true</DebugSymbols>

+ 15 - 2
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
     /// </summary>
     public interface IResourceDictionary : IDictionary<string, object>
     {
+        /// <summary>
+        /// Tries to find a resource within the dictionary.
+        /// </summary>
+        /// <param name="key">The resource key.</param>
+        /// <param name="value">
+        /// When this method returns, contains the value associated with the specified key,
+        /// if the key is found; otherwise, null
+        /// <returns>
+        /// True if the resource if found, otherwise false.
+        /// </returns>
+        bool TryGetResource(string key, out object value);
     }
 }

+ 22 - 0
src/Avalonia.Styling/Controls/IResourceHost.cs

@@ -0,0 +1,22 @@
+using System;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Defines an element that can be queried for resources.
+    /// </summary>
+    public interface IResourceHost
+    {
+        /// <summary>
+        /// Tries to find a resource within the element.
+        /// </summary>
+        /// <param name="key">The resource key.</param>
+        /// <param name="value">
+        /// When this method returns, contains the value associated with the specified key,
+        /// if the key is found; otherwise, null
+        /// <returns>
+        /// True if the resource if found, otherwise false.
+        /// </returns>
+        bool TryGetResource(string key, out object value);
+    }
+}

+ 2 - 0
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<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
         {
             return ((IDictionary<string, object>)_inner).Contains(item);

+ 3 - 10
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
 {
     /// <summary>
     /// Defines the interface for styles.
     /// </summary>
-    public interface IStyle
+    public interface IStyle : IResourceHost
     {
         /// <summary>
         /// 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.
         /// </param>
         void Attach(IStyleable control, IStyleHost container);
-
-        /// <summary>
-        /// Tries to find a named resource within the style.
-        /// </summary>
-        /// <param name="name">The resource name.</param>
-        /// <returns>
-        /// The resource if found, otherwise <see cref="AvaloniaProperty.UnsetValue"/>.
-        /// </returns>
-        object FindResource(string name);
     }
 }

+ 0 - 1
src/Avalonia.Styling/Styling/IStyleHost.cs

@@ -17,6 +17,5 @@ namespace Avalonia.Styling
         /// Gets the parent style host element.
         /// </summary>
         IStyleHost StylingParent { get; }
-
     }
 }

+ 4 - 18
src/Avalonia.Styling/Styling/Style.cs

@@ -98,25 +98,11 @@ namespace Avalonia.Styling
             }
         }
 
-        /// <summary>
-        /// Tries to find a named resource within the style.
-        /// </summary>
-        /// <param name="name">The resource name.</param>
-        /// <returns>
-        /// The resource if found, otherwise <see cref="AvaloniaProperty.UnsetValue"/>.
-        /// </returns>
-        public object FindResource(string name)
+        /// <inheritdoc/>
+        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;
         }
 
         /// <summary>

+ 0 - 39
src/Avalonia.Styling/Styling/StyleExtensions.cs

@@ -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
-    {
-        /// <summary>
-        /// Tries to find a named style resource.
-        /// </summary>
-        /// <param name="control">The control from which to find the resource.</param>
-        /// <param name="name">The resource name.</param>
-        /// <returns>
-        /// The resource if found, otherwise <see cref="AvaloniaProperty.UnsetValue"/>.
-        /// </returns>
-        public static object FindStyleResource(this IStyleHost control, string name)
-        {
-            Contract.Requires<ArgumentNullException>(control != null);
-            Contract.Requires<ArgumentNullException>(name != null);
-            Contract.Requires<ArgumentException>(!string.IsNullOrWhiteSpace(name));
-
-            while (control != null)
-            {
-                var result = control.Styles.FindResource(name);
-
-                if (result != AvaloniaProperty.UnsetValue)
-                {
-                    return result;
-                }
-
-                control = control.StylingParent;
-            }
-
-            return AvaloniaProperty.UnsetValue;
-        }
-    }
-}

+ 26 - 14
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
     /// </summary>
     public class Styles : AvaloniaList<IStyle>, IStyle
     {
+        private IResourceDictionary _resources;
+
+        /// <summary>
+        /// Gets or sets a dictionary of style resources.
+        /// </summary>
+        public IResourceDictionary Resources
+        {
+            get
+            {
+                if (_resources == null)
+                {
+                    _resources = new ResourceDictionary();
+                }
+
+                return _resources;
+            }
+        }
+
         /// <summary>
         /// Attaches the style to a control if the style's selector matches.
         /// </summary>
@@ -26,26 +45,19 @@ namespace Avalonia.Styling
             }
         }
 
-        /// <summary>
-        /// Tries to find a named resource within the style.
-        /// </summary>
-        /// <param name="name">The resource name.</param>
-        /// <returns>
-        /// The resource if found, otherwise <see cref="AvaloniaProperty.UnsetValue"/>.
-        /// </returns>
-        public object FindResource(string name)
+        /// <inheritdoc/>
+        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;
         }
     }
 }

+ 5 - 2
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)

+ 3 - 11
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
             }
         }
 
-        /// <summary>
-        /// Tries to find a named resource within the style.
-        /// </summary>
-        /// <param name="name">The resource name.</param>
-        /// <returns>
-        /// The resource if found, otherwise <see cref="AvaloniaProperty.UnsetValue"/>.
-        /// </returns>
-        public object FindResource(string name)
-        {
-            return Loaded.FindResource(name);
-        }
+        /// <inheritdoc/>
+        public bool TryGetResource(string key, out object value) => Loaded.TryGetResource(key, out value);
     }
 }

+ 87 - 0
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"));
+        }
+
+    }
+}

+ 4 - 0
tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs

@@ -162,6 +162,10 @@ namespace Avalonia.Layout.UnitTests
         private void RegisterServices()
         {
             var globalStyles = new Mock<IGlobalStyles>();
+            var globalStylesResources = globalStyles.As<IResourceHost>();
+            var outObj = (object)10;
+            globalStylesResources.Setup(x => x.TryGetResource("FontSizeNormal", out outObj)).Returns(true);
+
             var renderInterface = new Mock<IPlatformRenderInterface>();
             renderInterface.Setup(x =>
                 x.CreateFormattedText(

+ 3 - 3
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);
         }

+ 7 - 5
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<Button>("button");
 
                 DelayedBinding.ApplyBindings(button);
@@ -169,9 +169,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
 
             var loader = new AvaloniaXamlLoader();
             var styles = (Styles)loader.Load(xaml);
-            var brush = (ISolidColorBrush)styles.FindResource("brush");
 
-            Assert.Equal(0xff506070, brush.Color.ToUint32());
+            styles.TryGetResource("brush", out var brush);
+
+            Assert.Equal(0xff506070, ((SolidColorBrush)brush).Color.ToUint32());
         }
 
         [Fact]
@@ -194,9 +195,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
 
             var loader = new AvaloniaXamlLoader();
             var styles = (Styles)loader.Load(xaml);
-            var brush = (ISolidColorBrush)styles.FindResource("brush");
 
-            Assert.Equal(0xff506070, brush.Color.ToUint32());
+            styles.TryGetResource("brush", out var brush);
+
+            Assert.Equal(0xff506070, ((SolidColorBrush)brush).Color.ToUint32());
         }
 
         [Fact(Skip = "TODO: Issue #492")]

+ 0 - 78
tests/Avalonia.Styling.UnitTests/ResourceTests.cs

@@ -1,78 +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.Collections.Generic;
-using Avalonia.Controls;
-using Xunit;
-
-namespace Avalonia.Styling.UnitTests
-{
-    public class ResourceTests
-    {
-        [Fact]
-        public void FindStyleResource_Should_Find_Correct_Resource()
-        {
-            Border target;
-
-            var tree = new Decorator
-            {
-                Styles = new Styles
-                {
-                    new Style
-                    {
-                        Resources =
-                        {
-                            { "Foo", "foo resource" },
-                            { "Bar", "overridden" },
-                        }
-                    }
-                },
-                Child = target = new Border
-                {
-                    Styles = new Styles
-                    {
-                        new Style
-                        {
-                            Resources =
-                            {
-                                { "Bar", "again overridden" },
-                            }
-                        },
-                        new Style
-                        {
-                            Resources =
-                            {
-                                { "Bar", "bar resource" },
-                            }
-                        }
-                    }
-                }
-            };
-
-            Assert.Equal("foo resource", target.FindStyleResource("Foo"));
-            Assert.Equal("bar resource", target.FindStyleResource("Bar"));
-        }
-
-        [Fact]
-        public void FindStyleResource_Should_Return_UnsetValue_For_Not_Found()
-        {
-            Border target;
-
-            var tree = target = new Border
-            {
-                Styles = new Styles
-                    {
-                        new Style
-                        {
-                            Resources =
-                            {
-                                { "Foo", "foo" },
-                            }
-                        },
-                    }
-            };
-
-            Assert.Equal(AvaloniaProperty.UnsetValue, target.FindStyleResource("Baz"));
-        }
-    }
-}

+ 4 - 0
tests/Avalonia.UnitTests/TestRoot.cs

@@ -63,6 +63,10 @@ namespace Avalonia.UnitTests
 
         public bool ShowAccessKeys { get; set; }
 
+        public IStyleHost StylingParent { get; set; }
+
+        IStyleHost IStyleHost.StylingParent => StylingParent;
+
         public IRenderTarget CreateRenderTarget() => _renderTarget;
 
         public void Invalidate(Rect rect)