diff --git a/src/Avalonia.Base/Metadata/TemplateDataTypeAttribute.cs b/src/Avalonia.Base/Metadata/TemplateDataTypeAttribute.cs
new file mode 100644
index 0000000000..29e8cf4a06
--- /dev/null
+++ b/src/Avalonia.Base/Metadata/TemplateDataTypeAttribute.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Avalonia.Metadata;
+
+[AttributeUsage(AttributeTargets.Property)]
+public class TemplateDataTypeAttribute : Attribute
+{
+
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
index cf691db860..75859a0a18 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
@@ -49,6 +49,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
else if (child is XamlPropertyAssignmentNode pa)
{
+ var templateDataTypeAttribute = context.GetAvaloniaTypes().TemplateDataTypeAttribute;
+
if (pa.Property.Name == "DataContext"
&& pa.Property.DeclaringType.Equals(context.GetAvaloniaTypes().StyledElement)
&& pa.Values[0] is XamlMarkupExtensionNode ext
@@ -56,8 +58,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
inferredDataContextTypeNode = ParseDataContext(context, on, obj);
}
- else if(context.GetAvaloniaTypes().DataTemplate.IsAssignableFrom(on.Type.GetClrType())
- && pa.Property.Name == "DataType"
+ else if(pa.Property.CustomAttributes.Any(a => a.Type == templateDataTypeAttribute)
&& pa.Values[0] is XamlTypeExtensionNode dataTypeNode)
{
inferredDataContextTypeNode = new AvaloniaXamlIlDataContextTypeMetadataNode(on, dataTypeNode.Value.GetClrType());
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 5da40035d2..da28891968 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
@@ -26,6 +26,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType Transitions { get; }
public IXamlType AssignBindingAttribute { get; }
public IXamlType DependsOnAttribute { get; }
+ public IXamlType TemplateDataTypeAttribute { get; }
public IXamlType UnsetValueType { get; }
public IXamlType StyledElement { get; }
public IXamlType IStyledElement { get; }
@@ -112,6 +113,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
Transitions = cfg.TypeSystem.GetType("Avalonia.Animation.Transitions");
AssignBindingAttribute = cfg.TypeSystem.GetType("Avalonia.Data.AssignBindingAttribute");
DependsOnAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DependsOnAttribute");
+ TemplateDataTypeAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.TemplateDataTypeAttribute");
AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, IAvaloniaObject,
AvaloniaProperty,
IBinding, cfg.WellKnownTypes.Object);
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
index b7db1a3fbb..1dd3cddf9a 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
@@ -7,6 +7,7 @@ namespace Avalonia.Markup.Xaml.Templates
{
public class DataTemplate : IRecyclingDataTemplate
{
+ [TemplateDataType]
public Type DataType { get; set; }
[Content]
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
index 7b065c7f47..b07e501fde 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
@@ -11,6 +11,7 @@ namespace Avalonia.Markup.Xaml.Templates
{
public class TreeDataTemplate : ITreeDataTemplate
{
+ [TemplateDataType]
public Type DataType { get; set; }
[Content]
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
index c1c2284372..fe3df7d189 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
@@ -8,11 +8,14 @@ using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Templates;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.Input;
using Avalonia.Markup.Data;
+using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
+using Avalonia.Metadata;
using Avalonia.UnitTests;
using XamlX;
using Xunit;
@@ -455,7 +458,106 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
ThrowsXamlTransformException(() => AvaloniaRuntimeXamlLoader.Load(xaml));
}
}
+
+ [Fact]
+ public void IgnoresDataTemplateTypeFromDataTypePropertyIfXDataTypeDefined()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+";
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var target = window.FindControl("target");
+
+ var dataContext = new TestDataContext();
+
+ dataContext.StringProperty = "Initial Value";
+
+ window.DataContext = dataContext;
+ window.ApplyTemplate();
+ target.ApplyTemplate();
+ ((ContentPresenter)target.Presenter).UpdateChild();
+
+ Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child).Text);
+ }
+ }
+
+ [Fact]
+ public void InfersCustomDataTemplateBasedOnAttribute()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+";
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var target = window.FindControl("target");
+
+ var dataContext = new TestDataContext();
+
+ dataContext.StringProperty = "Initial Value";
+
+ window.DataContext = dataContext;
+
+ window.ApplyTemplate();
+ target.ApplyTemplate();
+ ((ContentPresenter)target.Presenter).UpdateChild();
+
+ Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child).Text);
+ }
+ }
+
+ [Fact]
+ public void InfersCustomDataTemplateBasedOnAttributeFromBaseClass()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+";
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var target = window.FindControl("target");
+
+ var dataContext = new TestDataContext();
+
+ dataContext.StringProperty = "Initial Value";
+
+ window.DataContext = dataContext;
+
+ window.ApplyTemplate();
+ target.ApplyTemplate();
+ ((ContentPresenter)target.Presenter).UpdateChild();
+
+ Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child).Text);
+ }
+ }
+
[Fact]
public void ResolvesElementNameBinding()
{
@@ -1324,7 +1426,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
public string StringProperty { get; set; }
}
- public class TestDataContext : IHasPropertyDerived
+ public class TestDataContextBaseClass {}
+
+ public class TestDataContext : TestDataContextBaseClass, IHasPropertyDerived
{
public string StringProperty { get; set; }
@@ -1413,4 +1517,20 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
return ReferenceEquals(null, parameter) == false;
}
}
+
+ public class CustomDataTemplate : IDataTemplate
+ {
+ [TemplateDataType]
+ public Type FancyDataType { get; set; }
+
+ [Content]
+ [TemplateContent]
+ public object Content { get; set; }
+
+ public bool Match(object data) => FancyDataType.IsInstanceOfType(data);
+
+ public IControl Build(object data) => TemplateContent.Load(Content)?.Control;
+ }
+
+ public class CustomDataTemplateInherit : CustomDataTemplate { }
}