Browse Source

Start making DynamicResource react to changes.

pull/1136/head
Steven Kirk 9 years ago
parent
commit
513efe99f7
  1. 3
      src/Avalonia.Controls/Application.cs
  2. 100
      src/Avalonia.Controls/Control.cs
  3. 5
      src/Avalonia.Styling/Controls/IResourceProvider.cs
  4. 6
      src/Avalonia.Styling/Controls/ResourceDictionary.cs
  5. 11
      src/Avalonia.Styling/Controls/ResourcesChangedEventArgs.cs
  6. 11
      src/Avalonia.Styling/LogicalTree/ILogical.cs
  7. 13
      src/Avalonia.Styling/Styling/Style.cs
  8. 27
      src/Avalonia.Styling/Styling/Styles.cs
  9. 11
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  10. 3
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  11. 76
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs

3
src/Avalonia.Controls/Application.cs

@ -49,6 +49,9 @@ namespace Avalonia
OnExit += OnExiting; OnExit += OnExiting;
} }
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary> /// <summary>
/// Gets the current instance of the <see cref="Application"/> class. /// Gets the current instance of the <see cref="Application"/> class.
/// </summary> /// </summary>

100
src/Avalonia.Controls/Control.cs

@ -154,6 +154,11 @@ namespace Avalonia.Controls
/// </remarks> /// </remarks>
public event EventHandler Initialized; public event EventHandler Initialized;
/// <summary>
/// Occurs when a resource in this control or a parent control has changed.
/// </summary>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary> /// <summary>
/// Gets or sets the name of the control. /// Gets or sets the name of the control.
/// </summary> /// </summary>
@ -269,8 +274,22 @@ namespace Avalonia.Controls
/// </remarks> /// </remarks>
public Styles Styles public Styles Styles
{ {
get { return _styles ?? (_styles = new Styles()); } get { return _styles ?? (Styles = new Styles()); }
set { _styles = value; } set
{
Contract.Requires<ArgumentNullException>(value != null);
if (_styles != value)
{
if (_styles != null)
{
_styles.ResourcesChanged -= StyleResourcesChanged;
}
_styles = value;
_styles.ResourcesChanged += StyleResourcesChanged;
}
}
} }
/// <summary> /// <summary>
@ -290,7 +309,19 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets the control's resource dictionary. /// Gets or sets the control's resource dictionary.
/// </summary> /// </summary>
public IResourceDictionary Resources => _resources ?? (_resources = new ResourceDictionary()); public IResourceDictionary Resources
{
get
{
if (_resources == null)
{
_resources = new ResourceDictionary();
_resources.CollectionChanged += ResourceDictionaryChanged;
}
return _resources;
}
}
/// <summary> /// <summary>
/// Gets or sets a user-defined object attached to the control. /// Gets or sets a user-defined object attached to the control.
@ -310,6 +341,32 @@ namespace Avalonia.Controls
internal set { SetValue(TemplatedParentProperty, value); } internal set { SetValue(TemplatedParentProperty, value); }
} }
/// <summary>
/// Gets the control's logical children.
/// </summary>
protected IAvaloniaList<ILogical> LogicalChildren
{
get
{
if (_logicalChildren == null)
{
var list = new AvaloniaList<ILogical>();
list.ResetBehavior = ResetBehavior.Remove;
list.Validate = ValidateLogicalChild;
list.CollectionChanged += LogicalChildrenCollectionChanged;
_logicalChildren = list;
}
return _logicalChildren;
}
}
/// <summary>
/// Gets the <see cref="Classes"/> collection in a form that allows adding and removing
/// pseudoclasses.
/// </summary>
protected IPseudoClasses PseudoClasses => Classes;
/// <summary> /// <summary>
/// Gets a value indicating whether the element is attached to a rooted logical tree. /// Gets a value indicating whether the element is attached to a rooted logical tree.
/// </summary> /// </summary>
@ -398,32 +455,17 @@ namespace Avalonia.Controls
this.OnDetachedFromLogicalTreeCore(e); this.OnDetachedFromLogicalTreeCore(e);
} }
/// <summary> /// <inheritdoc/>
/// Gets the control's logical children. void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e)
/// </summary>
protected IAvaloniaList<ILogical> LogicalChildren
{ {
get ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
{
if (_logicalChildren == null)
{
var list = new AvaloniaList<ILogical>();
list.ResetBehavior = ResetBehavior.Remove;
list.Validate = ValidateLogicalChild;
list.CollectionChanged += LogicalChildrenCollectionChanged;
_logicalChildren = list;
}
return _logicalChildren; foreach (var child in LogicalChildren)
{
child.NotifyResourcesChanged(e);
} }
} }
/// <summary>
/// Gets the <see cref="Classes"/> collection in a form that allows adding and removing
/// pseudoclasses.
/// </summary>
protected IPseudoClasses PseudoClasses => Classes;
/// <inheritdoc/> /// <inheritdoc/>
bool IResourceProvider.TryGetResource(string key, out object value) bool IResourceProvider.TryGetResource(string key, out object value)
{ {
@ -857,5 +899,15 @@ namespace Avalonia.Controls
} }
} }
} }
private void ResourceDictionaryChanged(object sender, NotifyCollectionChangedEventArgs e)
{
((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
}
private void StyleResourcesChanged(object sender, ResourcesChangedEventArgs e)
{
((ILogical)this).NotifyResourcesChanged(e);
}
} }
} }

5
src/Avalonia.Styling/Controls/IResourceProvider.cs

@ -7,6 +7,11 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public interface IResourceProvider public interface IResourceProvider
{ {
/// <summary>
/// Raised when the resources in the element are changed.
/// </summary>
event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary> /// <summary>
/// Tries to find a resource within the element. /// Tries to find a resource within the element.
/// </summary> /// </summary>

6
src/Avalonia.Styling/Controls/ResourceDictionary.cs

@ -1,4 +1,7 @@
using System; // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections; using System.Collections;
using Avalonia.Collections; using Avalonia.Collections;
@ -9,6 +12,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class ResourceDictionary : AvaloniaDictionary<string, object>, IResourceDictionary, IDictionary public class ResourceDictionary : AvaloniaDictionary<string, object>, IResourceDictionary, IDictionary
{ {
/// <inheritdoc/>
public bool TryGetResource(string key, out object value) => TryGetValue(key, out value); public bool TryGetResource(string key, out object value) => TryGetValue(key, out value);
} }
} }

11
src/Avalonia.Styling/Controls/ResourcesChangedEventArgs.cs

@ -0,0 +1,11 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Controls
{
public class ResourcesChangedEventArgs : EventArgs
{
}
}

11
src/Avalonia.Styling/LogicalTree/ILogical.cs

@ -3,6 +3,7 @@
using System; using System;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls;
namespace Avalonia.LogicalTree namespace Avalonia.LogicalTree
{ {
@ -55,5 +56,15 @@ namespace Avalonia.LogicalTree
/// this method yourself. /// this method yourself.
/// </remarks> /// </remarks>
void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e); void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e);
/// <summary>
/// Notifies the control that a change has been made to its resources.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// This method will be called automatically by the framework, you should not need to call
/// this method yourself.
/// </remarks>
void NotifyResourcesChanged(ResourcesChangedEventArgs e);
} }
} }

13
src/Avalonia.Styling/Styling/Style.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Metadata; using Avalonia.Metadata;
@ -16,8 +17,7 @@ namespace Avalonia.Styling
{ {
private static Dictionary<IStyleable, List<IDisposable>> _applied = private static Dictionary<IStyleable, List<IDisposable>> _applied =
new Dictionary<IStyleable, List<IDisposable>>(); new Dictionary<IStyleable, List<IDisposable>>();
private ResourceDictionary _resources;
private IResourceDictionary _resources;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Style"/> class. /// Initializes a new instance of the <see cref="Style"/> class.
@ -35,6 +35,9 @@ namespace Avalonia.Styling
Selector = selector(null); Selector = selector(null);
} }
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary> /// <summary>
/// Gets or sets a dictionary of style resources. /// Gets or sets a dictionary of style resources.
/// </summary> /// </summary>
@ -45,6 +48,7 @@ namespace Avalonia.Styling
if (_resources == null) if (_resources == null)
{ {
_resources = new ResourceDictionary(); _resources = new ResourceDictionary();
_resources.CollectionChanged += ResourceDictionaryChanged;
} }
return _resources; return _resources;
@ -151,5 +155,10 @@ namespace Avalonia.Styling
_applied.Remove(control); _applied.Remove(control);
} }
private void ResourceDictionaryChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
} }
} }

27
src/Avalonia.Styling/Styling/Styles.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // 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 System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
@ -12,7 +14,19 @@ namespace Avalonia.Styling
/// </summary> /// </summary>
public class Styles : AvaloniaList<IStyle>, IStyle public class Styles : AvaloniaList<IStyle>, IStyle
{ {
private IResourceDictionary _resources; private ResourceDictionary _resources;
public Styles()
{
ResetBehavior = ResetBehavior.Remove;
this.ForEachItem(
x => x.ResourcesChanged += SubResourceChanged,
x => x.ResourcesChanged -= SubResourceChanged,
() => { });
}
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary> /// <summary>
/// Gets or sets a dictionary of style resources. /// Gets or sets a dictionary of style resources.
@ -24,6 +38,7 @@ namespace Avalonia.Styling
if (_resources == null) if (_resources == null)
{ {
_resources = new ResourceDictionary(); _resources = new ResourceDictionary();
_resources.CollectionChanged += ResourceDictionaryChanged;
} }
return _resources; return _resources;
@ -64,5 +79,15 @@ namespace Avalonia.Styling
value = null; value = null;
return false; return false;
} }
private void ResourceDictionaryChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
private void SubResourceChanged(object sender, ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, e);
}
} }
} }

11
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -3,6 +3,8 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Reactive;
using System.Reactive.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data; using Avalonia.Data;
using Portable.Xaml; using Portable.Xaml;
@ -49,8 +51,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
if (control != null) if (control != null)
{ {
var resource = control.FindResource(ResourceKey); var o = Observable.FromEventPattern<ResourcesChangedEventArgs>(
return new InstancedBinding(resource); x => control.ResourcesChanged += x,
x => control.ResourcesChanged -= x)
.StartWith((EventPattern<ResourcesChangedEventArgs>)null)
.Select(x => control.FindResource(ResourceKey));
return new InstancedBinding(o);
} }
return null; return null;

3
src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs

@ -25,6 +25,9 @@ namespace Avalonia.Markup.Xaml.Styling
_baseUri = baseUri; _baseUri = baseUri;
} }
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary> /// <summary>
/// Gets or sets the source URL. /// Gets or sets the source URL.
/// </summary> /// </summary>

76
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs

@ -276,6 +276,82 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
[Fact]
public void DynamicResource_Tracks_Added_Resource()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
Assert.Null(border.Background);
userControl.Resources.Add("brush", new SolidColorBrush(0xff506070));
var brush = (SolidColorBrush)border.Background;
Assert.NotNull(brush);
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void DynamicResource_Tracks_Added_Style_Resource()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
Assert.Null(border.Background);
userControl.Styles.Resources.Add("brush", new SolidColorBrush(0xff506070));
var brush = (SolidColorBrush)border.Background;
Assert.NotNull(brush);
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void DynamicResource_Tracks_Added_Nested_Style_Resource()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Styles>
<Style>
</Style>
</UserControl.Styles>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
Assert.Null(border.Background);
((Style)userControl.Styles[0]).Resources.Add("brush", new SolidColorBrush(0xff506070));
var brush = (SolidColorBrush)border.Background;
Assert.NotNull(brush);
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact] [Fact]
public void DynamicResource_Can_Be_Found_Across_Xaml_Style_Files() public void DynamicResource_Can_Be_Found_Across_Xaml_Style_Files()
{ {

Loading…
Cancel
Save