From f1a2716858fc90067ffe6fa0651ee2a04f43c36a Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 26 May 2022 03:36:49 -0400 Subject: [PATCH] Inherit DataType from template without hardcoding on specific DataTemplate implementation --- .../Metadata/TemplateDataTypeAttribute.cs | 9 ++ ...valoniaXamlIlDataContextTypeTransformer.cs | 5 +- .../AvaloniaXamlIlWellKnownTypes.cs | 2 + .../Templates/DataTemplate.cs | 1 + .../Templates/TreeDataTemplate.cs | 1 + .../CompiledBindingExtensionTests.cs | 122 +++++++++++++++++- 6 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 src/Avalonia.Base/Metadata/TemplateDataTypeAttribute.cs 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 { } }