Skip to content

Commit

Permalink
Merge pull request #6665 from AvaloniaUI/non-control-templates
Browse files Browse the repository at this point in the history
Added support for non-control templates in XAML
  • Loading branch information
kekekeks authored Oct 4, 2021
2 parents 642b45f + 80c325d commit 5ece272
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/Avalonia.Base/Metadata/TemplateContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ namespace Avalonia.Metadata
[AttributeUsage(AttributeTargets.Property)]
public class TemplateContentAttribute : Attribute
{
public Type TemplateResultType { get; set; }
}
}
9 changes: 4 additions & 5 deletions src/Avalonia.Controls/Templates/IControlTemplate.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using Avalonia.Controls.Primitives;
using Avalonia.Styling;

Expand All @@ -10,18 +11,16 @@ public interface IControlTemplate : ITemplate<ITemplatedControl, ControlTemplate
{
}

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;
Expand Down
20 changes: 20 additions & 0 deletions src/Avalonia.Controls/Templates/TemplateResult.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,13 @@ public static (XamlLanguageTypeMappings language, XamlLanguageEmitMappings<IXaml
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"),
Expand Down
12 changes: 12 additions & 0 deletions src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -20,5 +21,16 @@ public static ControlTemplateResult Load(object 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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ public static class XamlIlRuntimeHelpers
{
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();
Expand All @@ -25,7 +31,11 @@ public static Func<IServiceProvider, object> DeferredTransformationFactoryV1(Fun
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);
};
}

Expand Down
62 changes: 62 additions & 0 deletions tests/Avalonia.Markup.Xaml.UnitTests/Xaml/GenericTemplateTests.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}

0 comments on commit 5ece272

Please sign in to comment.