diff --git a/src/Avalonia.Base/Data/TemplateBinding.cs b/src/Avalonia.Base/Data/TemplateBinding.cs
index db878620b4..fd3c0f5b62 100644
--- a/src/Avalonia.Base/Data/TemplateBinding.cs
+++ b/src/Avalonia.Base/Data/TemplateBinding.cs
@@ -5,6 +5,7 @@ using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.Logging;
+using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia.Data
@@ -26,7 +27,7 @@ namespace Avalonia.Data
{
}
- public TemplateBinding(AvaloniaProperty property)
+ public TemplateBinding([InheritDataTypeFrom(InheritDataTypeFromScopeKind.ControlTemplate)] AvaloniaProperty property)
: base(BindingPriority.Template)
{
Property = property;
@@ -64,6 +65,7 @@ namespace Avalonia.Data
///
/// Gets or sets the name of the source property on the templated parent.
///
+ [InheritDataTypeFrom(InheritDataTypeFromScopeKind.ControlTemplate)]
public AvaloniaProperty? Property { get; set; }
///
diff --git a/src/Avalonia.Base/Metadata/InheritDataTypeFromAttribute.cs b/src/Avalonia.Base/Metadata/InheritDataTypeFromAttribute.cs
new file mode 100644
index 0000000000..4a57667f82
--- /dev/null
+++ b/src/Avalonia.Base/Metadata/InheritDataTypeFromAttribute.cs
@@ -0,0 +1,44 @@
+using System;
+
+namespace Avalonia.Metadata;
+
+///
+/// Represents the kind of scope from which a data type can be inherited. Used in resolving target for AvaloniaProperty.
+///
+public enum InheritDataTypeFromScopeKind
+{
+ ///
+ /// Indicates that the data type should be inherited from a style.
+ ///
+ Style = 1,
+
+ ///
+ /// Indicates that the data type should be inherited from a control template.
+ ///
+ ControlTemplate,
+}
+
+///
+/// Attribute that instructs the compiler to resolve the data type using specific scope hints, such as Style or ControlTemplate.
+///
+///
+/// This attribute is used to configure markup extensions like TemplateBinding to properly parse AvaloniaProperty values,
+/// targeting a specific scope data type.
+///
+[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
+public sealed class InheritDataTypeFromAttribute : Attribute
+{
+ ///
+ /// Initializes a new instance of the class with the specified scope kind.
+ ///
+ /// The kind of scope from which to inherit the data type.
+ public InheritDataTypeFromAttribute(InheritDataTypeFromScopeKind scopeKind)
+ {
+ ScopeKind = scopeKind;
+ }
+
+ ///
+ /// Gets the kind of scope from which the data type should be inherited.
+ ///
+ public InheritDataTypeFromScopeKind ScopeKind { get; }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
index 5cb5316b1c..53fa0a2a4d 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
@@ -263,12 +263,31 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
return true;
}
-
+
if (type.FullName == "Avalonia.AvaloniaProperty")
{
- var scope = context.ParentNodes().OfType().FirstOrDefault();
+ var attrType = context.GetAvaloniaTypes().InheritDataTypeFromAttribute;
+ var scopeKind = customAttributes?
+ .FirstOrDefault(a => a.Type.Equals(attrType))?.Parameters
+ .FirstOrDefault() switch
+ {
+ 1 => AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style,
+ 2 => AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate,
+ _ => (AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes?)null
+ };
+
+ var scope = context.ParentNodes().OfType()
+ .FirstOrDefault(s => scopeKind.HasValue ? s.ScopeType == scopeKind : true);
if (scope == null)
- throw new XamlX.XamlLoadException("Unable to find the parent scope for AvaloniaProperty lookup", node);
+ {
+#if NET6_0_OR_GREATER
+ var isScopeDefined = Enum.IsDefined(scopeKind ?? default);
+#else
+ var isScopeDefined = Enum.IsDefined(typeof(AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes), scopeKind ?? default);
+#endif
+ var scopeKindStr = isScopeDefined ? scopeKind!.Value.ToString() : "parent";
+ throw new XamlX.XamlLoadException($"Unable to find the {scopeKindStr} scope for AvaloniaProperty lookup", node);
+ }
result = XamlIlAvaloniaPropertyHelper.CreateNode(context, text, scope.TargetType, node );
return true;
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
index afe4654df9..d19fa977ee 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
IXamlAstTypeReference targetType;
- var templatableBaseType = context.Configuration.TypeSystem.GetType("Avalonia.Controls.Control");
+ var templatableBaseType = context.GetAvaloniaTypes().Control;
targetType = tt?.Values.FirstOrDefault() switch
{
@@ -49,7 +49,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public enum ScopeTypes
{
- Style,
+ Style = 1,
ControlTemplate,
Transitions
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
index a8718524a5..619940e208 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
@@ -86,7 +86,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlAstTextNode(node, property.Value, type: context.Configuration.WellKnownTypes.String),
- targetProperty.PropertyType, out var typedValue))
+ targetProperty, out var typedValue))
throw new XamlTransformException(
$"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}",
node);
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 01612bff27..4557335bdf 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
@@ -29,6 +29,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
IXamlType targetType = null;
IXamlLineInfo lineInfo = null;
+ var avaloniaTypes = context.GetAvaloniaTypes();
+
var styleParent = context.ParentNodes()
.OfType()
.FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
@@ -46,17 +48,24 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
IXamlType propType = null;
+ IXamlIlAvaloniaPropertyNode avaloniaPropertyNode = 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 XamlStyleTransformException("Setter.Property must be a string", node);
+ avaloniaPropertyNode = property.Values.OfType().FirstOrDefault();
+ if (avaloniaPropertyNode is null)
+ {
+ var propertyName = property.Values.OfType().FirstOrDefault()?.Text;
+ if (propertyName == null)
+ throw new XamlStyleTransformException("Setter.Property must be a string.", node);
+
+ avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
+ new XamlAstClrTypeReference(lineInfo, targetType, false), property.Values[0]);
+
+ property.Values = new List {avaloniaPropertyNode};
+ }
- var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
- new XamlAstClrTypeReference(lineInfo, targetType, false), property.Values[0]);
- property.Values = new List {avaloniaPropertyNode};
propType = avaloniaPropertyNode.AvaloniaPropertyType;
}
else
@@ -83,7 +92,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
valueProperty.Values[0]);
valueProperty.Property = new SetterValueProperty(valueProperty.Property,
- on.Type.GetClrType(), propType, context.GetAvaloniaTypes());
+ on.Type.GetClrType(), propType, avaloniaTypes);
+ }
+
+ // Handling a very specific case, when ITemplate value is used inside of Setter.Value,
+ // Which then is materialized for a specific control, and usually would set TemplatedParent.
+ // Note: this code is not always valid, as TemplatedParent might not be set,
+ // but we have better validation in runtime for TemplatedBinding.
+ // See Correctly_Resolve_TemplateBinding_In_Theme_Detached_Template test.
+ if (!avaloniaTypes.ITemplateOfControl.IsAssignableFrom(propType)
+ && on.Children.OfType()?.FirstOrDefault() is { } valueObj
+ && avaloniaTypes.ITemplateOfControl.IsAssignableFrom(valueObj?.Type.GetClrType()))
+ {
+ on.Children[on.Children.IndexOf(valueObj)] = new AvaloniaXamlIlTargetTypeMetadataNode(valueObj,
+ new XamlAstClrTypeReference(on, targetType, false),
+ AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate);
}
return node;
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
index c056f2b3f5..651bd80576 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Linq;
using XamlX;
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
index 9bad038d42..02201b8109 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
@@ -34,6 +34,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType DependsOnAttribute { get; }
public IXamlType DataTypeAttribute { get; }
public IXamlType InheritDataTypeFromItemsAttribute { get; }
+ public IXamlType InheritDataTypeFromAttribute { get; }
public IXamlType MarkupExtensionOptionAttribute { get; }
public IXamlType MarkupExtensionDefaultOptionAttribute { get; }
public IXamlType AvaloniaListAttribute { get; }
@@ -60,6 +61,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType DataTemplate { get; }
public IXamlType IDataTemplate { get; }
+ public IXamlType ITemplateOfControl { get; }
+ public IXamlType Control { get; }
public IXamlType ItemsControl { get; }
public IXamlType ReflectionBindingExtension { get; }
@@ -196,6 +199,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
DependsOnAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DependsOnAttribute");
DataTypeAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DataTypeAttribute");
InheritDataTypeFromItemsAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.InheritDataTypeFromItemsAttribute");
+ InheritDataTypeFromAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.InheritDataTypeFromAttribute");
MarkupExtensionOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionOptionAttribute");
MarkupExtensionDefaultOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionDefaultOptionAttribute");
AvaloniaListAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.AvaloniaListAttribute");
@@ -240,6 +244,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
ResolveByNameExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ResolveByNameExtension");
DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate");
IDataTemplate = cfg.TypeSystem.GetType("Avalonia.Controls.Templates.IDataTemplate");
+ Control = cfg.TypeSystem.GetType("Avalonia.Controls.Control");
+ ITemplateOfControl = cfg.TypeSystem.GetType("Avalonia.Controls.ITemplate`1").MakeGenericType(Control);
ItemsControl = cfg.TypeSystem.GetType("Avalonia.Controls.ItemsControl");
ReflectionBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension");
RelativeSource = cfg.TypeSystem.GetType("Avalonia.Data.RelativeSource");
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
index 387d153018..2ec19a1aa4 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
@@ -88,12 +88,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return new XamlIlAvaloniaPropertyFieldNode(context.GetAvaloniaTypes(), lineInfo, found);
}
- var clrProperty =
- ((XamlAstClrProperty)new PropertyReferenceResolver().Transform(context,
- forgedReference));
- return new XamlIlAvaloniaPropertyNode(lineInfo,
- context.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty"),
- clrProperty);
+ var clrProperty = (XamlAstClrProperty)new PropertyReferenceResolver().Transform(context, forgedReference);
+ var avaloniaPropertyBaseType = context.GetAvaloniaTypes().AvaloniaProperty;
+
+ // PropertyReferenceResolver.Transform failed resolving property, return empty stub from here:
+ if (clrProperty.DeclaringType == XamlPseudoType.Unknown)
+ {
+ return new XamlIlAvaloniaPropertyNode(lineInfo, avaloniaPropertyBaseType, clrProperty, XamlPseudoType.Unknown);
+ }
+
+ return new XamlIlAvaloniaPropertyNode(lineInfo, avaloniaPropertyBaseType, clrProperty);
}
public static IXamlType GetAvaloniaPropertyType(IXamlField field,
@@ -124,12 +128,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
class XamlIlAvaloniaPropertyNode : XamlAstNode, IXamlAstValueNode, IXamlIlAstEmitableNode, IXamlIlAvaloniaPropertyNode
{
- public XamlIlAvaloniaPropertyNode(IXamlLineInfo lineInfo, IXamlType type, XamlAstClrProperty property) : base(lineInfo)
+ public XamlIlAvaloniaPropertyNode(IXamlLineInfo lineInfo, IXamlType type, XamlAstClrProperty property, IXamlType propertyType) : base(lineInfo)
{
Type = new XamlAstClrTypeReference(this, type, false);
Property = property;
- AvaloniaPropertyType = Property.Getter?.ReturnType
- ?? Property.Setters.First().Parameters[0];
+ AvaloniaPropertyType = propertyType;
+ }
+
+ public XamlIlAvaloniaPropertyNode(IXamlLineInfo lineInfo, IXamlType type, XamlAstClrProperty property)
+ : this(lineInfo, type, property, GetPropertyType(property))
+ {
}
public XamlAstClrProperty Property { get; }
@@ -143,6 +151,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
public IXamlType AvaloniaPropertyType { get; }
+
+ private static IXamlType GetPropertyType(XamlAstClrProperty property) =>
+ property.Getter?.ReturnType
+ ?? property.Setters.FirstOrDefault()?.Parameters[0]
+ ?? throw new InvalidOperationException(
+ $"Unable to resolve \"{property.DeclaringType.Name}.{property.Name}\" property type. There is no setter or getter.");
}
class XamlIlAvaloniaPropertyFieldNode : XamlAstNode, IXamlAstValueNode, IXamlIlAstEmitableNode, IXamlIlAvaloniaPropertyNode
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
index 21a3f4eae2..0adb26973d 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
@@ -239,7 +239,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
var textNode = new XamlAstTextNode(lineInfo, indexer.Arguments[currentParamIndex], type: context.Configuration.WellKnownTypes.String);
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, textNode,
- param, out var converted))
+ property.CustomAttributes, param, out var converted))
throw new XamlX.XamlTransformException(
$"Unable to convert indexer parameter value of '{indexer.Arguments[currentParamIndex]}' to {param.GetFqn()}",
textNode);
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs
index 66ad6b6d6a..6c2ee2bb08 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs
@@ -1,5 +1,10 @@
using Avalonia.Controls;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Primitives;
+using Avalonia.Data;
+using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
+using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
@@ -103,6 +108,72 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal(Brushes.Red, border.Background);
}
}
+
+ [Fact]
+ public void Correctly_Resolve_TemplateBinding_In_Nested_Style()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = $@"
+
+
+
+
+
+
+
+";
+
+ var theme = (ControlTheme)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var style = Assert.IsType";
+
+ var style = (Style)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var setter = Assert.IsType(Assert.Single(style.Setters));
+
+ Assert.Equal(TestTemplatedControl.TestDataProperty, (setter.Value as TemplateBinding)?.Property);
+ }
+ }
+
+ [Fact]
+ public void Fails_To_Resolve_TemplateBinding_In_Style_Without_Template_Metadata()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = $@"
+";
+
+ var exception = Assert.ThrowsAny(() => AvaloniaRuntimeXamlLoader.Load(xaml));
+ Assert.Contains("ControlTemplate", exception.Message);
+ }
+ }
}
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestTemplatedControl.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestTemplatedControl.cs
index 0c862bb66a..b8d7d6502e 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestTemplatedControl.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestTemplatedControl.cs
@@ -4,5 +4,13 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
public class TestTemplatedControl : TemplatedControl
{
+ public static readonly StyledProperty