Browse Source

Merge pull request #4271 from AvaloniaUI/refactor/recyclingdatatemplate

Added IRecyclingDataTemplate.
pull/4340/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
0cf6bf2e9c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  2. 21
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  3. 2
      src/Avalonia.Controls/Repeater/ElementFactory.cs
  4. 1
      src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs
  5. 29
      src/Avalonia.Controls/Templates/FuncDataTemplate.cs
  6. 6
      src/Avalonia.Controls/Templates/FuncTemplate`2.cs
  7. 12
      src/Avalonia.Controls/Templates/IDataTemplate.cs
  8. 25
      src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs
  9. 2
      src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs
  10. 11
      src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
  11. 2
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  12. 2
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

1
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@ -142,7 +142,6 @@ namespace Avalonia.Controls.Generators
private readonly IDataTemplate _inner;
public WrapperTreeDataTemplate(IDataTemplate inner) => _inner = inner;
public IControl Build(object param) => _inner.Build(param);
public bool SupportsRecycling => _inner.SupportsRecycling;
public bool Match(object data) => _inner.Match(data);
public InstancedBinding ItemsSelector(object item) => null;
}

21
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -86,7 +86,7 @@ namespace Avalonia.Controls.Presenters
private IControl _child;
private bool _createdChild;
private IDataTemplate _dataTemplate;
private IRecyclingDataTemplate _recyclingDataTemplate;
private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
/// <summary>
@ -281,7 +281,7 @@ namespace Avalonia.Controls.Presenters
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
_dataTemplate = null;
_recyclingDataTemplate = null;
_createdChild = false;
InvalidateMeasure();
}
@ -307,22 +307,21 @@ namespace Avalonia.Controls.Presenters
{
var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
// We have content and it isn't a control, so if the new data template is the same
// as the old data template, try to recycle the existing child control to display
// the new data.
if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling)
if (dataTemplate is IRecyclingDataTemplate rdt)
{
newChild = oldChild;
var toRecycle = rdt == _recyclingDataTemplate ? oldChild : null;
newChild = rdt.Build(content, toRecycle);
_recyclingDataTemplate = rdt;
}
else
{
_dataTemplate = dataTemplate;
newChild = _dataTemplate.Build(content);
newChild = dataTemplate.Build(content);
_recyclingDataTemplate = null;
}
}
else
{
_dataTemplate = null;
_recyclingDataTemplate = null;
}
return newChild;
@ -422,7 +421,7 @@ namespace Avalonia.Controls.Presenters
LogicalChildren.Remove(Child);
((ISetInheritanceParent)Child).SetParent(Child.Parent);
Child = null;
_dataTemplate = null;
_recyclingDataTemplate = null;
}
InvalidateMeasure();

2
src/Avalonia.Controls/Repeater/ElementFactory.cs

@ -4,8 +4,6 @@ namespace Avalonia.Controls
{
public abstract class ElementFactory : IElementFactory
{
bool IDataTemplate.SupportsRecycling => false;
public IControl Build(object data)
{
return GetElementCore(new ElementFactoryGetArgs { Data = data });

1
src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs

@ -13,7 +13,6 @@ namespace Avalonia.Controls
public ItemTemplateWrapper(IDataTemplate dataTemplate) => _dataTemplate = dataTemplate;
public bool SupportsRecycling => false;
public IControl Build(object param) => GetElement(null, param);
public bool Match(object data) => _dataTemplate.Match(data);

29
src/Avalonia.Controls/Templates/FuncDataTemplate.cs

@ -6,7 +6,7 @@ namespace Avalonia.Controls.Templates
/// <summary>
/// Builds a control for a piece of data.
/// </summary>
public class FuncDataTemplate : FuncTemplate<object, IControl>, IDataTemplate
public class FuncDataTemplate : FuncTemplate<object, IControl>, IRecyclingDataTemplate
{
/// <summary>
/// The default data template used in the case where no matching data template is found.
@ -30,10 +30,8 @@ namespace Avalonia.Controls.Templates
},
true);
/// <summary>
/// The implementation of the <see cref="Match"/> method.
/// </summary>
private readonly Func<object, bool> _match;
private readonly bool _supportsRecycling;
/// <summary>
/// Initializes a new instance of the <see cref="FuncDataTemplate"/> class.
@ -70,12 +68,9 @@ namespace Avalonia.Controls.Templates
Contract.Requires<ArgumentNullException>(match != null);
_match = match;
SupportsRecycling = supportsRecycling;
_supportsRecycling = supportsRecycling;
}
/// <inheritdoc/>
public bool SupportsRecycling { get; }
/// <summary>
/// Checks to see if this data template matches the specified data.
/// </summary>
@ -88,6 +83,24 @@ namespace Avalonia.Controls.Templates
return _match(data);
}
/// <summary>
/// Creates or recycles a control to display the specified data.
/// </summary>
/// <param name="data">The data to display.</param>
/// <param name="existing">An optional control to recycle.</param>
/// <returns>
/// The <paramref name="existing"/> control if supplied and applicable to
/// <paramref name="data"/>, otherwise a new control.
/// </returns>
/// <remarks>
/// The caller should ensure that any control passed to <paramref name="existing"/>
/// originated from the same data template.
/// </remarks>
public IControl Build(object data, IControl existing)
{
return _supportsRecycling && existing is object ? existing : Build(data);
}
/// <summary>
/// Determines of an object is of the specified type.
/// </summary>

6
src/Avalonia.Controls/Templates/FuncTemplate`2.cs

@ -1,5 +1,7 @@
using System;
#nullable enable
namespace Avalonia.Controls.Templates
{
/// <summary>
@ -18,9 +20,7 @@ namespace Avalonia.Controls.Templates
/// <param name="func">The function used to create the control.</param>
public FuncTemplate(Func<TParam, INameScope, TControl> func)
{
Contract.Requires<ArgumentNullException>(func != null);
_func = func;
_func = func ?? throw new ArgumentNullException(nameof(func));
}
/// <summary>

12
src/Avalonia.Controls/Templates/IDataTemplate.cs

@ -1,3 +1,7 @@
using System;
#nullable enable
namespace Avalonia.Controls.Templates
{
/// <summary>
@ -5,12 +9,6 @@ namespace Avalonia.Controls.Templates
/// </summary>
public interface IDataTemplate : ITemplate<object, IControl>
{
/// <summary>
/// Gets a value indicating whether the data template supports recycling of the generated
/// control.
/// </summary>
bool SupportsRecycling { get; }
/// <summary>
/// Checks to see if this data template matches the specified data.
/// </summary>
@ -20,4 +18,4 @@ namespace Avalonia.Controls.Templates
/// </returns>
bool Match(object data);
}
}
}

25
src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs

@ -0,0 +1,25 @@
#nullable enable
namespace Avalonia.Controls.Templates
{
/// <summary>
/// An <see cref="IDataTemplate"/> that supports recycling existing elements.
/// </summary>
public interface IRecyclingDataTemplate : IDataTemplate
{
/// <summary>
/// Creates or recycles a control to display the specified data.
/// </summary>
/// <param name="data">The data to display.</param>
/// <param name="existing">An optional control to recycle.</param>
/// <returns>
/// The <paramref name="existing"/> control if supplied and applicable to
/// <paramref name="data"/>, otherwise a new control.
/// </returns>
/// <remarks>
/// The caller should ensure that any control passed to <paramref name="existing"/>
/// originated from the same data template.
/// </remarks>
IControl Build(object data, IControl? existing);
}
}

2
src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs

@ -7,8 +7,6 @@ namespace Avalonia.Diagnostics
{
internal class ViewLocator : IDataTemplate
{
public bool SupportsRecycling => false;
public IControl Build(object data)
{
var name = data.GetType().FullName.Replace("ViewModel", "View");

11
src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs

@ -5,7 +5,7 @@ using Avalonia.Metadata;
namespace Avalonia.Markup.Xaml.Templates
{
public class DataTemplate : IDataTemplate
public class DataTemplate : IRecyclingDataTemplate
{
public Type DataType { get; set; }
@ -14,8 +14,6 @@ namespace Avalonia.Markup.Xaml.Templates
[TemplateContent]
public object Content { get; set; }
public bool SupportsRecycling { get; set; } = true;
public bool Match(object data)
{
if (DataType == null)
@ -28,6 +26,11 @@ namespace Avalonia.Markup.Xaml.Templates
}
}
public IControl Build(object data) => TemplateContent.Load(Content).Control;
public IControl Build(object data) => Build(data, null);
public IControl Build(object data, IControl existing)
{
return existing ?? TemplateContent.Load(Content).Control;
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -18,8 +18,6 @@ namespace Avalonia.Markup.Xaml.Templates
[AssignBinding]
public Binding ItemsSource { get; set; }
public bool SupportsRecycling { get; set; } = true;
public bool Match(object data)
{
if (DataType == null)

2
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -1273,8 +1273,6 @@ namespace Avalonia.Controls.UnitTests
return new TextBlock { Text = node.Value };
}
public bool SupportsRecycling => false;
public InstancedBinding ItemsSelector(object item)
{
var obs = ExpressionObserver.Create(item, o => (o as Node).Children);

Loading…
Cancel
Save