diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index dd047db279..b0db89f7ea 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -26,7 +26,7 @@ namespace Avalonia /// /// The parent object that inherited values are inherited from. /// - private AvaloniaObject _inheritanceParent; + private IAvaloniaObject _inheritanceParent; /// /// The set values/bindings on this object. @@ -120,7 +120,7 @@ namespace Avalonia /// /// The inheritance parent. /// - protected AvaloniaObject InheritanceParent + protected IAvaloniaObject InheritanceParent { get { diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 80d29b14ab..22d17bf633 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -58,6 +58,7 @@ + diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index e246635906..0bede31fb4 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -33,7 +33,7 @@ namespace Avalonia.Controls /// - Implements to allow styling to work on the control. /// - Implements to form part of a logical tree. /// - public class Control : InputElement, IControl, INamed, ISetLogicalParent, ISupportInitialize + public class Control : InputElement, IControl, INamed, ISetInheritanceParent, ISetLogicalParent, ISupportInitialize { /// /// Defines the property. @@ -455,6 +455,15 @@ namespace Avalonia.Controls } } + /// + /// Sets the control's inheritance parent. + /// + /// The parent. + void ISetInheritanceParent.SetParent(IAvaloniaObject parent) + { + InheritanceParent = parent; + } + /// /// Adds a pseudo-class to be set when a property is true. /// diff --git a/src/Avalonia.Controls/ISetInheritanceParent.cs b/src/Avalonia.Controls/ISetInheritanceParent.cs new file mode 100644 index 0000000000..788ab77246 --- /dev/null +++ b/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 +{ + /// + /// Defines an interface through which a 's inheritance parent can be set. + /// + /// + /// You should not usually need to use this interface - it is for advanced scenarios only. + /// Additionally, also sets the inheritance parent; this + /// interface is only needed where the logical and inheritance parents differ. + /// + public interface ISetInheritanceParent + { + /// + /// Sets the control's inheritance parent. + /// + /// The parent. + void SetParent(IAvaloniaObject parent); + } +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 3470a43c1c..87435319c7 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -80,6 +80,7 @@ namespace Avalonia.Controls.Presenters private IControl _child; private bool _createdChild; + private IDataTemplate _dataTemplate; /// /// Initializes static members of the class. @@ -200,6 +201,13 @@ namespace Avalonia.Controls.Presenters } } + /// + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + _dataTemplate = null; + } + /// /// Updates the control based on the control's . /// @@ -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; diff --git a/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs b/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs index 9eff4243b1..df25733524 100644 --- a/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs +++ b/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs @@ -11,54 +11,6 @@ namespace Avalonia.Controls.Templates /// public static class DataTemplateExtensions { - /// - /// Materializes a piece of data based on a data template. - /// - /// The control materializing the data template. - /// The data. - /// - /// An optional primary template that can will be tried before the - /// in the tree are searched. - /// - /// The data materialized as a control. - 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; - } - } - } - /// /// Find a data template that matches a piece of data. /// diff --git a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs index 8ce3f69f36..bdf0732492 100644 --- a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs +++ b/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, IDataTemplate { /// - /// 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. /// public static readonly FuncDataTemplate Default = - new FuncDataTemplate(typeof(object), o => (o != null) ? new TextBlock { Text = o.ToString() } : null); + new FuncDataTemplate( + 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; + } + }); /// /// The implementation of the method. diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs index 2886b63c55..d90dbfda7f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs +++ b/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(); } } \ No newline at end of file diff --git a/tests/Avalonia.Controls.UnitTests/ControlTests.cs b/tests/Avalonia.Controls.UnitTests/ControlTests.cs index 4871f84e82..038641b8b3 100644 --- a/tests/Avalonia.Controls.UnitTests/ControlTests.cs +++ b/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; } } } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests.cs index d3d9101e07..198c19f60f 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests.cs +++ b/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(_ => new Border()), + }, + Content = "foo", + }; + + target.UpdateChild(); + var control = target.Child; + + Assert.IsType(control); + + target.Content = "bar"; + target.UpdateChild(); + + Assert.Same(control, target.Child); + } + private class TestContentControl : ContentControl { public IControl Child { get; set; }