|
|
@@ -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!;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|