committed by
GitHub
10 changed files with 302 additions and 37 deletions
@ -0,0 +1,29 @@ |
|||
using Avalonia.Controls.Templates; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public abstract class ElementFactory : IElementFactory |
|||
{ |
|||
bool IDataTemplate.SupportsRecycling => false; |
|||
|
|||
public IControl Build(object data) |
|||
{ |
|||
return GetElementCore(new ElementFactoryGetArgs { Data = data }); |
|||
} |
|||
|
|||
public IControl GetElement(ElementFactoryGetArgs args) |
|||
{ |
|||
return GetElementCore(args); |
|||
} |
|||
|
|||
public bool Match(object data) => true; |
|||
|
|||
public void RecycleElement(ElementFactoryRecycleArgs args) |
|||
{ |
|||
RecycleElementCore(args); |
|||
} |
|||
|
|||
protected abstract IControl GetElementCore(ElementFactoryGetArgs args); |
|||
protected abstract void RecycleElementCore(ElementFactoryRecycleArgs args); |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
using Avalonia.Controls.Templates; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Represents the optional arguments to use when calling an implementation of the
|
|||
/// <see cref="IElementFactory"/>'s <see cref="IElementFactory.GetElement"/> method.
|
|||
/// </summary>
|
|||
public class ElementFactoryGetArgs |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the data item for which an appropriate element tree should be realized
|
|||
/// when calling <see cref="IElementFactory.GetElement"/>.
|
|||
/// </summary>
|
|||
public object Data { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the <see cref="IControl"/> that is expected to be the parent of the
|
|||
/// realized element from <see cref="IElementFactory.GetElement"/>.
|
|||
/// </summary>
|
|||
public IControl Parent { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the index of the item that should be realized.
|
|||
/// </summary>
|
|||
public int Index { get; set; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents the optional arguments to use when calling an implementation of the
|
|||
/// <see cref="IElementFactory"/>'s <see cref="IElementFactory.GetElement"/> method.
|
|||
/// </summary>
|
|||
public class ElementFactoryRecycleArgs |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the <see cref="IControl"/> to recycle when calling
|
|||
/// <see cref="IElementFactory.RecycleElement"/>.
|
|||
/// </summary>
|
|||
public IControl Element { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the <see cref="IControl"/> that is expected to be the parent of the
|
|||
/// realized element from <see cref="IElementFactory.GetElement"/>.
|
|||
/// </summary>
|
|||
public IControl Parent { get; set; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A data template that supports creating and recyling elements for an <see cref="ItemsRepeater"/>.
|
|||
/// </summary>
|
|||
public interface IElementFactory : IDataTemplate |
|||
{ |
|||
/// <summary>
|
|||
/// Gets an <see cref="IControl"/>.
|
|||
/// </summary>
|
|||
/// <param name="args">The element args.</param>
|
|||
public IControl GetElement(ElementFactoryGetArgs args); |
|||
|
|||
/// <summary>
|
|||
/// Recycles an <see cref="IControl"/> that was previously retrieved using
|
|||
/// <see cref="GetElement"/>.
|
|||
/// </summary>
|
|||
/// <param name="args">The recycle args.</param>
|
|||
public void RecycleElement(ElementFactoryRecycleArgs args); |
|||
} |
|||
} |
|||
@ -0,0 +1,119 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Controls.Templates; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class SelectTemplateEventArgs : EventArgs |
|||
{ |
|||
public string? TemplateKey { get; set; } |
|||
public object? DataContext { get; internal set; } |
|||
public IControl? Owner { get; internal set; } |
|||
} |
|||
|
|||
public class RecyclingElementFactory : ElementFactory |
|||
{ |
|||
private RecyclePool? _recyclePool; |
|||
private IDictionary<string, IDataTemplate>? _templates; |
|||
private SelectTemplateEventArgs? _args; |
|||
|
|||
public RecyclingElementFactory() |
|||
{ |
|||
Templates = new Dictionary<string, IDataTemplate>(); |
|||
} |
|||
|
|||
public RecyclePool RecyclePool |
|||
{ |
|||
get => _recyclePool ??= new RecyclePool(); |
|||
set => _recyclePool = value ?? throw new ArgumentNullException(nameof(value)); |
|||
} |
|||
|
|||
public IDictionary<string, IDataTemplate> Templates |
|||
{ |
|||
get => _templates ??= new Dictionary<string, IDataTemplate>(); |
|||
set => _templates = value ?? throw new ArgumentNullException(nameof(value)); |
|||
} |
|||
|
|||
public event EventHandler<SelectTemplateEventArgs>? SelectTemplateKey; |
|||
|
|||
protected override IControl GetElementCore(ElementFactoryGetArgs args) |
|||
{ |
|||
if (_templates == null || _templates.Count == 0) |
|||
{ |
|||
throw new InvalidOperationException("Templates cannot be empty."); |
|||
} |
|||
|
|||
var templateKey = Templates.Count == 1 ? |
|||
Templates.First().Key : |
|||
OnSelectTemplateKeyCore(args.Data, args.Parent); |
|||
|
|||
if (string.IsNullOrEmpty(templateKey)) |
|||
{ |
|||
// Note: We could allow null/whitespace, which would work as long as
|
|||
// the recycle pool is not shared. in order to make this work in all cases
|
|||
// currently we validate that a valid template key is provided.
|
|||
throw new InvalidOperationException("Template key cannot be null or empty."); |
|||
} |
|||
|
|||
// Get an element from the Recycle Pool or create one
|
|||
var element = RecyclePool.TryGetElement(templateKey, args.Parent); |
|||
|
|||
if (element is null) |
|||
{ |
|||
// No need to call HasKey if there is only one template.
|
|||
if (Templates.Count > 1 && !Templates.ContainsKey(templateKey)) |
|||
{ |
|||
var message = $"No templates of key '{templateKey}' were found in the templates collection."; |
|||
throw new InvalidOperationException(message); |
|||
} |
|||
|
|||
var dataTemplate = Templates[templateKey]; |
|||
element = dataTemplate.Build(args.Data); |
|||
|
|||
// Associate ReuseKey with element
|
|||
RecyclePool.SetReuseKey(element, templateKey); |
|||
} |
|||
|
|||
return element; |
|||
} |
|||
|
|||
protected override void RecycleElementCore(ElementFactoryRecycleArgs args) |
|||
{ |
|||
var element = args.Element; |
|||
var key = RecyclePool.GetReuseKey(element); |
|||
RecyclePool.PutElement(element, key, args.Parent); |
|||
} |
|||
|
|||
protected virtual string OnSelectTemplateKeyCore(object dataContext, IControl owner) |
|||
{ |
|||
if (SelectTemplateKey is object) |
|||
{ |
|||
_args ??= new SelectTemplateEventArgs(); |
|||
_args.TemplateKey = null; |
|||
_args.DataContext = dataContext; |
|||
_args.Owner = owner; |
|||
|
|||
try |
|||
{ |
|||
SelectTemplateKey(this, _args); |
|||
} |
|||
finally |
|||
{ |
|||
_args.DataContext = null; |
|||
_args.Owner = null; |
|||
} |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(_args?.TemplateKey)) |
|||
{ |
|||
throw new InvalidOperationException( |
|||
"Please provide a valid template identifier in the handler for the SelectTemplateKey event."); |
|||
} |
|||
|
|||
return _args!.TemplateKey!; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue