Browse Source

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

Added support for non-control templates in XAML
release/0.10.8
Nikita Tsukanov 4 years ago
committed by Dan Walmsley
parent
commit
80c35ff14b
  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)]
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.Styling;
@ -10,18 +11,16 @@ namespace Avalonia.Controls.Templates
{
}
public class ControlTemplateResult
public class ControlTemplateResult : TemplateResult<IControl>
{
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;
NameScope = nameScope;
}
public void Deconstruct(out IControl control, out INameScope scope)
public new void Deconstruct(out IControl control, out INameScope scope)
{
control = Control;
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 =
typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlXmlNamespaceInfoProvider"),
DeferredContentPropertyAttributes = {typeSystem.GetType("Avalonia.Metadata.TemplateContentAttribute")},
DeferredContentExecutorCustomizationDefaultTypeParameter = typeSystem.GetType("Avalonia.Controls.IControl"),
DeferredContentExecutorCustomizationTypeParameterDeferredContentAttributePropertyNames = new List<string>
{
"TemplateResultType"
},
DeferredContentExecutorCustomization =
runtimeHelpers.FindMethod(m => m.Name == "DeferredTransformationFactoryV1"),
runtimeHelpers.FindMethod(m => m.Name == "DeferredTransformationFactoryV2"),
UsableDuringInitializationAttributes =
{
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 ControlTemplateResult Load(object templateContent)
{
if (templateContent is Func<IServiceProvider, object> direct)
{
@ -20,5 +21,16 @@ namespace Avalonia.Markup.Xaml.Templates
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,
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
.OfType<IResourceNode>().ToList();
@ -25,7 +31,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
var scope = parentScope != null ? new ChildNameScope(parentScope) : (INameScope)new NameScope();
var obj = builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject, scope));
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