Browse Source

Merge pull request #6665 from AvaloniaUI/non-control-templates

Added support for non-control templates in XAML
pull/6648/head
Nikita Tsukanov 5 years ago
committed by GitHub
parent
commit
5ece272a59
  1. 1
      src/Avalonia.Base/Metadata/TemplateContent.cs
  2. 9
      src/Avalonia.Controls/Templates/IControlTemplate.cs
  3. 20
      src/Avalonia.Controls/Templates/TemplateResult.cs
  4. 7
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  5. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
  6. 12
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
  7. 12
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  8. 62
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/GenericTemplateTests.cs

1
src/Avalonia.Base/Metadata/TemplateContent.cs

@ -8,5 +8,6 @@ namespace Avalonia.Metadata
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public class TemplateContentAttribute : Attribute public class TemplateContentAttribute : Attribute
{ {
public Type TemplateResultType { get; set; }
} }
} }

9
src/Avalonia.Controls/Templates/IControlTemplate.cs

@ -1,3 +1,4 @@
using System;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Styling; using Avalonia.Styling;
@ -10,18 +11,16 @@ namespace Avalonia.Controls.Templates
{ {
} }
public class ControlTemplateResult public class ControlTemplateResult : TemplateResult<IControl>
{ {
public IControl Control { get; } public IControl Control { get; }
public INameScope NameScope { get; }
public ControlTemplateResult(IControl control, INameScope nameScope) public ControlTemplateResult(IControl control, INameScope nameScope) : base(control, nameScope)
{ {
Control = control; Control = control;
NameScope = nameScope;
} }
public void Deconstruct(out IControl control, out INameScope scope) public new void Deconstruct(out IControl control, out INameScope scope)
{ {
control = Control; control = Control;
scope = NameScope; scope = NameScope;

20
src/Avalonia.Controls/Templates/TemplateResult.cs

@ -0,0 +1,20 @@
namespace Avalonia.Controls.Templates
{
public class TemplateResult<T>
{
public T Result { get; }
public INameScope NameScope { get; }
public TemplateResult(T result, INameScope nameScope)
{
Result = result;
NameScope = nameScope;
}
public void Deconstruct(out T result, out INameScope scope)
{
result = Result;
scope = NameScope;
}
}
}

7
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs

@ -49,8 +49,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
XmlNamespaceInfoProvider = XmlNamespaceInfoProvider =
typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlXmlNamespaceInfoProvider"), typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlXmlNamespaceInfoProvider"),
DeferredContentPropertyAttributes = {typeSystem.GetType("Avalonia.Metadata.TemplateContentAttribute")}, DeferredContentPropertyAttributes = {typeSystem.GetType("Avalonia.Metadata.TemplateContentAttribute")},
DeferredContentExecutorCustomizationDefaultTypeParameter = typeSystem.GetType("Avalonia.Controls.IControl"),
DeferredContentExecutorCustomizationTypeParameterDeferredContentAttributePropertyNames = new List<string>
{
"TemplateResultType"
},
DeferredContentExecutorCustomization = DeferredContentExecutorCustomization =
runtimeHelpers.FindMethod(m => m.Name == "DeferredTransformationFactoryV1"), runtimeHelpers.FindMethod(m => m.Name == "DeferredTransformationFactoryV2"),
UsableDuringInitializationAttributes = UsableDuringInitializationAttributes =
{ {
typeSystem.GetType("Avalonia.Metadata.UsableDuringInitializationAttribute"), typeSystem.GetType("Avalonia.Metadata.UsableDuringInitializationAttribute"),

2
src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github

@ -1 +1 @@
Subproject commit f4ac681b91a9dc7a7a095d1050a683de23d86b72 Subproject commit 8e20d65eb5f1efbae08e49b18f39bfdce32df7b3

12
src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs

@ -7,6 +7,7 @@ namespace Avalonia.Markup.Xaml.Templates
public static class TemplateContent public static class TemplateContent
{ {
public static ControlTemplateResult Load(object templateContent) public static ControlTemplateResult Load(object templateContent)
{ {
if (templateContent is Func<IServiceProvider, object> direct) if (templateContent is Func<IServiceProvider, object> direct)
{ {
@ -20,5 +21,16 @@ namespace Avalonia.Markup.Xaml.Templates
throw new ArgumentException(nameof(templateContent)); throw new ArgumentException(nameof(templateContent));
} }
public static TemplateResult<T> Load<T>(object templateContent)
{
if (templateContent is Func<IServiceProvider, object> direct)
return (TemplateResult<T>)direct(null);
if (templateContent is null)
return null;
throw new ArgumentException(nameof(templateContent));
}
} }
} }

12
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -15,6 +15,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{ {
public static Func<IServiceProvider, object> DeferredTransformationFactoryV1(Func<IServiceProvider, object> builder, public static Func<IServiceProvider, object> DeferredTransformationFactoryV1(Func<IServiceProvider, object> builder,
IServiceProvider provider) IServiceProvider provider)
{
return DeferredTransformationFactoryV2<IControl>(builder, provider);
}
public static Func<IServiceProvider, object> DeferredTransformationFactoryV2<T>(Func<IServiceProvider, object> builder,
IServiceProvider provider)
{ {
var resourceNodes = provider.GetService<IAvaloniaXamlIlParentStackProvider>().Parents var resourceNodes = provider.GetService<IAvaloniaXamlIlParentStackProvider>().Parents
.OfType<IResourceNode>().ToList(); .OfType<IResourceNode>().ToList();
@ -25,7 +31,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
var scope = parentScope != null ? new ChildNameScope(parentScope) : (INameScope)new NameScope(); var scope = parentScope != null ? new ChildNameScope(parentScope) : (INameScope)new NameScope();
var obj = builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject, scope)); var obj = builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject, scope));
scope.Complete(); scope.Complete();
return new ControlTemplateResult((IControl)obj, scope);
if(typeof(T) == typeof(IControl))
return new ControlTemplateResult((IControl)obj, scope);
return new TemplateResult<T>((T)obj, scope);
}; };
} }

62
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/GenericTemplateTests.cs

@ -0,0 +1,62 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Metadata;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
public class SampleTemplatedObject : StyledElement
{
[Content] public List<SampleTemplatedObject> Content { get; set; } = new List<SampleTemplatedObject>();
public string Foo { get; set; }
}
public class SampleTemplatedObjectTemplate
{
[Content]
[TemplateContent(TemplateResultType = typeof(SampleTemplatedObject))]
public object Content { get; set; }
}
public class SampleTemplatedObjectContainer
{
public SampleTemplatedObjectTemplate Template { get; set; }
}
public class GenericTemplateTests
{
[Fact]
public void DataTemplate_Can_Be_Empty()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<s:SampleTemplatedObjectContainer xmlns='https://github.com/avaloniaui'
xmlns:sys='clr-namespace:System;assembly=netstandard'
xmlns:s='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<s:SampleTemplatedObjectContainer.Template>
<s:SampleTemplatedObjectTemplate>
<s:SampleTemplatedObject x:Name='root'>
<s:SampleTemplatedObject x:Name='child1' Foo='foo' />
<s:SampleTemplatedObject x:Name='child2' Foo='bar' />
</s:SampleTemplatedObject>
</s:SampleTemplatedObjectTemplate>
</s:SampleTemplatedObjectContainer.Template>
</s:SampleTemplatedObjectContainer>";
var container =
(SampleTemplatedObjectContainer)AvaloniaRuntimeXamlLoader.Load(xaml,
typeof(GenericTemplateTests).Assembly);
var res = TemplateContent.Load<SampleTemplatedObject>(container.Template.Content);
Assert.Equal(res.Result, res.NameScope.Find("root"));
Assert.Equal(res.Result.Content[0], res.NameScope.Find("child1"));
Assert.Equal(res.Result.Content[1], res.NameScope.Find("child2"));
Assert.Equal("foo", res.Result.Content[0].Foo);
Assert.Equal("bar", res.Result.Content[1].Foo);
}
}
}
}
Loading…
Cancel
Save