Browse Source

Fix TemplateBinding and allow custom attributes in XamlValueConverter (#14612)

* Add InheritDataTypeFromAttribute and use it in TemplateBinding

* Add failing tests for TemplateBinding depending on a scope

* Update XamlX and RoslynTypeSystem

* Add missing interface implementations

* Improve errors readability in XamlAvaloniaPropertyHelper

* Use more specific TryGetCorrectlyTypedValue overloads

* Finally, respect InheritDataTypeFromAttribute in the AvaloniaProperty parser

* Add some docs

* Output better exception

* Update XamlX

* Add missing docs

* Add attribute to well known types

* Add Correctly_Resolve_TemplateBinding_In_Theme_Detached_Template test and fix ColorPicker usage

---------

Co-authored-by: Steven Kirk <grokys@users.noreply.github.com>
pull/16143/head
Max Katz 2 years ago
committed by GitHub
parent
commit
fbf559d86c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      src/Avalonia.Base/Data/TemplateBinding.cs
  2. 44
      src/Avalonia.Base/Metadata/InheritDataTypeFromAttribute.cs
  3. 25
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  4. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
  5. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
  6. 37
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
  7. 1
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
  8. 6
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  9. 32
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
  10. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
  11. 71
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlThemeTests.cs
  12. 37
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  13. 8
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestTemplatedControl.cs

4
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
/// <summary>
/// Gets or sets the name of the source property on the templated parent.
/// </summary>
[InheritDataTypeFrom(InheritDataTypeFromScopeKind.ControlTemplate)]
public AvaloniaProperty? Property { get; set; }
/// <inheritdoc/>

44
src/Avalonia.Base/Metadata/InheritDataTypeFromAttribute.cs

@ -0,0 +1,44 @@
using System;
namespace Avalonia.Metadata;
/// <summary>
/// Represents the kind of scope from which a data type can be inherited. Used in resolving target for AvaloniaProperty.
/// </summary>
public enum InheritDataTypeFromScopeKind
{
/// <summary>
/// Indicates that the data type should be inherited from a style.
/// </summary>
Style = 1,
/// <summary>
/// Indicates that the data type should be inherited from a control template.
/// </summary>
ControlTemplate,
}
/// <summary>
/// Attribute that instructs the compiler to resolve the data type using specific scope hints, such as Style or ControlTemplate.
/// </summary>
/// <remarks>
/// This attribute is used to configure markup extensions like TemplateBinding to properly parse AvaloniaProperty values,
/// targeting a specific scope data type.
/// </remarks>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public sealed class InheritDataTypeFromAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="InheritDataTypeFromAttribute"/> class with the specified scope kind.
/// </summary>
/// <param name="scopeKind">The kind of scope from which to inherit the data type.</param>
public InheritDataTypeFromAttribute(InheritDataTypeFromScopeKind scopeKind)
{
ScopeKind = scopeKind;
}
/// <summary>
/// Gets the kind of scope from which the data type should be inherited.
/// </summary>
public InheritDataTypeFromScopeKind ScopeKind { get; }
}

25
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<AvaloniaXamlIlTargetTypeMetadataNode>().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<AvaloniaXamlIlTargetTypeMetadataNode>()
.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<AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes>(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;

4
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
}

2
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);

37
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<AvaloniaXamlIlTargetTypeMetadataNode>()
.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<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "Property");
if (property != null)
{
var propertyName = property.Values.OfType<XamlAstTextNode>().FirstOrDefault()?.Text;
if (propertyName == null)
throw new XamlStyleTransformException("Setter.Property must be a string", node);
avaloniaPropertyNode = property.Values.OfType<IXamlIlAvaloniaPropertyNode>().FirstOrDefault();
if (avaloniaPropertyNode is null)
{
var propertyName = property.Values.OfType<XamlAstTextNode>().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<IXamlAstValueNode> {avaloniaPropertyNode};
}
var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
new XamlAstClrTypeReference(lineInfo, targetType, false), property.Values[0]);
property.Values = new List<IXamlAstValueNode> {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<XamlAstObjectNode>()?.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;

1
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;

6
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");

32
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

2
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);

71
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 = $@"
<ControlTheme xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:u='using:Avalonia.Markup.Xaml.UnitTests.Xaml'
TargetType='u:TestTemplatedControl'>
<Setter Property='Template'>
<ControlTemplate>
<Border/>
</ControlTemplate>
</Setter>
<Style Selector='^ /template/ Border'>
<Setter Property='Tag' Value='{{TemplateBinding TestData}}'/>
</Style>
</ControlTheme>";
var theme = (ControlTheme)AvaloniaRuntimeXamlLoader.Load(xaml);
var style = Assert.IsType<Style>(Assert.Single(theme.Children));
var setter = Assert.IsType<Setter>(Assert.Single(style.Setters));
Assert.Equal(TestTemplatedControl.TestDataProperty, (setter.Value as TemplateBinding)?.Property);
}
}
[Fact]
public void Correctly_Resolve_TemplateBinding_In_Theme_Detached_Template()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = (Window)AvaloniaRuntimeXamlLoader.Load($@"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:u='using:Avalonia.Markup.Xaml.UnitTests.Xaml'>
<Window.Resources>
<ControlTheme x:Key='MyTheme' TargetType='ContentControl'>
<Setter Property='CornerRadius' Value='10, 0, 0, 10' />
<Setter Property='Content'>
<Template>
<Border CornerRadius='{{TemplateBinding CornerRadius}}'/>
</Template>
</Setter>
<Setter Property='Template'>
<ControlTemplate>
<Button Content='{{TemplateBinding Content}}'
ContentTemplate='{{TemplateBinding ContentTemplate}}' />
</ControlTemplate>
</Setter>
</ControlTheme>
</Window.Resources>
<ContentControl Theme='{{StaticResource MyTheme}}' />
</Window>");
var control = Assert.IsType<ContentControl>(window.Content);
window.Show();
var border = Assert.IsType<Border>(control.Content);
Assert.Equal(new CornerRadius(10, 0, 0, 10), border.CornerRadius);
}
}
private const string ControlThemeXaml = @"
<ControlTheme x:Key='MyTheme' TargetType='u:TestTemplatedControl'>

37
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -5,6 +5,7 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Xml;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
@ -603,5 +604,41 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
inner => Assert.IsAssignableFrom<XmlException>(inner));
}
}
[Fact]
public void Correctly_Resolve_TemplateBinding_In_Style_With_Template_Selector()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = $@"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:u='using:Avalonia.Markup.Xaml.UnitTests.Xaml'
Selector='u|TestTemplatedControl /template/ Border'>
<Setter Property='Tag' Value='{{TemplateBinding TestData}}'/>
</Style>";
var style = (Style)AvaloniaRuntimeXamlLoader.Load(xaml);
var setter = Assert.IsType<Setter>(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 = $@"
<Style xmlns='https://github.com/avaloniaui'
Selector='Border'>
<Setter Property='Tag' Value='{{TemplateBinding TestData}}'/>
</Style>";
var exception = Assert.ThrowsAny<XmlException>(() => AvaloniaRuntimeXamlLoader.Load(xaml));
Assert.Contains("ControlTemplate", exception.Message);
}
}
}
}

8
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<object> TestDataProperty =
AvaloniaProperty.Register<TestTemplatedControl, object>(nameof(TestData));
public object TestData
{
get => GetValue(TestDataProperty);
set => SetValue(TestDataProperty, value);
}
}
}

Loading…
Cancel
Save