diff --git a/samples/ControlCatalog/Pages/ButtonsPage.xaml b/samples/ControlCatalog/Pages/ButtonsPage.xaml
index 059b4d97886..8a474a203df 100644
--- a/samples/ControlCatalog/Pages/ButtonsPage.xaml
+++ b/samples/ControlCatalog/Pages/ButtonsPage.xaml
@@ -90,6 +90,7 @@
+
@@ -55,7 +53,14 @@ public class StyledElement : Animatable, IDataContextProvider, IStyledElement, I
nameof(TemplatedParent),
o => o.TemplatedParent,
(o ,v) => o.TemplatedParent = v);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty ThemeProperty =
+ AvaloniaProperty.Register(nameof(Theme));
+ private static readonly ControlTheme s_invalidTheme = new ControlTheme();
private int _initCount;
private string? _name;
private readonly Classes _classes = new Classes();
@@ -67,6 +72,8 @@ public class StyledElement : Animatable, IDataContextProvider, IStyledElement, I
private List? _appliedStyles;
private ITemplatedControl? _templatedParent;
private bool _dataContextUpdating;
+ private bool _hasPromotedTheme;
+ private ControlTheme? _implicitTheme;
///
/// Initializes static members of the class.
@@ -230,6 +237,15 @@ public ITemplatedControl? TemplatedParent
internal set => SetAndRaise(TemplatedParentProperty, ref _templatedParent, value);
}
+ ///
+ /// Gets or sets the theme to be applied to the element.
+ ///
+ public ControlTheme? Theme
+ {
+ get => GetValue(ThemeProperty);
+ set => SetValue(ThemeProperty, value);
+ }
+
///
/// Gets the styled element's logical children.
///
@@ -302,6 +318,7 @@ protected IAvaloniaList LogicalChildren
///
IStyleHost? IStyleHost.StylingParent => (IStyleHost?)InheritanceParent;
+
///
public virtual void BeginInit()
{
@@ -341,10 +358,15 @@ protected bool ApplyStyling()
}
finally
{
+ _styled = true;
EndBatchUpdate();
}
- _styled = true;
+ if (_hasPromotedTheme)
+ {
+ _hasPromotedTheme = false;
+ ClearValue(ThemeProperty);
+ }
}
return _styled;
@@ -475,6 +497,31 @@ void ISetInheritanceParent.SetParent(IAvaloniaObject? parent)
};
}
+ ControlTheme? IStyleable.GetEffectiveTheme()
+ {
+ var theme = Theme;
+
+ // Explitly set Theme property takes precedence.
+ if (theme is not null)
+ return theme;
+
+ // If the Theme property is not set, try to find a ControlTheme resource with our StyleKey.
+ if (_implicitTheme is null)
+ {
+ var key = ((IStyleable)this).StyleKey;
+
+ if (this.TryFindResource(key, out var value) && value is ControlTheme t)
+ _implicitTheme = t;
+ else
+ _implicitTheme = s_invalidTheme;
+ }
+
+ if (_implicitTheme != s_invalidTheme)
+ return _implicitTheme;
+
+ return null;
+ }
+
void IStyleable.StyleApplied(IStyleInstance instance)
{
instance = instance ?? throw new ArgumentNullException(nameof(instance));
@@ -590,6 +637,30 @@ protected virtual void OnInitialized()
{
}
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == ThemeProperty)
+ {
+ // 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 = change.GetNewValue();
+ _hasPromotedTheme = true;
+ }
+ else if (_hasPromotedTheme && change.Priority == BindingPriority.LocalValue)
+ {
+ _hasPromotedTheme = false;
+ }
+
+ InvalidateStyles();
+ }
+ }
+
private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted)
{
if (o is StyledElement element)
@@ -696,6 +767,7 @@ private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
if (_logicalRoot != null)
{
_logicalRoot = null;
+ _implicitTheme = null;
DetachStyles();
OnDetachedFromLogicalTree(e);
DetachedFromLogicalTree?.Invoke(this, e);
@@ -760,7 +832,7 @@ private void ClearLogicalParent(IList children)
private void DetachStyles()
{
- if (_appliedStyles is object)
+ if (_appliedStyles?.Count > 0)
{
BeginBatchUpdate();
diff --git a/src/Avalonia.Base/Styling/ChildSelector.cs b/src/Avalonia.Base/Styling/ChildSelector.cs
index 34f3a76b61a..9512dc34df6 100644
--- a/src/Avalonia.Base/Styling/ChildSelector.cs
+++ b/src/Avalonia.Base/Styling/ChildSelector.cs
@@ -65,6 +65,6 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
}
protected override Selector? MovePrevious() => null;
- internal override bool HasValidNestingSelector() => _parent.HasValidNestingSelector();
+ protected override Selector? MovePreviousOrParent() => _parent;
}
}
diff --git a/src/Avalonia.Base/Styling/ControlTheme.cs b/src/Avalonia.Base/Styling/ControlTheme.cs
new file mode 100644
index 00000000000..644e8b32d4b
--- /dev/null
+++ b/src/Avalonia.Base/Styling/ControlTheme.cs
@@ -0,0 +1,67 @@
+using System;
+
+namespace Avalonia.Styling
+{
+ ///
+ /// Defines a switchable theme for a control.
+ ///
+ public class ControlTheme : StyleBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ControlTheme() { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value for .
+ public ControlTheme(Type targetType) => TargetType = targetType;
+
+ ///
+ /// Gets or sets the type for which this control theme is intended.
+ ///
+ public Type? TargetType { get; set; }
+
+ ///
+ /// Gets or sets a control theme that is the basis of the current theme.
+ ///
+ public ControlTheme? BasedOn { get; set; }
+
+ public override SelectorMatchResult TryAttach(IStyleable target, object? host)
+ {
+ _ = target ?? throw new ArgumentNullException(nameof(target));
+
+ if (TargetType is null)
+ throw new InvalidOperationException("ControlTheme has no TargetType.");
+
+ var result = BasedOn?.TryAttach(target, host) ?? SelectorMatchResult.NeverThisType;
+
+ if (HasSettersOrAnimations && TargetType.IsAssignableFrom(target.StyleKey))
+ {
+ Attach(target, null);
+ result = SelectorMatchResult.AlwaysThisType;
+ }
+
+ var childResult = TryAttachChildren(target, host);
+
+ if (childResult > result)
+ result = childResult;
+
+ return result;
+ }
+
+ public override string ToString()
+ {
+ if (TargetType is not null)
+ return "ControlTheme: " + TargetType.Name;
+ else
+ return "ControlTheme";
+ }
+
+ internal override void SetParent(StyleBase? parent)
+ {
+ throw new InvalidOperationException("ControlThemes cannot be added as a nested style.");
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Styling/DescendentSelector.cs b/src/Avalonia.Base/Styling/DescendentSelector.cs
index 4ffaff6861e..677a9241899 100644
--- a/src/Avalonia.Base/Styling/DescendentSelector.cs
+++ b/src/Avalonia.Base/Styling/DescendentSelector.cs
@@ -70,6 +70,6 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
}
protected override Selector? MovePrevious() => null;
- internal override bool HasValidNestingSelector() => _parent.HasValidNestingSelector();
+ protected override Selector? MovePreviousOrParent() => _parent;
}
}
diff --git a/src/Avalonia.Base/Styling/IStyle.cs b/src/Avalonia.Base/Styling/IStyle.cs
index e9faf82c07a..417739fb28a 100644
--- a/src/Avalonia.Base/Styling/IStyle.cs
+++ b/src/Avalonia.Base/Styling/IStyle.cs
@@ -23,6 +23,6 @@ public interface IStyle : IResourceNode
///
/// A describing how the style matches the control.
///
- SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host);
+ SelectorMatchResult TryAttach(IStyleable target, object? host);
}
}
diff --git a/src/Avalonia.Base/Styling/IStyleable.cs b/src/Avalonia.Base/Styling/IStyleable.cs
index 5bc972e7ab2..254da4d85c3 100644
--- a/src/Avalonia.Base/Styling/IStyleable.cs
+++ b/src/Avalonia.Base/Styling/IStyleable.cs
@@ -3,8 +3,6 @@
using Avalonia.Collections;
using Avalonia.Metadata;
-#nullable enable
-
namespace Avalonia.Styling
{
///
@@ -28,6 +26,11 @@ public interface IStyleable : IAvaloniaObject, INamed
///
ITemplatedControl? TemplatedParent { get; }
+ ///
+ /// Gets the effective theme for the control as used by the syling system.
+ ///
+ ControlTheme? GetEffectiveTheme();
+
///
/// Notifies the element that a style has been applied.
///
diff --git a/src/Avalonia.Base/Styling/NestingSelector.cs b/src/Avalonia.Base/Styling/NestingSelector.cs
index 481a9378670..77c5b719c60 100644
--- a/src/Avalonia.Base/Styling/NestingSelector.cs
+++ b/src/Avalonia.Base/Styling/NestingSelector.cs
@@ -15,9 +15,17 @@ internal class NestingSelector : Selector
protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe)
{
- if (parent is Style s && s.Selector is Selector selector)
+ if (parent is Style s && s.Selector is not null)
{
- return selector.Match(control, (parent as Style)?.Parent, subscribe);
+ return s.Selector.Match(control, s.Parent, subscribe);
+ }
+ else if (parent is ControlTheme theme)
+ {
+ if (theme.TargetType is null)
+ throw new InvalidOperationException("ControlTheme has no TargetType.");
+ return theme.TargetType.IsAssignableFrom(control.StyleKey) ?
+ SelectorMatch.AlwaysThisType :
+ SelectorMatch.NeverThisType;
}
throw new InvalidOperationException(
@@ -25,6 +33,6 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
}
protected override Selector? MovePrevious() => null;
- internal override bool HasValidNestingSelector() => true;
+ protected override Selector? MovePreviousOrParent() => null;
}
}
diff --git a/src/Avalonia.Base/Styling/NotSelector.cs b/src/Avalonia.Base/Styling/NotSelector.cs
index cdc3254d380..c7727bb6b83 100644
--- a/src/Avalonia.Base/Styling/NotSelector.cs
+++ b/src/Avalonia.Base/Styling/NotSelector.cs
@@ -67,6 +67,6 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
}
protected override Selector? MovePrevious() => _previous;
- internal override bool HasValidNestingSelector() => _argument.HasValidNestingSelector();
+ protected override Selector? MovePreviousOrParent() => _previous;
}
}
diff --git a/src/Avalonia.Base/Styling/NthChildSelector.cs b/src/Avalonia.Base/Styling/NthChildSelector.cs
index 047bf434da4..f4737916648 100644
--- a/src/Avalonia.Base/Styling/NthChildSelector.cs
+++ b/src/Avalonia.Base/Styling/NthChildSelector.cs
@@ -105,7 +105,7 @@ internal static SelectorMatch Evaluate(
}
protected override Selector? MovePrevious() => _previous;
- internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false;
+ protected override Selector? MovePreviousOrParent() => _previous;
public override string ToString()
{
diff --git a/src/Avalonia.Base/Styling/OrSelector.cs b/src/Avalonia.Base/Styling/OrSelector.cs
index 913c27bf0c5..af9249864f9 100644
--- a/src/Avalonia.Base/Styling/OrSelector.cs
+++ b/src/Avalonia.Base/Styling/OrSelector.cs
@@ -103,18 +103,12 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
}
protected override Selector? MovePrevious() => null;
+ protected override Selector? MovePreviousOrParent() => null;
- internal override bool HasValidNestingSelector()
+ internal override void ValidateNestingSelector(bool inControlTheme)
{
foreach (var selector in _selectors)
- {
- if (!selector.HasValidNestingSelector())
- {
- return false;
- }
- }
-
- return true;
+ selector.ValidateNestingSelector(inControlTheme);
}
private Type? EvaluateTargetType()
diff --git a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs
index 7a37daf0879..48136ba2de4 100644
--- a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs
+++ b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs
@@ -90,7 +90,7 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
}
protected override Selector? MovePrevious() => _previous;
- internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false;
+ protected override Selector? MovePreviousOrParent() => _previous;
internal static bool Compare(Type propertyType, object? propertyValue, object? value)
{
diff --git a/src/Avalonia.Base/Styling/Selector.cs b/src/Avalonia.Base/Styling/Selector.cs
index 1e06f3d375e..7ce17518dd6 100644
--- a/src/Avalonia.Base/Styling/Selector.cs
+++ b/src/Avalonia.Base/Styling/Selector.cs
@@ -86,7 +86,36 @@ public SelectorMatch Match(IStyleable control, IStyle? parent = null, bool subsc
///
protected abstract Selector? MovePrevious();
- internal abstract bool HasValidNestingSelector();
+ ///
+ /// Moves to the previous selector or the parent selector.
+ ///
+ protected abstract Selector? MovePreviousOrParent();
+
+ internal virtual void ValidateNestingSelector(bool inControlTheme)
+ {
+ var s = this;
+ var templateCount = 0;
+
+ do
+ {
+ if (inControlTheme)
+ {
+ if (!s.InTemplate && s.IsCombinator)
+ throw new InvalidOperationException(
+ "ControlTheme style may not directly contain a child or descendent selector.");
+ if (s is TemplateSelector && templateCount++ > 0)
+ throw new InvalidOperationException(
+ "ControlTemplate styles cannot contain multiple template selectors.");
+ }
+
+ var previous = s.MovePreviousOrParent();
+
+ if (previous is null && s is not NestingSelector)
+ throw new InvalidOperationException("Child styles must have a nesting selector.");
+
+ s = previous;
+ } while (s is not null);
+ }
private static SelectorMatch MatchUntilCombinator(
IStyleable control,
diff --git a/src/Avalonia.Base/Styling/Style.cs b/src/Avalonia.Base/Styling/Style.cs
index 000e588bada..c61b08b2a1e 100644
--- a/src/Avalonia.Base/Styling/Style.cs
+++ b/src/Avalonia.Base/Styling/Style.cs
@@ -1,22 +1,13 @@
using System;
-using System.Collections.Generic;
-using Avalonia.Animation;
-using Avalonia.Controls;
-using Avalonia.Metadata;
namespace Avalonia.Styling
{
///
/// Defines a style.
///
- public class Style : AvaloniaObject, IStyle, IResourceProvider
+ public class Style : StyleBase
{
- private IResourceHost? _owner;
- private StyleChildren? _children;
- private IResourceDictionary? _resources;
- private List? _setters;
- private List? _animations;
- private StyleCache? _childCache;
+ private Selector? _selector;
///
/// Initializes a new instance of the class.
@@ -35,113 +26,41 @@ public Style(Func selector)
}
///
- /// Gets the children of the style.
- ///
- public IList Children => _children ??= new(this);
-
- ///
- /// Gets the or Application that hosts the style.
+ /// Gets or sets the style's selector.
///
- public IResourceHost? Owner
+ public Selector? Selector
{
- get => _owner;
- private set
- {
- if (_owner != value)
- {
- _owner = value;
- OwnerChanged?.Invoke(this, EventArgs.Empty);
- }
- }
+ get => _selector;
+ set => _selector = ValidateSelector(value);
}
- ///
- /// Gets the parent style if this style is hosted in a collection.
- ///
- public Style? Parent { get; private set; }
-
- ///
- /// Gets or sets a dictionary of style resources.
- ///
- public IResourceDictionary Resources
+ public override SelectorMatchResult TryAttach(IStyleable target, object? host)
{
- get => _resources ?? (Resources = new ResourceDictionary());
- set
- {
- value = value ?? throw new ArgumentNullException(nameof(value));
-
- var hadResources = _resources?.HasResources ?? false;
-
- _resources = value;
-
- if (Owner is object)
- {
- _resources.AddOwner(Owner);
-
- if (hadResources || _resources.HasResources)
- {
- Owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
- }
- }
- }
- }
-
- ///
- /// Gets or sets the style's selector.
- ///
- public Selector? Selector { get; set; }
-
- ///
- /// Gets the style's setters.
- ///
- public IList Setters => _setters ??= new List();
-
- ///
- /// Gets the style's animations.
- ///
- public IList Animations => _animations ??= new List();
-
- bool IResourceNode.HasResources => _resources?.Count > 0;
- IReadOnlyList IStyle.Children => (IReadOnlyList?)_children ?? Array.Empty();
+ _ = target ?? throw new ArgumentNullException(nameof(target));
- public event EventHandler? OwnerChanged;
+ var result = SelectorMatchResult.NeverThisType;
- public void Add(ISetter setter) => Setters.Add(setter);
- public void Add(IStyle style) => Children.Add(style);
-
- public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host)
- {
- target = target ?? throw new ArgumentNullException(nameof(target));
+ if (HasSettersOrAnimations)
+ {
+ var match = Selector?.Match(target, Parent, true) ??
+ (target == host ?
+ SelectorMatch.AlwaysThisInstance :
+ SelectorMatch.NeverThisInstance);
- var match = Selector is object ? Selector.Match(target, Parent) :
- target == host ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance;
+ if (match.IsMatch)
+ Attach(target, match.Activator);
- if (match.IsMatch && (_setters is object || _animations is object))
- {
- var instance = new StyleInstance(this, target, _setters, _animations, match.Activator);
- target.StyleApplied(instance);
- instance.Start();
+ result = match.Result;
}
- var result = match.Result;
+ var childResult = TryAttachChildren(target, host);
- if (_children is not null)
- {
- _childCache ??= new StyleCache();
- var childResult = _childCache.TryAttach(_children, target, host);
- if (childResult > result)
- result = childResult;
- }
+ if (childResult > result)
+ result = childResult;
return result;
}
- public bool TryGetResource(object key, out object? result)
- {
- result = null;
- return _resources?.TryGetResource(key, out result) ?? false;
- }
-
///
/// Returns a string representation of the style.
///
@@ -158,41 +77,30 @@ public override string ToString()
}
}
- void IResourceProvider.AddOwner(IResourceHost owner)
+ internal override void SetParent(StyleBase? parent)
{
- owner = owner ?? throw new ArgumentNullException(nameof(owner));
-
- if (Owner != null)
+ if (parent is Style parentStyle && parentStyle.Selector is not null)
{
- throw new InvalidOperationException("The Style already has a parent.");
- }
-
- Owner = owner;
- _resources?.AddOwner(owner);
- }
-
- void IResourceProvider.RemoveOwner(IResourceHost owner)
- {
- owner = owner ?? throw new ArgumentNullException(nameof(owner));
-
- if (Owner == owner)
- {
- Owner = null;
- _resources?.RemoveOwner(owner);
+ if (Selector is null)
+ throw new InvalidOperationException("Child styles must have a selector.");
+ Selector.ValidateNestingSelector(false);
}
- }
-
- internal void SetParent(Style? parent)
- {
- if (parent?.Selector is not null)
+ else if (parent is ControlTheme)
{
if (Selector is null)
throw new InvalidOperationException("Child styles must have a selector.");
- if (!Selector.HasValidNestingSelector())
- throw new InvalidOperationException("Child styles must have a nesting selector.");
+ Selector.ValidateNestingSelector(true);
}
- Parent = parent;
+ base.SetParent(parent);
+ }
+
+ private static Selector? ValidateSelector(Selector? selector)
+ {
+ if (selector is TemplateSelector)
+ throw new InvalidOperationException(
+ "Invalid selector: Template selector must be followed by control selector.");
+ return selector;
}
}
}
diff --git a/src/Avalonia.Base/Styling/StyleBase.cs b/src/Avalonia.Base/Styling/StyleBase.cs
new file mode 100644
index 00000000000..306a4cf0100
--- /dev/null
+++ b/src/Avalonia.Base/Styling/StyleBase.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Animation;
+using Avalonia.Controls;
+using Avalonia.Metadata;
+using Avalonia.Styling.Activators;
+
+namespace Avalonia.Styling
+{
+ ///
+ /// Base class for and .
+ ///
+ public abstract class StyleBase : AvaloniaObject, IStyle, IResourceProvider
+ {
+ private IResourceHost? _owner;
+ private StyleChildren? _children;
+ private IResourceDictionary? _resources;
+ private List? _setters;
+ private List? _animations;
+ private StyleCache? _childCache;
+
+ public IList Children => _children ??= new(this);
+
+ public IResourceHost? Owner
+ {
+ get => _owner;
+ private set
+ {
+ if (_owner != value)
+ {
+ _owner = value;
+ OwnerChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+ }
+
+ public IStyle? Parent { get; private set; }
+
+ public IResourceDictionary Resources
+ {
+ get => _resources ?? (Resources = new ResourceDictionary());
+ set
+ {
+ value = value ?? throw new ArgumentNullException(nameof(value));
+
+ var hadResources = _resources?.HasResources ?? false;
+
+ _resources = value;
+
+ if (Owner is object)
+ {
+ _resources.AddOwner(Owner);
+
+ if (hadResources || _resources.HasResources)
+ {
+ Owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
+ }
+ }
+ }
+ }
+
+ public IList Setters => _setters ??= new List();
+ public IList Animations => _animations ??= new List();
+
+ bool IResourceNode.HasResources => _resources?.Count > 0;
+ IReadOnlyList IStyle.Children => (IReadOnlyList?)_children ?? Array.Empty();
+
+ internal bool HasSettersOrAnimations => _setters?.Count > 0 || _animations?.Count > 0;
+
+ public void Add(ISetter setter) => Setters.Add(setter);
+ public void Add(IStyle style) => Children.Add(style);
+
+ public event EventHandler? OwnerChanged;
+
+ public abstract SelectorMatchResult TryAttach(IStyleable target, object? host);
+
+ public bool TryGetResource(object key, out object? result)
+ {
+ result = null;
+ return _resources?.TryGetResource(key, out result) ?? false;
+ }
+
+ internal void Attach(IStyleable target, IStyleActivator? activator)
+ {
+ var instance = new StyleInstance(this, target, _setters, _animations, activator);
+ target.StyleApplied(instance);
+ instance.Start();
+ }
+
+ internal SelectorMatchResult TryAttachChildren(IStyleable target, object? host)
+ {
+ if (_children is null || _children.Count == 0)
+ return SelectorMatchResult.NeverThisType;
+ _childCache ??= new StyleCache();
+ return _childCache.TryAttach(_children, target, host);
+ }
+
+ internal virtual void SetParent(StyleBase? parent) => Parent = parent;
+
+ void IResourceProvider.AddOwner(IResourceHost owner)
+ {
+ owner = owner ?? throw new ArgumentNullException(nameof(owner));
+
+ if (Owner != null)
+ {
+ throw new InvalidOperationException("The Style already has a parent.");
+ }
+
+ Owner = owner;
+ _resources?.AddOwner(owner);
+ }
+
+ void IResourceProvider.RemoveOwner(IResourceHost owner)
+ {
+ owner = owner ?? throw new ArgumentNullException(nameof(owner));
+
+ if (Owner == owner)
+ {
+ Owner = null;
+ _resources?.RemoveOwner(owner);
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Styling/StyleCache.cs b/src/Avalonia.Base/Styling/StyleCache.cs
index 32854768807..81196f6a279 100644
--- a/src/Avalonia.Base/Styling/StyleCache.cs
+++ b/src/Avalonia.Base/Styling/StyleCache.cs
@@ -12,7 +12,7 @@ namespace Avalonia.Styling
///
internal class StyleCache : Dictionary?>
{
- public SelectorMatchResult TryAttach(IList styles, IStyleable target, IStyleHost? host)
+ public SelectorMatchResult TryAttach(IList styles, IStyleable target, object? host)
{
if (TryGetValue(target.StyleKey, out var cached))
{
diff --git a/src/Avalonia.Base/Styling/StyleChildren.cs b/src/Avalonia.Base/Styling/StyleChildren.cs
index 5f8635f1553..42b0a331eeb 100644
--- a/src/Avalonia.Base/Styling/StyleChildren.cs
+++ b/src/Avalonia.Base/Styling/StyleChildren.cs
@@ -5,20 +5,20 @@ namespace Avalonia.Styling
{
internal class StyleChildren : Collection
{
- private readonly Style _owner;
+ private readonly StyleBase _owner;
- public StyleChildren(Style owner) => _owner = owner;
+ public StyleChildren(StyleBase owner) => _owner = owner;
protected override void InsertItem(int index, IStyle item)
{
- (item as Style)?.SetParent(_owner);
+ (item as StyleBase)?.SetParent(_owner);
base.InsertItem(index, item);
}
protected override void RemoveItem(int index)
{
var item = Items[index];
- (item as Style)?.SetParent(null);
+ (item as StyleBase)?.SetParent(null);
if (_owner.Owner is IResourceHost host)
(item as IResourceProvider)?.RemoveOwner(host);
base.RemoveItem(index);
@@ -26,7 +26,7 @@ protected override void RemoveItem(int index)
protected override void SetItem(int index, IStyle item)
{
- (item as Style)?.SetParent(_owner);
+ (item as StyleBase)?.SetParent(_owner);
base.SetItem(index, item);
if (_owner.Owner is IResourceHost host)
(item as IResourceProvider)?.AddOwner(host);
diff --git a/src/Avalonia.Base/Styling/Styler.cs b/src/Avalonia.Base/Styling/Styler.cs
index 74cf77ea40c..ad5c1cd102f 100644
--- a/src/Avalonia.Base/Styling/Styler.cs
+++ b/src/Avalonia.Base/Styling/Styler.cs
@@ -1,19 +1,24 @@
using System;
-#nullable enable
-
namespace Avalonia.Styling
{
public class Styler : IStyler
{
public void ApplyStyles(IStyleable target)
{
- target = target ?? throw new ArgumentNullException(nameof(target));
+ _ = target ?? throw new ArgumentNullException(nameof(target));
+
+ // Apply the control theme.
+ target.GetEffectiveTheme()?.TryAttach(target, target);
+
+ // If the control has a themed templated parent then apply the styles from the
+ // templated parent theme.
+ if (target.TemplatedParent is IStyleable styleableParent)
+ styleableParent.GetEffectiveTheme()?.TryAttach(target, styleableParent);
+ // Apply styles from the rest of the tree.
if (target is IStyleHost styleHost)
- {
ApplyStyles(target, styleHost);
- }
}
private void ApplyStyles(IStyleable target, IStyleHost host)
@@ -21,14 +26,10 @@ private void ApplyStyles(IStyleable target, IStyleHost host)
var parent = host.StylingParent;
if (parent != null)
- {
ApplyStyles(target, parent);
- }
if (host.IsStylesInitialized)
- {
host.Styles.TryAttach(target, host);
- }
}
}
}
diff --git a/src/Avalonia.Base/Styling/Styles.cs b/src/Avalonia.Base/Styling/Styles.cs
index e4c3371007e..c213475bb71 100644
--- a/src/Avalonia.Base/Styling/Styles.cs
+++ b/src/Avalonia.Base/Styling/Styles.cs
@@ -26,6 +26,11 @@ public Styles()
{
_styles.ResetBehavior = ResetBehavior.Remove;
_styles.CollectionChanged += OnCollectionChanged;
+ _styles.Validate = i =>
+ {
+ if (i is ControlTheme)
+ throw new InvalidOperationException("ControlThemes cannot be added to a Styles collection.");
+ };
}
public Styles(IResourceHost owner)
@@ -111,7 +116,7 @@ public IStyle this[int index]
set => _styles[index] = value;
}
- public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host)
+ public SelectorMatchResult TryAttach(IStyleable target, object? host)
{
_cache ??= new StyleCache();
return _cache.TryAttach(this, target, host);
diff --git a/src/Avalonia.Base/Styling/TemplateSelector.cs b/src/Avalonia.Base/Styling/TemplateSelector.cs
index b0a2dae8d64..278e24a2037 100644
--- a/src/Avalonia.Base/Styling/TemplateSelector.cs
+++ b/src/Avalonia.Base/Styling/TemplateSelector.cs
@@ -49,6 +49,6 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
}
protected override Selector? MovePrevious() => null;
- internal override bool HasValidNestingSelector() => _parent?.HasValidNestingSelector() ?? false;
+ protected override Selector? MovePreviousOrParent() => _parent;
}
}
diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
index d52c8c7d5cc..94a6db41f6a 100644
--- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
+++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
@@ -141,7 +141,7 @@ protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bo
}
protected override Selector? MovePrevious() => _previous;
- internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false;
+ protected override Selector? MovePreviousOrParent() => _previous;
private string BuildSelectorString()
{
diff --git a/src/Avalonia.Controls/Design.cs b/src/Avalonia.Controls/Design.cs
index 07d2918a885..80600b22763 100644
--- a/src/Avalonia.Controls/Design.cs
+++ b/src/Avalonia.Controls/Design.cs
@@ -1,4 +1,5 @@
+using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Avalonia.Styling;
@@ -6,6 +7,8 @@ namespace Avalonia.Controls
{
public static class Design
{
+ private static Dictionary
IEnumerable Containers { get; }
+ ///
+ /// Gets or sets the theme to be applied to the items in the control.
+ ///
+ ControlTheme? ItemContainerTheme { get; set; }
+
///
/// Gets or sets the data template used to display the items in the control.
///
diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
index a76dcbe9c81..8b36b07cec7 100644
--- a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
+++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
@@ -4,6 +4,7 @@
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
+using Avalonia.Styling;
namespace Avalonia.Controls.Generators
{
@@ -35,6 +36,11 @@ public ItemContainerGenerator(IControl owner)
///
public event EventHandler? Recycled;
+ ///
+ /// Gets or sets the theme to be applied to the items in the control.
+ ///
+ public ControlTheme? ItemContainerTheme { get; set; }
+
///
/// Gets or sets the data template used to display the items in the control.
///
@@ -190,10 +196,18 @@ public int IndexFromContainer(IControl? container)
result.SetValue(
ContentPresenter.ContentTemplateProperty,
ItemTemplate,
- BindingPriority.TemplatedParent);
+ BindingPriority.Style);
}
}
+ if (ItemContainerTheme != null)
+ {
+ result.SetValue(
+ StyledElement.ThemeProperty,
+ ItemContainerTheme,
+ BindingPriority.TemplatedParent);
+ }
+
return result;
}
diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
index 635f3a7d372..3ff1b0702dd 100644
--- a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
+++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
@@ -44,28 +44,29 @@ public ItemContainerGenerator(
{
var container = item as T;
- if (container != null)
+ if (container is null)
{
- return container;
- }
- else
- {
- var result = new T();
+ container = new T();
if (ContentTemplateProperty != null)
{
- result.SetValue(ContentTemplateProperty, ItemTemplate, BindingPriority.Style);
+ container.SetValue(ContentTemplateProperty, ItemTemplate, BindingPriority.Style);
}
- result.SetValue(ContentProperty, item, BindingPriority.Style);
+ container.SetValue(ContentProperty, item, BindingPriority.Style);
if (!(item is IControl))
{
- result.DataContext = item;
+ container.DataContext = item;
}
+ }
- return result;
+ if (ItemContainerTheme != null)
+ {
+ container.SetValue(StyledElement.ThemeProperty, ItemContainerTheme, BindingPriority.Style);
}
+
+ return container;
}
///
diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
index 536a5fdd06e..4e3deb55524 100644
--- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
+++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
@@ -71,6 +71,11 @@ public TreeItemContainerGenerator(
var template = GetTreeDataTemplate(item, ItemTemplate);
var result = new T();
+ if (ItemContainerTheme != null)
+ {
+ result.SetValue(Control.ThemeProperty, ItemContainerTheme, BindingPriority.Style);
+ }
+
result.SetValue(ContentProperty, template.Build(item), BindingPriority.Style);
var itemsSelector = template.ItemsSelector(item);
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index 1ac642c22b2..345e7fcac89 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -15,6 +15,7 @@
using Avalonia.LogicalTree;
using Avalonia.Metadata;
using Avalonia.VisualTree;
+using Avalonia.Styling;
namespace Avalonia.Controls
{
@@ -36,6 +37,12 @@ public class ItemsControl : TemplatedControl, IItemsPresenterHost, ICollectionCh
public static readonly DirectProperty ItemsProperty =
AvaloniaProperty.RegisterDirect(nameof(Items), o => o.Items, (o, v) => o.Items = v);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty ItemContainerThemeProperty =
+ AvaloniaProperty.Register(nameof(ItemContainerTheme));
+
///
/// Defines the property.
///
@@ -88,6 +95,7 @@ public IItemContainerGenerator ItemContainerGenerator
{
_itemContainerGenerator = CreateItemContainerGenerator();
+ _itemContainerGenerator.ItemContainerTheme = ItemContainerTheme;
_itemContainerGenerator.ItemTemplate = ItemTemplate;
_itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e);
_itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e);
@@ -108,6 +116,15 @@ public IEnumerable? Items
set { SetAndRaise(ItemsProperty, ref _items, value); }
}
+ ///
+ /// Gets or sets the that is applied to the container element generated for each item.
+ ///
+ public ControlTheme? ItemContainerTheme
+ {
+ get { return GetValue(ItemContainerThemeProperty); }
+ set { SetValue(ItemContainerThemeProperty, value); }
+ }
+
///
/// Gets the number of items in .
///
@@ -349,6 +366,10 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
{
UpdatePseudoClasses(change.GetNewValue());
}
+ else if (change.Property == ItemContainerThemeProperty && _itemContainerGenerator is not null)
+ {
+ _itemContainerGenerator.ItemContainerTheme = change.GetNewValue();
+ }
}
///
diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
index 35033c58f0d..dc52cc3ae20 100644
--- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs
+++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
@@ -367,6 +367,17 @@ protected virtual void OnApplyTemplate(TemplateAppliedEventArgs e)
{
}
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == ThemeProperty)
+ {
+ foreach (var child in this.GetTemplateChildren())
+ child.InvalidateStyles();
+ }
+ }
+
///
/// Called when the control's template is applied.
///
diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs
index 490b0b3ce36..2e3aa037c2d 100644
--- a/src/Avalonia.Controls/TreeViewItem.cs
+++ b/src/Avalonia.Controls/TreeViewItem.cs
@@ -121,6 +121,11 @@ protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e
{
ItemTemplate = _treeView.ItemTemplate;
}
+
+ if (ItemContainerTheme == null && _treeView?.ItemContainerTheme != null)
+ {
+ ItemContainerTheme = _treeView.ItemContainerTheme;
+ }
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs
index b009778f972..811f9c7baa5 100644
--- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs
+++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs
@@ -37,6 +37,7 @@ public static Window LoadDesignerWindow(string xaml, string assemblyPath, string
var localAsm = assemblyPath != null ? Assembly.LoadFile(Path.GetFullPath(assemblyPath)) : null;
var loaded = loader.Load(stream, localAsm, null, baseUri, true);
var style = loaded as IStyle;
+ var resources = loaded as ResourceDictionary;
if (style != null)
{
var substitute = Design.GetPreviewWith((AvaloniaObject)style);
@@ -58,6 +59,27 @@ public static Window LoadDesignerWindow(string xaml, string assemblyPath, string
}
};
}
+ else if (resources != null)
+ {
+ var substitute = Design.GetPreviewWith(resources);
+ if (substitute != null)
+ {
+ substitute.Resources.MergedDictionaries.Add(resources);
+ control = substitute;
+ }
+ else
+ control = new StackPanel
+ {
+ Children =
+ {
+ new TextBlock {Text = "ResourceDictionaries can't be previewed without Design.PreviewWith. Add"},
+ new TextBlock {Text = ""},
+ new TextBlock {Text = " "},
+ new TextBlock {Text = ""},
+ new TextBlock {Text = "in your resource dictionary"}
+ }
+ };
+ }
else if (loaded is Application)
control = new TextBlock {Text = "Application can't be previewed in design view"};
else
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
index f8e2e0544f6..631da80d8b8 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
@@ -67,8 +67,15 @@ public ControlDetailsViewModel(TreePageViewModel treePage, IAvaloniaObject avalo
var setters = new List();
- if (styleSource is Style style)
+ if (styleSource is StyleBase style)
{
+ var selector = style switch
+ {
+ Style s => s.Selector?.ToString(),
+ ControlTheme t => t.TargetType?.Name.ToString(),
+ _ => null,
+ };
+
foreach (var setter in style.Setters)
{
if (setter is Setter regularSetter
@@ -105,7 +112,7 @@ public ControlDetailsViewModel(TreePageViewModel treePage, IAvaloniaObject avalo
}
}
- AppliedStyles.Add(new StyleViewModel(appliedStyle, style.Selector?.ToString() ?? "No selector", setters));
+ AppliedStyles.Add(new StyleViewModel(appliedStyle, selector ?? "No selector", setters));
}
}
diff --git a/src/Avalonia.Themes.Default/SimpleTheme.cs b/src/Avalonia.Themes.Default/SimpleTheme.cs
index 664c95644fc..98e35355c89 100644
--- a/src/Avalonia.Themes.Default/SimpleTheme.cs
+++ b/src/Avalonia.Themes.Default/SimpleTheme.cs
@@ -103,7 +103,7 @@ public SimpleThemeMode Mode
void IResourceProvider.RemoveOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.RemoveOwner(owner);
- public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) => Loaded.TryAttach(target, host);
+ public SelectorMatchResult TryAttach(IStyleable target, object? host) => Loaded.TryAttach(target, host);
public bool TryGetResource(object key, out object? value)
{
diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.cs b/src/Avalonia.Themes.Fluent/FluentTheme.cs
index 81601f72a10..79dd81a0681 100644
--- a/src/Avalonia.Themes.Fluent/FluentTheme.cs
+++ b/src/Avalonia.Themes.Fluent/FluentTheme.cs
@@ -174,7 +174,7 @@ public event EventHandler? OwnerChanged
}
}
- public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) => Loaded.TryAttach(target, host);
+ public SelectorMatchResult TryAttach(IStyleable target, object? host) => Loaded.TryAttach(target, host);
public bool TryGetResource(object key, out object? value)
{
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
index e3a55feac92..0d41ec93b46 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
@@ -46,6 +46,7 @@ void InsertBefore(params IXamlAstTransformer[] t)
);
InsertBefore(
+ new AvaloniaXamlIlControlThemeTransformer(),
new AvaloniaXamlIlSelectorTransformer(),
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlBindingPathParser(),
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs
new file mode 100644
index 00000000000..1338dc7248a
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlThemeTransformer.cs
@@ -0,0 +1,39 @@
+using System.Linq;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ class AvaloniaXamlIlControlThemeTransformer : IXamlAstTransformer
+ {
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (!(node is XamlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.ControlTheme"))
+ return node;
+
+ // Check if we've already transformed this node.
+ if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlTargetTypeMetadataNode)
+ return node;
+
+ var targetTypeNode = on.Children.OfType()
+ .FirstOrDefault(p => p.Property.GetClrProperty().Name == "TargetType") ??
+ throw new XamlParseException("ControlTheme must have a TargetType.", node);
+
+ IXamlType targetType;
+
+ if (targetTypeNode.Values[0] is XamlTypeExtensionNode extension)
+ targetType = extension.Value.GetClrType();
+ else if (targetTypeNode.Values[0] is XamlAstTextNode text)
+ targetType = TypeReferenceResolver.ResolveType(context, text.Text, false, text, true).GetClrType();
+ else
+ throw new XamlParseException("Could not determine TargetType for ControlTheme.", targetTypeNode);
+
+ return new AvaloniaXamlIlTargetTypeMetadataNode(on,
+ new XamlAstClrTypeReference(targetTypeNode, targetType, false),
+ AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
index e816265422f..6da95be1c16 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
@@ -1,19 +1,14 @@
-using System;
using System.Collections.Generic;
using System.Linq;
-using Avalonia.Data.Core;
-using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
-using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
using XamlParseException = XamlX.XamlParseException;
- using XamlLoadException = XamlX.XamlLoadException;
class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@@ -22,35 +17,23 @@ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode nod
&& on.Type.GetClrType().FullName == "Avalonia.Styling.Setter"))
return node;
- var parent = context.ParentNodes().OfType()
- .FirstOrDefault(p => p.Type.GetClrType().FullName == "Avalonia.Styling.Style");
-
- if (parent == null)
- throw new XamlParseException(
- "Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style", node);
- var selectorProperty = parent.Children.OfType()
- .FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector");
- if (selectorProperty == null)
- throw new XamlParseException(
- "Can not find parent Style Selector", node);
- var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode;
- if (selector?.TargetType == null)
- throw new XamlParseException(
- "Can not resolve parent Style Selector type", node);
+ var targetTypeNode = context.ParentNodes()
+ .OfType()
+ .FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style) ??
+ throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType", node);
IXamlType propType = null;
var property = @on.Children.OfType()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "Property");
if (property != null)
{
-
var propertyName = property.Values.OfType().FirstOrDefault()?.Text;
if (propertyName == null)
throw new XamlParseException("Setter.Property must be a string", node);
var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
- new XamlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]);
+ new XamlAstClrTypeReference(targetTypeNode, targetTypeNode.TargetType.GetClrType(), false), property.Values[0]);
property.Values = new List {avaloniaPropertyNode};
propType = avaloniaPropertyNode.AvaloniaPropertyType;
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
index 46b5bc0c40f..d92003ad9f3 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
@@ -82,7 +82,7 @@ public event EventHandler? OwnerChanged
}
}
- public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) => Loaded.TryAttach(target, host);
+ public SelectorMatchResult TryAttach(IStyleable target, object? host) => Loaded.TryAttach(target, host);
public bool TryGetResource(object key, out object? value)
{
diff --git a/tests/Avalonia.Base.UnitTests/Styling/ControlThemeTests.cs b/tests/Avalonia.Base.UnitTests/Styling/ControlThemeTests.cs
new file mode 100644
index 00000000000..7a27a02fc4f
--- /dev/null
+++ b/tests/Avalonia.Base.UnitTests/Styling/ControlThemeTests.cs
@@ -0,0 +1,92 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Styling;
+using Xunit;
+
+namespace Avalonia.Base.UnitTests.Styling
+{
+ public class ControlThemeTests
+ {
+ [Fact]
+ public void ControlTheme_Cannot_Be_Added_To_Styles()
+ {
+ var target = new ControlTheme(typeof(Button));
+ var styles = new Styles();
+
+ Assert.Throws(() => styles.Add(target));
+ }
+
+ [Fact]
+ public void ControlTheme_Cannot_Be_Added_To_Style_Children()
+ {
+ var target = new ControlTheme(typeof(Button));
+ var style = new Style();
+
+ Assert.Throws(() => style.Children.Add(target));
+ }
+
+ [Fact]
+ public void ControlTheme_Cannot_Be_Added_To_ControlTheme_Children()
+ {
+ var target = new ControlTheme(typeof(Button));
+ var other = new ControlTheme(typeof(CheckBox));
+
+ Assert.Throws(() => other.Children.Add(target));
+ }
+
+ [Fact]
+ public void Style_Without_Selector_Cannot_Be_Added_To_Children()
+ {
+ var target = new ControlTheme(typeof(Button));
+ var child = new Style();
+
+ Assert.Throws(() => target.Children.Add(child));
+ }
+
+ [Fact]
+ public void Style_Without_Nesting_Selector_Cannot_Be_Added_To_Children()
+ {
+ var target = new ControlTheme(typeof(Button));
+ var child = new Style(x => x.OfType