Browse Source

Made a start adding Control/Application.Resources.

pull/1136/head
Steven Kirk 9 years ago
parent
commit
84aa27162f
  1. 2
      samples/ControlCatalog/MainWindow.xaml.cs
  2. 19
      src/Avalonia.Controls/Application.cs
  3. 15
      src/Avalonia.Controls/Control.cs
  4. 29
      src/Avalonia.Controls/ControlExtensions.cs
  5. 2
      src/Avalonia.Controls/IControl.cs
  6. 2
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  7. 1
      src/Avalonia.Styling/Avalonia.Styling.csproj
  8. 17
      src/Avalonia.Styling/Controls/IResourceDictionary.cs
  9. 22
      src/Avalonia.Styling/Controls/IResourceHost.cs
  10. 2
      src/Avalonia.Styling/Controls/ResourceDictionary.cs
  11. 13
      src/Avalonia.Styling/Styling/IStyle.cs
  12. 1
      src/Avalonia.Styling/Styling/IStyleHost.cs
  13. 22
      src/Avalonia.Styling/Styling/Style.cs
  14. 39
      src/Avalonia.Styling/Styling/StyleExtensions.cs
  15. 40
      src/Avalonia.Styling/Styling/Styles.cs
  16. 7
      src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs
  17. 14
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  18. 87
      tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs
  19. 4
      tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
  20. 6
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  21. 12
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  22. 78
      tests/Avalonia.Styling.UnitTests/ResourceTests.cs
  23. 4
      tests/Avalonia.UnitTests/TestRoot.cs

2
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);
}
}

19
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
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
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>

2
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.

2
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
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>

17
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
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
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);

13
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);
}
}

1
src/Avalonia.Styling/Styling/IStyleHost.cs

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

22
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>

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;
}
}
}

40
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;
}
}
}

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

14
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
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
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(

6
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);
}

12
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")]

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

Loading…
Cancel
Save