diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs
index 527b63292d..d09d1dc8d2 100644
--- a/src/Avalonia.Base/Layout/Layoutable.cs
+++ b/src/Avalonia.Base/Layout/Layoutable.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia.Logging;
+using Avalonia.Styling;
using Avalonia.VisualTree;
#nullable enable
@@ -795,6 +796,12 @@ namespace Avalonia.Layout
base.OnVisualParentChanged(oldParent, newParent);
}
+ private protected override void OnControlThemeChanged()
+ {
+ base.OnControlThemeChanged();
+ InvalidateMeasure();
+ }
+
///
/// Called when the layout manager raises a LayoutUpdated event.
///
diff --git a/src/Avalonia.Base/PropertyStore/FramePriority.cs b/src/Avalonia.Base/PropertyStore/FramePriority.cs
new file mode 100644
index 0000000000..950a8375f2
--- /dev/null
+++ b/src/Avalonia.Base/PropertyStore/FramePriority.cs
@@ -0,0 +1,36 @@
+using System.Diagnostics;
+using Avalonia.Data;
+
+namespace Avalonia.PropertyStore
+{
+ internal enum FramePriority : sbyte
+ {
+ Animation,
+ AnimationTemplatedParentTheme,
+ AnimationTheme,
+ StyleTrigger,
+ StyleTriggerTemplatedParentTheme,
+ StyleTriggerTheme,
+ Template,
+ TemplateTemplatedParentTheme,
+ TemplateTheme,
+ Style,
+ StyleTemplatedParentTheme,
+ StyleTheme,
+ }
+
+ internal static class FramePriorityExtensions
+ {
+ public static FramePriority ToFramePriority(this BindingPriority priority, FrameType type = FrameType.Style)
+ {
+ Debug.Assert(priority != BindingPriority.LocalValue);
+ var p = (int)(priority > 0 ? priority : priority + 1);
+ return (FramePriority)(p * 3 + (int)type);
+ }
+
+ public static bool IsType(this FramePriority priority, FrameType type)
+ {
+ return (FrameType)((int)priority % 3) == type;
+ }
+ }
+}
diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs
index 1d886e7501..756ab7aadf 100644
--- a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs
+++ b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs
@@ -10,8 +10,8 @@ namespace Avalonia.PropertyStore
internal class ImmediateValueFrame : ValueFrame
{
public ImmediateValueFrame(BindingPriority priority)
+ : base(priority, FrameType.Style)
{
- Priority = priority;
}
public TypedBindingEntry AddBinding(
diff --git a/src/Avalonia.Base/PropertyStore/ValueFrame.cs b/src/Avalonia.Base/PropertyStore/ValueFrame.cs
index 5ada4b3c84..7a9d1bb13a 100644
--- a/src/Avalonia.Base/PropertyStore/ValueFrame.cs
+++ b/src/Avalonia.Base/PropertyStore/ValueFrame.cs
@@ -1,13 +1,18 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
using Avalonia.Utilities;
-using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
namespace Avalonia.PropertyStore
{
+ internal enum FrameType
+ {
+ Style,
+ TemplatedParentTheme,
+ Theme,
+ }
+
internal abstract class ValueFrame
{
private List? _entries;
@@ -15,11 +20,18 @@ namespace Avalonia.PropertyStore
private ValueStore? _owner;
private bool _isShared;
+ protected ValueFrame(BindingPriority priority, FrameType type)
+ {
+ Priority = priority;
+ FramePriority = priority.ToFramePriority(type);
+ }
+
public int EntryCount => _index.Count;
public bool IsActive => GetIsActive(out _);
public ValueStore? Owner => !_isShared ? _owner :
throw new AvaloniaInternalException("Cannot get owner for shared ValueFrame");
- public BindingPriority Priority { get; protected set; }
+ public BindingPriority Priority { get; }
+ public FramePriority FramePriority { get; }
public bool Contains(AvaloniaProperty property) => _index.ContainsKey(property);
diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs
index d858e30212..e14d018564 100644
--- a/src/Avalonia.Base/PropertyStore/ValueStore.cs
+++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs
@@ -4,8 +4,8 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
using Avalonia.Diagnostics;
-using Avalonia.Logging;
using Avalonia.Utilities;
+using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
namespace Avalonia.PropertyStore
{
@@ -580,6 +580,29 @@ namespace Avalonia.PropertyStore
return false;
}
+ public void RemoveFrames(FrameType type)
+ {
+ var removed = false;
+
+ for (var i = _frames.Count - 1; i >= 0; --i)
+ {
+ var frame = _frames[i];
+
+ if (frame.FramePriority.IsType(type))
+ {
+ _frames.RemoveAt(i);
+ frame.Dispose();
+ removed = true;
+ }
+ }
+
+ if (removed)
+ {
+ ++_frameGeneration;
+ ReevaluateEffectiveValues();
+ }
+ }
+
public AvaloniaPropertyValue GetDiagnostic(AvaloniaProperty property)
{
object? value;
@@ -612,7 +635,7 @@ namespace Avalonia.PropertyStore
{
Debug.Assert(!_frames.Contains(frame));
- var index = BinarySearchFrame(frame.Priority);
+ var index = BinarySearchFrame(frame.FramePriority);
_frames.Insert(index, frame);
++_frameGeneration;
frame.SetOwner(this);
@@ -626,7 +649,7 @@ namespace Avalonia.PropertyStore
{
Debug.Assert(priority != BindingPriority.LocalValue);
- var index = BinarySearchFrame(priority);
+ var index = BinarySearchFrame(priority.ToFramePriority());
if (index > 0 && _frames[index - 1] is ImmediateValueFrame f &&
f.Priority == priority &&
@@ -914,7 +937,7 @@ namespace Avalonia.PropertyStore
}
}
- private int BinarySearchFrame(BindingPriority priority)
+ private int BinarySearchFrame(FramePriority priority)
{
var lo = 0;
var hi = _frames.Count - 1;
@@ -923,7 +946,7 @@ namespace Avalonia.PropertyStore
while (lo <= hi)
{
var i = lo + ((hi - lo) >> 1);
- var order = priority - _frames[i].Priority;
+ var order = priority - _frames[i].FramePriority;
if (order <= 0)
{
diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs
index c72f398fd9..33bca9b0ab 100644
--- a/src/Avalonia.Base/StyledElement.cs
+++ b/src/Avalonia.Base/StyledElement.cs
@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
+using System.Diagnostics;
using System.Linq;
using Avalonia.Animation;
using Avalonia.Collections;
@@ -11,6 +12,7 @@ using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Logging;
using Avalonia.LogicalTree;
+using Avalonia.PropertyStore;
using Avalonia.Styling;
namespace Avalonia
@@ -69,10 +71,10 @@ namespace Avalonia
private IAvaloniaList? _logicalChildren;
private IResourceDictionary? _resources;
private Styles? _styles;
- private bool _styled;
+ private bool _stylesApplied;
+ private bool _themeApplied;
private ITemplatedControl? _templatedParent;
private bool _dataContextUpdating;
- private bool _hasPromotedTheme;
private ControlTheme? _implicitTheme;
///
@@ -141,7 +143,7 @@ namespace Avalonia
set
{
- if (_styled)
+ if (_stylesApplied)
{
throw new InvalidOperationException("Cannot set Name : styled element already styled.");
}
@@ -353,31 +355,31 @@ namespace Avalonia
///
public bool ApplyStyling()
{
- if (_initCount == 0 && !_styled)
+ if (_initCount == 0 && (!_stylesApplied || !_themeApplied))
{
- var hasPromotedTheme = _hasPromotedTheme;
-
GetValueStore().BeginStyling();
try
{
- ApplyControlTheme();
- ApplyStyles(this);
+ if (!_themeApplied)
+ {
+ ApplyControlTheme();
+ _themeApplied = true;
+ }
+
+ if (!_stylesApplied)
+ {
+ ApplyStyles(this);
+ _stylesApplied = true;
+ }
}
finally
{
- _styled = true;
GetValueStore().EndStyling();
}
-
- if (hasPromotedTheme)
- {
- _hasPromotedTheme = false;
- ClearValue(ThemeProperty);
- }
}
- return _styled;
+ return _stylesApplied;
}
///
@@ -615,31 +617,25 @@ namespace Avalonia
if (change.Property == ThemeProperty)
{
- var (oldValue, newValue) = change.GetOldAndNewValue();
-
- // Changing the theme detaches all styles, meaning that if the theme property was
- // set via a style, it will get cleared! To work around this, if the value was
- // applied at less than local value priority then promote the value to local value
- // priority until styling is re-applied.
- if (change.Priority > BindingPriority.LocalValue)
- {
- Theme = newValue;
- _hasPromotedTheme = true;
- }
- else if (_hasPromotedTheme && change.Priority == BindingPriority.LocalValue)
- {
- _hasPromotedTheme = false;
- }
-
- InvalidateStyles();
-
- if (oldValue is not null)
- DetachControlThemeFromTemplateChildren(oldValue);
+ OnControlThemeChanged();
+ _themeApplied = false;
}
}
- internal virtual void DetachControlThemeFromTemplateChildren(ControlTheme theme)
+ private protected virtual void OnControlThemeChanged()
{
+ var values = GetValueStore();
+ values.BeginStyling();
+ try { values.RemoveFrames(FrameType.Theme); }
+ finally { values.EndStyling(); }
+ }
+
+ internal virtual void OnTemplatedParentControlThemeChanged()
+ {
+ var values = GetValueStore();
+ values.BeginStyling();
+ try { values.RemoveFrames(FrameType.TemplatedParentTheme); }
+ finally { values.EndStyling(); }
}
internal ControlTheme? GetEffectiveTheme()
@@ -736,26 +732,28 @@ namespace Avalonia
var theme = GetEffectiveTheme();
if (theme is not null)
- ApplyControlTheme(theme);
+ ApplyControlTheme(theme, FrameType.Theme);
if (TemplatedParent is StyledElement styleableParent &&
styleableParent.GetEffectiveTheme() is { } parentTheme)
{
- ApplyControlTheme(parentTheme);
+ ApplyControlTheme(parentTheme, FrameType.TemplatedParentTheme);
}
}
- private void ApplyControlTheme(ControlTheme theme)
+ private void ApplyControlTheme(ControlTheme theme, FrameType type)
{
+ Debug.Assert(type is FrameType.Theme or FrameType.TemplatedParentTheme);
+
if (theme.BasedOn is ControlTheme basedOn)
- ApplyControlTheme(basedOn);
+ ApplyControlTheme(basedOn, type);
- theme.TryAttach(this, null);
+ theme.TryAttach(this, type);
if (theme.HasChildren)
{
foreach (var child in theme.Children)
- ApplyStyle(child, null);
+ ApplyStyle(child, null, type);
}
}
@@ -768,17 +766,17 @@ namespace Avalonia
if (host.IsStylesInitialized)
{
foreach (var style in host.Styles)
- ApplyStyle(style, host);
+ ApplyStyle(style, host, FrameType.Style);
}
}
- private void ApplyStyle(IStyle style, IStyleHost? host)
+ private void ApplyStyle(IStyle style, IStyleHost? host, FrameType type)
{
if (style is Style s)
- s.TryAttach(this, host);
+ s.TryAttach(this, host, type);
foreach (var child in style.Children)
- ApplyStyle(child, host);
+ ApplyStyle(child, host, type);
}
private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
@@ -895,6 +893,7 @@ namespace Avalonia
for (var i = valueStore.Frames.Count - 1; i >= 0; --i)
{
if (valueStore.Frames[i] is StyleInstance si &&
+ si.Source is not ControlTheme &&
(styles is null || styles.Contains(si.Source)))
{
valueStore.RemoveFrame(si);
@@ -902,7 +901,7 @@ namespace Avalonia
}
valueStore.EndStyling();
- _styled = false;
+ _stylesApplied = false;
}
private void InvalidateStylesOnThisAndDescendents()
diff --git a/src/Avalonia.Base/Styling/ControlTheme.cs b/src/Avalonia.Base/Styling/ControlTheme.cs
index 2971703c95..5fc900d2cb 100644
--- a/src/Avalonia.Base/Styling/ControlTheme.cs
+++ b/src/Avalonia.Base/Styling/ControlTheme.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics;
using Avalonia.PropertyStore;
namespace Avalonia.Styling
@@ -36,8 +37,10 @@ namespace Avalonia.Styling
throw new InvalidOperationException("ControlThemes cannot be added as a nested style.");
}
- internal override SelectorMatchResult TryAttach(IStyleable target, object? host)
+ internal SelectorMatchResult TryAttach(IStyleable target, FrameType type)
{
+ Debug.Assert(type is FrameType.Theme or FrameType.TemplatedParentTheme);
+
_ = target ?? throw new ArgumentNullException(nameof(target));
if (TargetType is null)
@@ -45,7 +48,7 @@ namespace Avalonia.Styling
if (HasSettersOrAnimations && TargetType.IsAssignableFrom(target.StyleKey))
{
- Attach(target, null);
+ Attach(target, null, type);
return SelectorMatchResult.AlwaysThisType;
}
diff --git a/src/Avalonia.Base/Styling/Style.cs b/src/Avalonia.Base/Styling/Style.cs
index aad91824d3..15d8a9fe2e 100644
--- a/src/Avalonia.Base/Styling/Style.cs
+++ b/src/Avalonia.Base/Styling/Style.cs
@@ -1,4 +1,5 @@
using System;
+using Avalonia.PropertyStore;
namespace Avalonia.Styling
{
@@ -58,7 +59,7 @@ namespace Avalonia.Styling
base.SetParent(parent);
}
- internal override SelectorMatchResult TryAttach(IStyleable target, object? host)
+ internal SelectorMatchResult TryAttach(IStyleable target, object? host, FrameType type)
{
_ = target ?? throw new ArgumentNullException(nameof(target));
@@ -73,7 +74,7 @@ namespace Avalonia.Styling
if (match.IsMatch)
{
- Attach(target, match.Activator);
+ Attach(target, match.Activator, type);
}
result = match.Result;
diff --git a/src/Avalonia.Base/Styling/StyleBase.cs b/src/Avalonia.Base/Styling/StyleBase.cs
index dba80df2e5..83fcf04d2f 100644
--- a/src/Avalonia.Base/Styling/StyleBase.cs
+++ b/src/Avalonia.Base/Styling/StyleBase.cs
@@ -92,9 +92,7 @@ namespace Avalonia.Styling
return false;
}
- internal abstract SelectorMatchResult TryAttach(IStyleable target, object? host);
-
- internal ValueFrame Attach(IStyleable target, IStyleActivator? activator)
+ internal ValueFrame Attach(IStyleable target, IStyleActivator? activator, FrameType type)
{
if (target is not AvaloniaObject ao)
throw new InvalidOperationException("Styles can only be applied to AvaloniaObjects.");
@@ -109,7 +107,7 @@ namespace Avalonia.Styling
{
var canShareInstance = activator is null;
- instance = new StyleInstance(this, activator);
+ instance = new StyleInstance(this, activator, type);
if (_setters is not null)
{
diff --git a/src/Avalonia.Base/Styling/StyleInstance.cs b/src/Avalonia.Base/Styling/StyleInstance.cs
index 2d7c695b32..4985aa16c7 100644
--- a/src/Avalonia.Base/Styling/StyleInstance.cs
+++ b/src/Avalonia.Base/Styling/StyleInstance.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Reactive.Subjects;
using Avalonia.Animation;
using Avalonia.Data;
@@ -27,10 +26,13 @@ namespace Avalonia.Styling
private List? _animations;
private Subject? _animationTrigger;
- public StyleInstance(IStyle style, IStyleActivator? activator)
+ public StyleInstance(
+ IStyle style,
+ IStyleActivator? activator,
+ FrameType type)
+ : base(GetPriority(activator), type)
{
_activator = activator;
- Priority = activator is object ? BindingPriority.StyleTrigger : BindingPriority.Style;
Source = style;
}
@@ -99,5 +101,10 @@ namespace Avalonia.Styling
hasChanged = _isActive != previous;
return _isActive;
}
+
+ private static BindingPriority GetPriority(IStyleActivator? activator)
+ {
+ return activator is not null ? BindingPriority.StyleTrigger : BindingPriority.Style;
+ }
}
}
diff --git a/src/Avalonia.Base/Styling/Styles.cs b/src/Avalonia.Base/Styling/Styles.cs
index 76271b9748..f22bbc0eae 100644
--- a/src/Avalonia.Base/Styling/Styles.cs
+++ b/src/Avalonia.Base/Styling/Styles.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Controls;
+using Avalonia.PropertyStore;
namespace Avalonia.Styling
{
@@ -233,7 +234,7 @@ namespace Avalonia.Styling
{
if (s is not Style style)
continue;
- var r = style.TryAttach(target, host);
+ var r = style.TryAttach(target, host, FrameType.Style);
if (r > result)
result = r;
}
diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
index 80151fbfb3..17d90e6dd0 100644
--- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs
+++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
@@ -5,6 +5,7 @@ using Avalonia.Interactivity;
using Avalonia.Logging;
using Avalonia.LogicalTree;
using Avalonia.Media;
+using Avalonia.PropertyStore;
using Avalonia.Styling;
using Avalonia.VisualTree;
@@ -395,56 +396,36 @@ namespace Avalonia.Controls.Primitives
}
}
- internal override void DetachControlThemeFromTemplateChildren(ControlTheme theme)
+ private protected override void OnControlThemeChanged()
{
- static ControlTheme? GetControlTheme(StyleBase style)
- {
- var s = style;
+ base.OnControlThemeChanged();
- while (s is not null)
+ var count = VisualChildren.Count;
+ for (var i = 0; i < count; ++i)
+ {
+ if (VisualChildren[i] is StyledElement child &&
+ child.TemplatedParent == this)
{
- if (s is ControlTheme c)
- return c;
- s = s.Parent as StyleBase;
+ child.OnTemplatedParentControlThemeChanged();
}
-
- return null;
}
+ }
- static void Detach(Visual control, ITemplatedControl templatedParent, ControlTheme theme)
- {
- var valueStore = control.GetValueStore();
- var count = valueStore.Frames.Count;
-
- if (control != templatedParent)
- {
- valueStore.BeginStyling();
-
- for (var i = count - 1; i >= 0; --i)
- {
- if (valueStore.Frames[i] is StyleInstance si &&
- si.Source is StyleBase style &&
- GetControlTheme(style) == theme)
- {
- valueStore.RemoveFrame(si);
- }
- }
-
- valueStore.EndStyling();
- }
+ internal override void OnTemplatedParentControlThemeChanged()
+ {
+ base.OnTemplatedParentControlThemeChanged();
- var children = ((IVisual)control).VisualChildren;
- count = children.Count;
+ var count = VisualChildren.Count;
+ var templatedParent = TemplatedParent;
- for (var i = 0; i < count; i++)
+ for (var i = 0; i < count; ++i)
+ {
+ if (VisualChildren[i] is TemplatedControl child &&
+ child.TemplatedParent == templatedParent)
{
- if (children[i] is Visual v &&
- v.TemplatedParent == templatedParent)
- Detach(v, templatedParent, theme);
+ child.OnTemplatedParentControlThemeChanged();
}
}
-
- Detach(this, this, theme);
}
}
}
diff --git a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs
index 668b8a875c..dec813b3b0 100644
--- a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs
@@ -5,6 +5,7 @@ using Avalonia.Controls.Shapes;
using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.Media;
+using Avalonia.PropertyStore;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Moq;
@@ -435,7 +436,7 @@ namespace Avalonia.Base.UnitTests.Animation
}
};
- style.TryAttach(control, control);
+ style.TryAttach(control, control, FrameType.Style);
// Which means that the transition state hasn't been initialized with the new
// Transitions when the Opacity change notification gets raised here.
diff --git a/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs b/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs
index bb726a1d63..3a307447ac 100644
--- a/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs
+++ b/tests/Avalonia.Base.UnitTests/PropertyStore/ValueStoreTests_Frames.cs
@@ -117,7 +117,7 @@ namespace Avalonia.Base.UnitTests.PropertyStore
private static StyleInstance InstanceStyle(Style style, StyledElement target)
{
- var result = new StyleInstance(style, null);
+ var result = new StyleInstance(style, null, FrameType.Style);
foreach (var setter in style.Setters)
result.Add(setter.Instance(result, target));
diff --git a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
index dc31d3d3ec..18f572dedc 100644
--- a/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Styling/SetterTests.cs
@@ -6,6 +6,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Media;
+using Avalonia.PropertyStore;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Moq;
@@ -503,7 +504,7 @@ namespace Avalonia.Base.UnitTests.Styling
private void Apply(Style style, Control control)
{
- style.TryAttach(control, null);
+ style.TryAttach(control, null, FrameType.Style);
}
private void Apply(Setter setter, Control control)
diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs b/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs
index e7ecba61a7..a318a8d76a 100644
--- a/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Styling/StyleTests.cs
@@ -5,6 +5,7 @@ using Avalonia.Base.UnitTests.Animation;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data;
+using Avalonia.PropertyStore;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Moq;
@@ -27,7 +28,7 @@ namespace Avalonia.Base.UnitTests.Styling
var target = new Class1();
- style.TryAttach(target, null);
+ style.TryAttach(target, null, FrameType.Style);
Assert.Equal("Foo", target.Foo);
}
@@ -45,7 +46,7 @@ namespace Avalonia.Base.UnitTests.Styling
var target = new Class1();
- style.TryAttach(target, null);
+ style.TryAttach(target, null, FrameType.Style);
Assert.Equal("foodefault", target.Foo);
target.Classes.Add("foo");
Assert.Equal("Foo", target.Foo);
@@ -66,7 +67,7 @@ namespace Avalonia.Base.UnitTests.Styling
var target = new Class1();
- style.TryAttach(target, target);
+ style.TryAttach(target, target, FrameType.Style);
Assert.Equal("Foo", target.Foo);
}
@@ -92,7 +93,7 @@ namespace Avalonia.Base.UnitTests.Styling
var target = new Class1();
var other = new Class1();
- style.TryAttach(target, other);
+ style.TryAttach(target, other, FrameType.Style);
Assert.Equal("foodefault", target.Foo);
}
@@ -113,7 +114,7 @@ namespace Avalonia.Base.UnitTests.Styling
Foo = "Original",
};
- style.TryAttach(target, null);
+ style.TryAttach(target, null, FrameType.Style);
Assert.Equal("Original", target.Foo);
}
@@ -577,7 +578,7 @@ namespace Avalonia.Base.UnitTests.Styling
Child = border = new Border(),
};
- style.TryAttach(border, null);
+ style.TryAttach(border, null, FrameType.Style);
Assert.Equal(new Thickness(4), border.BorderThickness);
root.Child = null;
@@ -761,7 +762,7 @@ namespace Avalonia.Base.UnitTests.Styling
var target = new Class1();
- style.TryAttach(target, null);
+ style.TryAttach(target, null, FrameType.Style);
Assert.Equal(1, target.Classes.ListenerCount);
@@ -874,7 +875,7 @@ namespace Avalonia.Base.UnitTests.Styling
var clock = new TestClock();
var target = new Class1 { Clock = clock };
- style.TryAttach(target, null);
+ style.TryAttach(target, null, FrameType.Style);
Assert.Equal(0.0, target.Double);
diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs
index a1dac931ce..b5524affb7 100644
--- a/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs
+++ b/tests/Avalonia.Base.UnitTests/Styling/StyledElementTests_Theming.cs
@@ -23,7 +23,7 @@ public class StyledElementTests_Theming
Assert.Null(target.Template);
- var root = CreateRoot(target);
+ CreateRoot(target);
Assert.NotNull(target.Template);
var border = Assert.IsType(target.VisualChild);
@@ -43,7 +43,7 @@ public class StyledElementTests_Theming
Assert.Null(target.Template);
- var root = CreateRoot(target);
+ CreateRoot(target);
Assert.NotNull(target.Template);
var border = Assert.IsType(target.VisualChild);
@@ -57,7 +57,7 @@ public class StyledElementTests_Theming
public void Theme_Is_Detached_When_Theme_Property_Cleared()
{
var target = CreateTarget();
- var root = CreateRoot(target);
+ CreateRoot(target);
Assert.NotNull(target.Template);
@@ -66,7 +66,47 @@ public class StyledElementTests_Theming
}
[Fact]
- public void Theme_Is_Detached_From_Template_Controls_When_Theme_Property_Cleared()
+ public void Setting_Explicit_Theme_Detaches_Default_Theme()
+ {
+ var target = new ThemedControl();
+ var root = new TestRoot
+ {
+ Resources = { { typeof(ThemedControl), CreateTheme() } },
+ Child = target,
+ };
+
+ root.LayoutManager.ExecuteInitialLayoutPass();
+
+ Assert.Equal("theme", target.Tag);
+
+ target.Theme = new ControlTheme(typeof(ThemedControl))
+ {
+ Setters =
+ {
+ new Setter(ThemedControl.BackgroundProperty, Brushes.Yellow),
+ }
+ };
+
+ root.LayoutManager.ExecuteLayoutPass();
+
+ Assert.Null(target.Tag);
+ Assert.Equal(Brushes.Yellow, target.Background);
+ }
+
+ [Fact]
+ public void Unrelated_Styles_Are_Not_Detached_When_Theme_Property_Cleared()
+ {
+ var target = CreateTarget();
+ CreateRoot(target, createAdditionalStyles: true);
+
+ Assert.Equal("style", target.Tag);
+
+ target.Theme = null;
+ Assert.Equal("style", target.Tag);
+ }
+
+ [Fact]
+ public void TemplatedParent_Theme_Is_Detached_From_Template_Controls_When_Theme_Property_Cleared()
{
var theme = new ControlTheme
{
@@ -93,10 +133,115 @@ public class StyledElementTests_Theming
target.Theme = null;
- Assert.IsType