Browse Source

Merge pull request #3647 from AvaloniaUI/feature/style-add-remove

Allow adding and removing styles at runtime
pull/3680/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
38280c3cad
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      src/Avalonia.Controls/Application.cs
  2. 20
      src/Avalonia.Controls/TopLevel.cs
  3. 7
      src/Avalonia.Layout/Layoutable.cs
  4. 139
      src/Avalonia.Styling/StyledElement.cs
  5. 14
      src/Avalonia.Styling/Styling/IGlobalStyles.cs
  6. 8
      src/Avalonia.Styling/Styling/IStyle.cs
  7. 16
      src/Avalonia.Styling/Styling/IStyleHost.cs
  8. 11
      src/Avalonia.Styling/Styling/IStyleable.cs
  9. 2
      src/Avalonia.Styling/Styling/Style.cs
  10. 143
      src/Avalonia.Styling/Styling/Styles.cs
  11. 23
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  12. 39
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  13. 11
      tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs
  14. 179
      tests/Avalonia.Styling.UnitTests/StyleTests.cs

26
src/Avalonia.Controls/Application.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Reactive.Concurrency;
using System.Threading;
using Avalonia.Animation;
@ -45,6 +46,8 @@ namespace Avalonia
private Styles _styles;
private IResourceDictionary _resources;
private bool _notifyingResourcesChanged;
private Action<IReadOnlyList<IStyle>> _stylesAdded;
private Action<IReadOnlyList<IStyle>> _stylesRemoved;
/// <summary>
/// Defines the <see cref="DataContext"/> property.
@ -201,6 +204,18 @@ namespace Avalonia
/// </summary>
public IApplicationLifetime ApplicationLifetime { get; set; }
event Action<IReadOnlyList<IStyle>> IGlobalStyles.GlobalStylesAdded
{
add => _stylesAdded += value;
remove => _stylesAdded -= value;
}
event Action<IReadOnlyList<IStyle>> IGlobalStyles.GlobalStylesRemoved
{
add => _stylesRemoved += value;
remove => _stylesRemoved -= value;
}
/// <summary>
/// Initializes the application by loading XAML etc.
/// </summary>
@ -214,6 +229,16 @@ namespace Avalonia
Styles.TryGetResource(key, out value);
}
void IStyleHost.StylesAdded(IReadOnlyList<IStyle> styles)
{
_stylesAdded?.Invoke(styles);
}
void IStyleHost.StylesRemoved(IReadOnlyList<IStyle> styles)
{
_stylesRemoved?.Invoke(styles);
}
/// <summary>
/// Register's the services needed by Avalonia.
/// </summary>
@ -286,6 +311,5 @@ namespace Avalonia
get => _name;
set => SetAndRaise(NameProperty, ref _name, value);
}
}
}

20
src/Avalonia.Controls/TopLevel.cs

@ -50,6 +50,7 @@ namespace Avalonia.Controls
private readonly IAccessKeyHandler _accessKeyHandler;
private readonly IKeyboardNavigationHandler _keyboardNavigationHandler;
private readonly IPlatformRenderInterface _renderInterface;
private readonly IGlobalStyles _globalStyles;
private Size _clientSize;
private ILayoutManager _layoutManager;
@ -94,6 +95,7 @@ namespace Avalonia.Controls
_inputManager = TryGetService<IInputManager>(dependencyResolver);
_keyboardNavigationHandler = TryGetService<IKeyboardNavigationHandler>(dependencyResolver);
_renderInterface = TryGetService<IPlatformRenderInterface>(dependencyResolver);
_globalStyles = TryGetService<IGlobalStyles>(dependencyResolver);
Renderer = impl.CreateRenderer(this);
@ -112,6 +114,13 @@ namespace Avalonia.Controls
_keyboardNavigationHandler?.SetOwner(this);
_accessKeyHandler?.SetOwner(this);
if (_globalStyles is object)
{
_globalStyles.GlobalStylesAdded += ((IStyleHost)this).StylesAdded;
_globalStyles.GlobalStylesRemoved += ((IStyleHost)this).StylesRemoved;
}
styler?.ApplyStyles(this);
ClientSize = impl.ClientSize;
@ -215,10 +224,7 @@ namespace Avalonia.Controls
/// <inheritdoc/>
double IRenderRoot.RenderScaling => PlatformImpl?.Scaling ?? 1;
IStyleHost IStyleHost.StylingParent
{
get { return AvaloniaLocator.Current.GetService<IGlobalStyles>(); }
}
IStyleHost IStyleHost.StylingParent => _globalStyles;
IRenderTarget IRenderRoot.CreateRenderTarget() => CreateRenderTarget();
@ -267,6 +273,12 @@ namespace Avalonia.Controls
/// </summary>
protected virtual void HandleClosed()
{
if (_globalStyles is object)
{
_globalStyles.GlobalStylesAdded -= ((IStyleHost)this).StylesAdded;
_globalStyles.GlobalStylesRemoved -= ((IStyleHost)this).StylesRemoved;
}
var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null);
((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);

7
src/Avalonia.Layout/Layoutable.cs

@ -507,6 +507,7 @@ namespace Avalonia.Layout
{
var margin = Margin;
ApplyStyling();
ApplyTemplate();
var constrained = LayoutHelper.ApplyLayoutConstraints(
@ -692,6 +693,12 @@ namespace Avalonia.Layout
return finalSize;
}
protected sealed override void InvalidateStyles()
{
base.InvalidateStyles();
InvalidateMeasure();
}
/// <inheritdoc/>
protected sealed override void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
{

139
src/Avalonia.Styling/StyledElement.cs

@ -213,6 +213,7 @@ namespace Avalonia
{
_styles = new Styles(this);
_styles.ResourcesChanged += ThisResourcesChanged;
NotifyResourcesChanged(new ResourcesChangedEventArgs());
}
return _styles;
@ -352,21 +353,34 @@ namespace Avalonia
if (--_initCount == 0 && _logicalRoot != null)
{
InitializeStylesIfNeeded();
ApplyStyling();
InitializeIfNeeded();
}
}
private void InitializeStylesIfNeeded(bool force = false)
/// <summary>
/// Applies styling to the control if the control is initialized and styling is not
/// already applied.
/// </summary>
/// <returns>
/// A value indicating whether styling is now applied to the control.
/// </returns>
protected bool ApplyStyling()
{
if (_initCount == 0 && (!_styled || force))
if (_initCount == 0 && !_styled)
{
ApplyStyling();
AvaloniaLocator.Current.GetService<IStyler>()?.ApplyStyles(this);
_styled = true;
}
return _styled;
}
/// <summary>
/// Detaches all styles from the element and queues a restyle.
/// </summary>
protected virtual void InvalidateStyles() => DetachStyles();
protected void InitializeIfNeeded()
{
if (_initCount == 0 && !IsInitialized)
@ -480,6 +494,21 @@ namespace Avalonia
void IStyleable.DetachStyles() => DetachStyles();
void IStyleable.DetachStyles(IReadOnlyList<IStyle> styles) => DetachStyles(styles);
void IStyleable.InvalidateStyles() => InvalidateStyles();
void IStyleHost.StylesAdded(IReadOnlyList<IStyle> styles)
{
InvalidateStylesOnThisAndDescendents();
}
void IStyleHost.StylesRemoved(IReadOnlyList<IStyle> styles)
{
var allStyles = RecurseStyles(styles);
DetachStylesFromThisAndDescendents(allStyles);
}
protected virtual void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
@ -604,11 +633,6 @@ namespace Avalonia
return null;
}
private void ApplyStyling()
{
AvaloniaLocator.Current.GetService<IStyler>()?.ApplyStyles(this);
}
private static void ValidateLogicalChild(ILogical c)
{
if (c == null)
@ -635,7 +659,7 @@ namespace Avalonia
{
_logicalRoot = e.Root;
InitializeStylesIfNeeded(true);
ApplyStyling();
OnAttachedToLogicalTree(e);
AttachedToLogicalTree?.Invoke(this, e);
@ -713,6 +737,64 @@ namespace Avalonia
_appliedStyles.Clear();
}
_styled = false;
}
private void DetachStyles(IReadOnlyList<IStyle> styles)
{
styles = styles ?? throw new ArgumentNullException(nameof(styles));
if (_appliedStyles is null)
{
return;
}
var count = styles.Count;
for (var i = 0; i < count; ++i)
{
for (var j = _appliedStyles.Count - 1; j >= 0; --j)
{
var applied = _appliedStyles[j];
if (applied.Source == styles[i])
{
applied.Dispose();
_appliedStyles.RemoveAt(j);
}
}
}
}
private void InvalidateStylesOnThisAndDescendents()
{
InvalidateStyles();
if (_logicalChildren is object)
{
var childCount = _logicalChildren.Count;
for (var i = 0; i < childCount; ++i)
{
(_logicalChildren[i] as StyledElement)?.InvalidateStylesOnThisAndDescendents();
}
}
}
private void DetachStylesFromThisAndDescendents(IReadOnlyList<IStyle> styles)
{
DetachStyles(styles);
if (_logicalChildren is object)
{
var childCount = _logicalChildren.Count;
for (var i = 0; i < childCount; ++i)
{
(_logicalChildren[i] as StyledElement)?.DetachStylesFromThisAndDescendents(styles);
}
}
}
private void ClearLogicalParent(IEnumerable<ILogical> children)
@ -750,5 +832,40 @@ namespace Avalonia
{
NotifyResourcesChanged(e);
}
private static IReadOnlyList<IStyle> RecurseStyles(IReadOnlyList<IStyle> styles)
{
var count = styles.Count;
List<IStyle>? result = null;
for (var i = 0; i < count; ++i)
{
var style = styles[i];
if (style.Children.Count > 0)
{
if (result is null)
{
result = new List<IStyle>(styles);
}
RecurseStyles(style.Children, result);
}
}
return result ?? styles;
}
private static void RecurseStyles(IReadOnlyList<IStyle> styles, List<IStyle> result)
{
var count = styles.Count;
for (var i = 0; i < count; ++i)
{
var style = styles[i];
result.Add(style);
RecurseStyles(style.Children, result);
}
}
}
}

14
src/Avalonia.Styling/Styling/IGlobalStyles.cs

@ -1,6 +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;
using System.Collections.Generic;
#nullable enable
namespace Avalonia.Styling
{
/// <summary>
@ -8,5 +13,14 @@ namespace Avalonia.Styling
/// </summary>
public interface IGlobalStyles : IStyleHost
{
/// <summary>
/// Raised when styles are added to <see cref="Styles"/> or a nested styles collection.
/// </summary>
public event Action<IReadOnlyList<IStyle>> GlobalStylesAdded;
/// <summary>
/// Raised when styles are removed from <see cref="Styles"/> or a nested styles collection.
/// </summary>
public event Action<IReadOnlyList<IStyle>> GlobalStylesRemoved;
}
}

8
src/Avalonia.Styling/Styling/IStyle.cs

@ -1,6 +1,7 @@
// 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;
#nullable enable
@ -13,7 +14,12 @@ namespace Avalonia.Styling
public interface IStyle : IResourceNode
{
/// <summary>
/// Attaches the style to a control if the style's selector matches.
/// Gets a collection of child styles.
/// </summary>
IReadOnlyList<IStyle> Children { get; }
/// <summary>
/// Attaches the style and any child styles to a control if the style's selector matches.
/// </summary>
/// <param name="target">The control to attach to.</param>
/// <param name="host">The element that hosts the style.</param>

16
src/Avalonia.Styling/Styling/IStyleHost.cs

@ -2,6 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
#nullable enable
namespace Avalonia.Styling
{
/// <summary>
@ -27,5 +31,17 @@ namespace Avalonia.Styling
/// Gets the parent style host element.
/// </summary>
IStyleHost StylingParent { get; }
/// <summary>
/// Called when styles are added to <see cref="Styles"/> or a nested styles collection.
/// </summary>
/// <param name="styles">The added styles.</param>
void StylesAdded(IReadOnlyList<IStyle> styles);
/// <summary>
/// Called when styles are removed from <see cref="Styles"/> or a nested styles collection.
/// </summary>
/// <param name="styles">The removed styles.</param>
void StylesRemoved(IReadOnlyList<IStyle> styles);
}
}

11
src/Avalonia.Styling/Styling/IStyleable.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using Avalonia.Collections;
#nullable enable
@ -38,5 +39,15 @@ namespace Avalonia.Styling
/// Detaches all styles applied to the element.
/// </summary>
void DetachStyles();
/// <summary>
/// Detaches a collection of styles, if applied to the element.
/// </summary>
void DetachStyles(IReadOnlyList<IStyle> styles);
/// <summary>
/// Detaches all styles from the element and queues a restyle.
/// </summary>
void InvalidateStyles();
}
}

2
src/Avalonia.Styling/Styling/Style.cs

@ -90,6 +90,8 @@ namespace Avalonia.Styling
/// <inheritdoc/>
bool IResourceProvider.HasResources => _resources?.Count > 0;
IReadOnlyList<IStyle> IStyle.Children => Array.Empty<IStyle>();
/// <inheritdoc/>
public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host)
{

143
src/Avalonia.Styling/Styling/Styles.cs

@ -27,40 +27,7 @@ namespace Avalonia.Styling
public Styles()
{
_styles.ResetBehavior = ResetBehavior.Remove;
_styles.ForEachItem(
x =>
{
if (x.ResourceParent == null && x is ISetResourceParent setParent)
{
setParent.SetParent(this);
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
x.ResourcesChanged += NotifyResourcesChanged;
_cache = null;
},
x =>
{
if (x.ResourceParent == this && x is ISetResourceParent setParent)
{
setParent.SetParent(null);
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
x.ResourcesChanged -= NotifyResourcesChanged;
_cache = null;
},
() => { });
_styles.CollectionChanged += OnCollectionChanged;
}
public Styles(IResourceNode parent)
@ -69,11 +36,7 @@ namespace Avalonia.Styling
_parent = parent;
}
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add => _styles.CollectionChanged += value;
remove => _styles.CollectionChanged -= value;
}
public event NotifyCollectionChangedEventHandler? CollectionChanged;
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs>? ResourcesChanged;
@ -121,6 +84,8 @@ namespace Avalonia.Styling
/// <inheritdoc/>
IStyle IReadOnlyList<IStyle>.this[int index] => _styles[index];
IReadOnlyList<IStyle> IStyle.Children => this;
/// <inheritdoc/>
public IStyle this[int index]
{
@ -257,6 +222,106 @@ namespace Avalonia.Styling
NotifyResourcesChanged(e);
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
static IReadOnlyList<T> ToReadOnlyList<T>(IList list)
{
if (list is IReadOnlyList<T>)
{
return (IReadOnlyList<T>)list;
}
else
{
var result = new T[list.Count];
list.CopyTo(result, 0);
return result;
}
}
void Add(IList items)
{
for (var i = 0; i < items.Count; ++i)
{
var style = (IStyle)items[i];
if (style.ResourceParent == null && style is ISetResourceParent setParent)
{
setParent.SetParent(this);
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (style.HasResources)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
style.ResourcesChanged += NotifyResourcesChanged;
_cache = null;
}
GetHost()?.StylesAdded(ToReadOnlyList<IStyle>(items));
}
void Remove(IList items)
{
for (var i = 0; i < items.Count; ++i)
{
var style = (IStyle)items[i];
if (style.ResourceParent == this && style is ISetResourceParent setParent)
{
setParent.SetParent(null);
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (style.HasResources)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
style.ResourcesChanged -= NotifyResourcesChanged;
_cache = null;
}
GetHost()?.StylesRemoved(ToReadOnlyList<IStyle>(items));
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
Add(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
Remove(e.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
Remove(e.OldItems);
Add(e.NewItems);
break;
case NotifyCollectionChangedAction.Reset:
throw new InvalidOperationException("Reset should not be called on Styles.");
}
CollectionChanged?.Invoke(this, e);
}
private IStyleHost? GetHost()
{
var node = _parent;
while (node != null)
{
if (node is IStyleHost host)
{
return host;
}
node = node.ResourceParent;
}
return null;
}
private void NotifyResourcesChanged(object sender, ResourcesChangedEventArgs e)
{
NotifyResourcesChanged(e);

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

@ -6,6 +6,8 @@ using System;
using Avalonia.Controls;
using System.Collections.Generic;
#nullable enable
namespace Avalonia.Markup.Xaml.Styling
{
/// <summary>
@ -14,8 +16,8 @@ namespace Avalonia.Markup.Xaml.Styling
public class StyleInclude : IStyle, ISetResourceParent
{
private Uri _baseUri;
private IStyle _loaded;
private IResourceNode _parent;
private IStyle[]? _loaded;
private IResourceNode? _parent;
/// <summary>
/// Initializes a new instance of the <see cref="StyleInclude"/> class.
@ -41,7 +43,7 @@ namespace Avalonia.Markup.Xaml.Styling
/// <summary>
/// Gets or sets the source URL.
/// </summary>
public Uri Source { get; set; }
public Uri? Source { get; set; }
/// <summary>
/// Gets the loaded style.
@ -53,11 +55,12 @@ namespace Avalonia.Markup.Xaml.Styling
if (_loaded == null)
{
var loader = new AvaloniaXamlLoader();
_loaded = (IStyle)loader.Load(Source, _baseUri);
(_loaded as ISetResourceParent)?.SetParent(this);
var loaded = (IStyle)loader.Load(Source, _baseUri);
(loaded as ISetResourceParent)?.SetParent(this);
_loaded = new[] { loaded };
}
return _loaded;
return _loaded?[0]!;
}
}
@ -65,13 +68,15 @@ namespace Avalonia.Markup.Xaml.Styling
bool IResourceProvider.HasResources => Loaded.HasResources;
/// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => _parent;
IResourceNode? IResourceNode.ResourceParent => _parent;
IReadOnlyList<IStyle> IStyle.Children => _loaded ?? Array.Empty<IStyle>();
/// <inheritdoc/>
public SelectorMatchResult TryAttach(IStyleable target, IStyleHost host) => Loaded.TryAttach(target, host);
public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) => Loaded.TryAttach(target, host);
/// <inheritdoc/>
public bool TryGetResource(object key, out object value) => Loaded.TryGetResource(key, out value);
public bool TryGetResource(object key, out object? value) => Loaded.TryGetResource(key, out value);
/// <inheritdoc/>
void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e)

39
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@ -9,6 +9,7 @@ using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Moq;
using Xunit;
@ -269,6 +270,44 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Reacts_To_Changes_In_Global_Styles()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
impl.SetupGet(x => x.Scaling).Returns(1);
var child = new Border { Classes = { "foo" } };
var target = new TestTopLevel(impl.Object)
{
Template = CreateTemplate(),
Content = child,
};
target.LayoutManager.ExecuteInitialLayoutPass(target);
Assert.Equal(new Thickness(0), child.BorderThickness);
var style = new Style(x => x.OfType<Border>().Class("foo"))
{
Setters =
{
new Setter(Border.BorderThicknessProperty, new Thickness(2))
}
};
Application.Current.Styles.Add(style);
target.LayoutManager.ExecuteInitialLayoutPass(target);
Assert.Equal(new Thickness(2), child.BorderThickness);
Application.Current.Styles.Remove(style);
Assert.Equal(new Thickness(0), child.BorderThickness);
}
}
private FuncControlTemplate<TestTopLevel> CreateTemplate()
{
return new FuncControlTemplate<TestTopLevel>((x, scope) =>

11
tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs

@ -9,6 +9,7 @@ using Avalonia.Styling;
using Xunit;
using System.ComponentModel;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using System.Collections.Generic;
namespace Avalonia.Markup.Xaml.UnitTests.Converters
{
@ -144,6 +145,16 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
throw new NotImplementedException();
}
public void DetachStyles(IReadOnlyList<IStyle> styles)
{
throw new NotImplementedException();
}
public void InvalidateStyles()
{
throw new NotImplementedException();
}
public void StyleApplied(IStyleInstance instance)
{
throw new NotImplementedException();

179
tests/Avalonia.Styling.UnitTests/StyleTests.cs

@ -244,6 +244,185 @@ namespace Avalonia.Styling.UnitTests
Assert.Equal(new Thickness(0), border.BorderThickness);
}
[Fact]
public void Removing_Style_Should_Detach_From_Control()
{
using (UnitTestApplication.Start(TestServices.RealStyler))
{
var border = new Border();
var root = new TestRoot
{
Styles =
{
new Style(x => x.OfType<Border>())
{
Setters =
{
new Setter(Border.BorderThicknessProperty, new Thickness(4)),
}
}
},
Child = border,
};
root.Measure(Size.Infinity);
Assert.Equal(new Thickness(4), border.BorderThickness);
root.Styles.RemoveAt(0);
Assert.Equal(new Thickness(0), border.BorderThickness);
}
}
[Fact]
public void Adding_Style_Should_Attach_To_Control()
{
using (UnitTestApplication.Start(TestServices.RealStyler))
{
var border = new Border();
var root = new TestRoot
{
Styles =
{
new Style(x => x.OfType<Border>())
{
Setters =
{
new Setter(Border.BorderThicknessProperty, new Thickness(4)),
}
}
},
Child = border,
};
root.Measure(Size.Infinity);
Assert.Equal(new Thickness(4), border.BorderThickness);
root.Styles.Add(new Style(x => x.OfType<Border>())
{
Setters =
{
new Setter(Border.BorderThicknessProperty, new Thickness(6)),
}
});
root.Measure(Size.Infinity);
Assert.Equal(new Thickness(6), border.BorderThickness);
}
}
[Fact]
public void Removing_Style_With_Nested_Style_Should_Detach_From_Control()
{
using (UnitTestApplication.Start(TestServices.RealStyler))
{
var border = new Border();
var root = new TestRoot
{
Styles =
{
new Styles
{
new Style(x => x.OfType<Border>())
{
Setters =
{
new Setter(Border.BorderThicknessProperty, new Thickness(4)),
}
}
}
},
Child = border,
};
root.Measure(Size.Infinity);
Assert.Equal(new Thickness(4), border.BorderThickness);
root.Styles.RemoveAt(0);
Assert.Equal(new Thickness(0), border.BorderThickness);
}
}
[Fact]
public void Adding_Nested_Style_Should_Attach_To_Control()
{
using (UnitTestApplication.Start(TestServices.RealStyler))
{
var border = new Border();
var root = new TestRoot
{
Styles =
{
new Styles
{
new Style(x => x.OfType<Border>())
{
Setters =
{
new Setter(Border.BorderThicknessProperty, new Thickness(4)),
}
}
}
},
Child = border,
};
root.Measure(Size.Infinity);
Assert.Equal(new Thickness(4), border.BorderThickness);
((Styles)root.Styles[0]).Add(new Style(x => x.OfType<Border>())
{
Setters =
{
new Setter(Border.BorderThicknessProperty, new Thickness(6)),
}
});
root.Measure(Size.Infinity);
Assert.Equal(new Thickness(6), border.BorderThickness);
}
}
[Fact]
public void Removing_Nested_Style_Should_Detach_From_Control()
{
using (UnitTestApplication.Start(TestServices.RealStyler))
{
var border = new Border();
var root = new TestRoot
{
Styles =
{
new Styles
{
new Style(x => x.OfType<Border>())
{
Setters =
{
new Setter(Border.BorderThicknessProperty, new Thickness(4)),
}
},
new Style(x => x.OfType<Border>())
{
Setters =
{
new Setter(Border.BorderThicknessProperty, new Thickness(6)),
}
},
}
},
Child = border,
};
root.Measure(Size.Infinity);
Assert.Equal(new Thickness(6), border.BorderThickness);
((Styles)root.Styles[0]).RemoveAt(1);
root.Measure(Size.Infinity);
Assert.Equal(new Thickness(4), border.BorderThickness);
}
}
private class Class1 : Control
{
public static readonly StyledProperty<string> FooProperty =

Loading…
Cancel
Save