Browse Source

Implicitly map x:DataType to a DataType on a DataTemplate.

pull/8220/head
Max Katz 4 years ago
parent
commit
d2af5dbcac
  1. 8
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  2. 79
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XDataTypeTransformer.cs
  3. 58
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs
  4. 17
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs

8
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -31,10 +31,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
// Before everything else // Before everything else
Transformers.Insert(0, new XNameTransformer()); Transformers.Insert(0, new XNameTransformer());
Transformers.Insert(1, new IgnoredDirectivesTransformer()); Transformers.Insert(1, new XDataTypeTransformer());
Transformers.Insert(2, _designTransformer = new AvaloniaXamlIlDesignPropertiesTransformer()); Transformers.Insert(2, new IgnoredDirectivesTransformer());
Transformers.Insert(3, _bindingTransformer = new AvaloniaBindingExtensionTransformer()); Transformers.Insert(3, _designTransformer = new AvaloniaXamlIlDesignPropertiesTransformer());
Transformers.Insert(4, _bindingTransformer = new AvaloniaBindingExtensionTransformer());
// Targeted // Targeted
InsertBefore<PropertyReferenceResolver>( InsertBefore<PropertyReferenceResolver>(

79
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XDataTypeTransformer.cs

@ -0,0 +1,79 @@
using System.Collections.Generic;
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
{
internal class XDataTypeTransformer : IXamlAstTransformer
{
private const string DataTypePropertyName = "DataType";
/// <summary>
/// Converts x:DataType directives to regular DataType assignments if property with Avalonia.Metadata.DataTypeAttribute exists.
/// </summary>
/// <returns></returns>
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlAstObjectNode on)
{
for (var c = 0; c < on.Children.Count; c++)
{
var ch = on.Children[c];
if (ch is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: DataTypePropertyName } d)
{
if (on.Children.OfType<XamlAstXamlPropertyValueNode>()
.Any(p => ((XamlAstNamePropertyReference)p.Property)?.Name == DataTypePropertyName))
{
// Break iteration if any DataType property was already set by user code.
break;
}
var templateDataTypeAttribute = context.GetAvaloniaTypes().DataTypeAttribute;
var clrType = on.Type switch
{
XamlAstClrTypeReference clrRef => clrRef.Type,
XamlAstXmlTypeReference xmlRef => TypeReferenceResolver.ResolveType(context, xmlRef.Name,
on.Type.IsMarkupExtension, on, strict: false).Type,
_ => null
};
if (clrType is null)
{
break;
}
// Technically it's possible to map "x:DataType" to a property with [DataType] attribute regardless of its name,
// but we go explicitly strict here and check the name as well.
var (declaringType, dataTypeProperty) = GetAllProperties(clrType)
.FirstOrDefault(t => t.property.Name == DataTypePropertyName && t.property.CustomAttributes
.Any(a => a.Type == templateDataTypeAttribute));
if (dataTypeProperty is not null)
{
on.Children[c] = new XamlAstXamlPropertyValueNode(d,
new XamlAstNamePropertyReference(d,
new XamlAstClrTypeReference(ch, declaringType, false), dataTypeProperty.Name,
on.Type),
d.Values);
}
}
}
}
return node;
}
private static IEnumerable<(IXamlType declaringType, IXamlProperty property)> GetAllProperties(IXamlType t)
{
foreach (var p in t.Properties)
yield return (t, p);
if(t.BaseType!=null)
foreach (var tuple in GetAllProperties(t.BaseType))
yield return tuple;
}
}
}

58
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs

@ -1,5 +1,7 @@
using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Xunit; using Xunit;
@ -89,6 +91,62 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
} }
} }
[Fact]
public void XDataType_Should_Be_Assigned_To_Clr_Property()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:sys='clr-namespace:System;assembly=netstandard'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.DataTemplates>
<DataTemplate x:DataType='sys:String'>
<Canvas Name='foo'/>
</DataTemplate>
</Window.DataTemplates>
<ContentControl Name='target' Content='Foo'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var target = window.FindControl<ContentControl>("target");
var template = (DataTemplate)window.DataTemplates.First();
window.ApplyTemplate();
target.ApplyTemplate();
((ContentPresenter)target.Presenter).UpdateChild();
Assert.Equal(typeof(string), template.DataType);
Assert.IsType<Canvas>(target.Presenter.Child);
}
}
[Fact]
public void XDataType_Should_Be_Ignored_If_DataType_Already_Set()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:sys='clr-namespace:System;assembly=netstandard'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.DataTemplates>
<DataTemplate DataType='sys:String' x:DataType='UserControl'>
<Canvas Name='foo'/>
</DataTemplate>
</Window.DataTemplates>
<ContentControl Name='target' Content='Foo'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var target = window.FindControl<ContentControl>("target");
window.ApplyTemplate();
target.ApplyTemplate();
((ContentPresenter)target.Presenter).UpdateChild();
Assert.IsType<Canvas>(target.Presenter.Child);
}
}
[Fact] [Fact]
public void Can_Set_DataContext_In_DataTemplate() public void Can_Set_DataContext_In_DataTemplate()
{ {

17
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs

@ -21,5 +21,22 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.IsType<Binding>(template.ItemsSource); Assert.IsType<Binding>(template.ItemsSource);
} }
} }
[Fact]
public void XDataType_Should_Be_Assigned_To_Clr_Property()
{
using (UnitTestApplication.Start(TestServices.MockPlatformWrapper))
{
var xaml = @"
<DataTemplates xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<TreeDataTemplate x:DataType='x:String' />
</DataTemplates>";
var templates = (DataTemplates)AvaloniaRuntimeXamlLoader.Load(xaml);
var template = (TreeDataTemplate)(templates.First());
Assert.Equal(typeof(string), template.DataType);
}
}
} }
} }

Loading…
Cancel
Save