Browse Source

Recycle DataTemplates.

If a `ContentPresenter`s content is assigned a value which matches the
current `DataTemplate` then just change the
`ContentPresenter.DataContext` and the existing item will update itself.
pull/558/head
Steven Kirk 10 years ago
parent
commit
35c4835ae4
  1. 4
      src/Avalonia.Base/AvaloniaObject.cs
  2. 1
      src/Avalonia.Controls/Avalonia.Controls.csproj
  3. 11
      src/Avalonia.Controls/Control.cs
  4. 22
      src/Avalonia.Controls/ISetInheritanceParent.cs
  5. 70
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  6. 48
      src/Avalonia.Controls/Templates/DataTemplateExtensions.cs
  7. 20
      src/Avalonia.Controls/Templates/FuncDataTemplate.cs
  8. 7
      src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
  9. 2
      tests/Avalonia.Controls.UnitTests/ControlTests.cs
  10. 24
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests.cs

4
src/Avalonia.Base/AvaloniaObject.cs

@ -26,7 +26,7 @@ namespace Avalonia
/// <summary>
/// The parent object that inherited values are inherited from.
/// </summary>
private AvaloniaObject _inheritanceParent;
private IAvaloniaObject _inheritanceParent;
/// <summary>
/// The set values/bindings on this object.
@ -120,7 +120,7 @@ namespace Avalonia
/// <value>
/// The inheritance parent.
/// </value>
protected AvaloniaObject InheritanceParent
protected IAvaloniaObject InheritanceParent
{
get
{

1
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -58,6 +58,7 @@
<Compile Include="INameScope.cs" />
<Compile Include="IPseudoClasses.cs" />
<Compile Include="DropDownItem.cs" />
<Compile Include="ISetInheritanceParent.cs" />
<Compile Include="ItemVirtualizationMode.cs" />
<Compile Include="IVirtualizingPanel.cs" />
<Compile Include="LayoutTransformControl.cs" />

11
src/Avalonia.Controls/Control.cs

@ -33,7 +33,7 @@ namespace Avalonia.Controls
/// - Implements <see cref="IStyleable"/> to allow styling to work on the control.
/// - Implements <see cref="ILogical"/> to form part of a logical tree.
/// </remarks>
public class Control : InputElement, IControl, INamed, ISetLogicalParent, ISupportInitialize
public class Control : InputElement, IControl, INamed, ISetInheritanceParent, ISetLogicalParent, ISupportInitialize
{
/// <summary>
/// Defines the <see cref="DataContext"/> property.
@ -455,6 +455,15 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Sets the control's inheritance parent.
/// </summary>
/// <param name="parent">The parent.</param>
void ISetInheritanceParent.SetParent(IAvaloniaObject parent)
{
InheritanceParent = parent;
}
/// <summary>
/// Adds a pseudo-class to be set when a property is true.
/// </summary>

22
src/Avalonia.Controls/ISetInheritanceParent.cs

@ -0,0 +1,22 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Controls
{
/// <summary>
/// Defines an interface through which a <see cref="Control"/>'s inheritance parent can be set.
/// </summary>
/// <remarks>
/// You should not usually need to use this interface - it is for advanced scenarios only.
/// Additionally, <see cref="ISetLogicalParent"/> also sets the inheritance parent; this
/// interface is only needed where the logical and inheritance parents differ.
/// </remarks>
public interface ISetInheritanceParent
{
/// <summary>
/// Sets the control's inheritance parent.
/// </summary>
/// <param name="parent">The parent.</param>
void SetParent(IAvaloniaObject parent);
}
}

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

@ -80,6 +80,7 @@ namespace Avalonia.Controls.Presenters
private IControl _child;
private bool _createdChild;
private IDataTemplate _dataTemplate;
/// <summary>
/// Initializes static members of the <see cref="ContentPresenter"/> class.
@ -200,6 +201,13 @@ namespace Avalonia.Controls.Presenters
}
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
_dataTemplate = null;
}
/// <summary>
/// Updates the <see cref="Child"/> control based on the control's <see cref="Content"/>.
/// </summary>
@ -215,32 +223,64 @@ namespace Avalonia.Controls.Presenters
{
var old = Child;
var content = Content;
var result = this.MaterializeDataTemplate(content, ContentTemplate);
var result = content as IControl;
if (old != null)
if (result == null)
{
VisualChildren.Remove(old);
DataContext = content;
if (content != null)
{
if (old != null && _dataTemplate?.Match(content) == true)
{
result = old;
}
else
{
_dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
result = _dataTemplate.Build(content);
var controlResult = result as Control;
if (controlResult != null)
{
NameScope.SetNameScope(controlResult, new NameScope());
}
}
}
else
{
_dataTemplate = null;
}
}
else
{
_dataTemplate = null;
}
if (result != null)
if (result != old)
{
if (!(content is IControl))
if (old != null)
{
result.DataContext = content;
VisualChildren.Remove(old);
}
Child = result;
if (result != null)
{
Child = result;
if (result.Parent == null)
if (result.Parent == null)
{
((ISetLogicalParent)result).SetParent((ILogical)this.TemplatedParent ?? this);
}
((ISetInheritanceParent)result).SetParent(this);
VisualChildren.Add(result);
}
else
{
((ISetLogicalParent)result).SetParent((ILogical)this.TemplatedParent ?? this);
Child = null;
}
VisualChildren.Add(result);
}
else
{
Child = null;
}
_createdChild = true;

48
src/Avalonia.Controls/Templates/DataTemplateExtensions.cs

@ -11,54 +11,6 @@ namespace Avalonia.Controls.Templates
/// </summary>
public static class DataTemplateExtensions
{
/// <summary>
/// Materializes a piece of data based on a data template.
/// </summary>
/// <param name="control">The control materializing the data template.</param>
/// <param name="data">The data.</param>
/// <param name="primary">
/// An optional primary template that can will be tried before the
/// <see cref="IControl.DataTemplates"/> in the tree are searched.
/// </param>
/// <returns>The data materialized as a control.</returns>
public static IControl MaterializeDataTemplate(
this IControl control,
object data,
IDataTemplate primary = null)
{
if (data == null)
{
return null;
}
else
{
var asControl = data as IControl;
if (asControl != null)
{
return asControl;
}
else
{
IDataTemplate template = control.FindDataTemplate(data, primary);
IControl result;
if (template != null)
{
result = template.Build(data);
}
else
{
result = FuncDataTemplate.Default.Build(data);
}
NameScope.SetNameScope((Control)result, new NameScope());
return result;
}
}
}
/// <summary>
/// Find a data template that matches a piece of data.
/// </summary>

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

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Linq;
using System.Reflection;
namespace Avalonia.Controls.Templates
@ -12,10 +13,25 @@ namespace Avalonia.Controls.Templates
public class FuncDataTemplate : FuncTemplate<object, IControl>, IDataTemplate
{
/// <summary>
/// The default data template used in the case where not matching data template is found.
/// The default data template used in the case where no matching data template is found.
/// </summary>
public static readonly FuncDataTemplate Default =
new FuncDataTemplate(typeof(object), o => (o != null) ? new TextBlock { Text = o.ToString() } : null);
new FuncDataTemplate<object>(
data =>
{
if (data != null)
{
var result = new TextBlock();
result.Bind(
TextBlock.TextProperty,
result.GetObservable(Control.DataContextProperty).Select(x => x?.ToString()));
return result;
}
else
{
return null;
}
});
/// <summary>
/// The implementation of the <see cref="Match"/> method.

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

@ -28,11 +28,6 @@ namespace Avalonia.Markup.Xaml.Templates
}
}
public IControl Build(object data)
{
var visualTreeForItem = Content.Load();
visualTreeForItem.DataContext = data;
return visualTreeForItem;
}
public IControl Build(object data) => Content.Load();
}
}

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

@ -262,7 +262,7 @@ namespace Avalonia.Controls.UnitTests
private class TestControl : Control
{
public new AvaloniaObject InheritanceParent => base.InheritanceParent;
public new IAvaloniaObject InheritanceParent => base.InheritanceParent;
}
}
}

24
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests.cs

@ -9,6 +9,7 @@ using Avalonia.Controls.Templates;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
using System;
namespace Avalonia.Controls.UnitTests.Presenters
{
@ -152,6 +153,29 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal("foo", target.DataContext);
}
[Fact]
public void Tries_To_Recycle_DataTemplate()
{
var target = new ContentPresenter
{
DataTemplates = new DataTemplates
{
new FuncDataTemplate<string>(_ => new Border()),
},
Content = "foo",
};
target.UpdateChild();
var control = target.Child;
Assert.IsType<Border>(control);
target.Content = "bar";
target.UpdateChild();
Assert.Same(control, target.Child);
}
private class TestContentControl : ContentControl
{
public IControl Child { get; set; }

Loading…
Cancel
Save