From 22bda08a901fa2a6fd4c284ad3dfc574d00686ab Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 12:28:39 +0200 Subject: [PATCH] Lazily initialize DataTemplates. Added an `IDataTemplateHost` interface with a `IsDataTemplatesInitialized` property to prevent the need for allocating empty `DataTemplates` collections for many controls. --- src/Avalonia.Controls/Application.cs | 9 +++--- src/Avalonia.Controls/Control.cs | 9 +++--- src/Avalonia.Controls/IControl.cs | 14 ++++++---- src/Avalonia.Controls/IGlobalDataTemplates.cs | 6 +--- .../Templates/DataTemplateExtensions.cs | 24 ++++++++++------ .../Templates/IDataTemplateHost.cs | 27 ++++++++++++++++++ src/Avalonia.Diagnostics/DevTools.xaml.cs | 2 +- .../ItemsControlTests.cs | 2 +- .../ListBoxTests.cs | 8 +++--- .../ContentPresenterTests_Unrooted.cs | 2 +- .../TabControlTests.cs | 2 +- .../TreeViewTests.cs | 28 +++++++++---------- tests/Avalonia.LeakTests/ControlTests.cs | 2 +- 13 files changed, 82 insertions(+), 53 deletions(-) create mode 100644 src/Avalonia.Controls/Templates/IDataTemplateHost.cs diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 6237a859a9..18eb1f7606 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -66,11 +66,7 @@ namespace Avalonia /// /// The application's global data templates. /// - public DataTemplates DataTemplates - { - get { return _dataTemplates ?? (_dataTemplates = new DataTemplates()); } - set { _dataTemplates = value; } - } + public DataTemplates DataTemplates => _dataTemplates ?? (_dataTemplates = new DataTemplates()); /// /// Gets the application's focus manager. @@ -112,6 +108,9 @@ namespace Avalonia /// public Styles Styles => _styles ?? (_styles = new Styles()); + /// + bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; + /// /// Gets the styling parent of the application, which is null. /// diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 83c66d6349..4913037ea4 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -243,11 +243,7 @@ namespace Avalonia.Controls /// Each control may define data templates which are applied to the control itself and its /// children. /// - public DataTemplates DataTemplates - { - get { return _dataTemplates ?? (_dataTemplates = new DataTemplates()); } - set { _dataTemplates = value; } - } + public DataTemplates DataTemplates => _dataTemplates ?? (_dataTemplates = new DataTemplates()); /// /// Gets a value that indicates whether the element has finished initialization. @@ -300,6 +296,9 @@ namespace Avalonia.Controls internal set { SetValue(TemplatedParentProperty, value); } } + /// + bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; + /// /// Gets a value indicating whether the element is attached to a rooted logical tree. /// diff --git a/src/Avalonia.Controls/IControl.cs b/src/Avalonia.Controls/IControl.cs index 3f5bd3fcac..41436c6d87 100644 --- a/src/Avalonia.Controls/IControl.cs +++ b/src/Avalonia.Controls/IControl.cs @@ -14,7 +14,14 @@ namespace Avalonia.Controls /// /// Interface for Avalonia controls. /// - public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IStyleable, IStyleHost + public interface IControl : IVisual, + IDataTemplateHost, + ILogical, + ILayoutable, + IInputElement, + INamed, + IStyleable, + IStyleHost { /// /// Occurs when the control has finished initialization. @@ -31,11 +38,6 @@ namespace Avalonia.Controls /// object DataContext { get; set; } - /// - /// Gets the data templates for the control. - /// - DataTemplates DataTemplates { get; } - /// /// Gets a value that indicates whether the element has finished initialization. /// diff --git a/src/Avalonia.Controls/IGlobalDataTemplates.cs b/src/Avalonia.Controls/IGlobalDataTemplates.cs index a20c7379a3..248615de0d 100644 --- a/src/Avalonia.Controls/IGlobalDataTemplates.cs +++ b/src/Avalonia.Controls/IGlobalDataTemplates.cs @@ -8,11 +8,7 @@ namespace Avalonia.Controls /// /// Defines the application-global data templates. /// - public interface IGlobalDataTemplates + public interface IGlobalDataTemplates : IDataTemplateHost { - /// - /// Gets the application-global data templates. - /// - DataTemplates DataTemplates { get; } } } diff --git a/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs b/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs index df25733524..559cb9b776 100644 --- a/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs +++ b/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs @@ -17,8 +17,8 @@ namespace Avalonia.Controls.Templates /// The control searching for the data template. /// The data. /// - /// An optional primary template that can will be tried before the - /// in the tree are searched. + /// An optional primary template that can will be tried before the DataTemplates in the + /// tree are searched. /// /// The data template or null if no matching data template was found. public static IDataTemplate FindDataTemplate( @@ -31,13 +31,16 @@ namespace Avalonia.Controls.Templates return primary; } - foreach (var i in control.GetSelfAndLogicalAncestors().OfType()) + foreach (var i in control.GetSelfAndLogicalAncestors().OfType()) { - foreach (IDataTemplate dt in i.DataTemplates) + if (i.IsDataTemplatesInitialized) { - if (dt.Match(data)) + foreach (IDataTemplate dt in i.DataTemplates) { - return dt; + if (dt.Match(data)) + { + return dt; + } } } } @@ -46,11 +49,14 @@ namespace Avalonia.Controls.Templates if (global != null) { - foreach (IDataTemplate dt in global.DataTemplates) + if (global.IsDataTemplatesInitialized) { - if (dt.Match(data)) + foreach (IDataTemplate dt in global.DataTemplates) { - return dt; + if (dt.Match(data)) + { + return dt; + } } } } diff --git a/src/Avalonia.Controls/Templates/IDataTemplateHost.cs b/src/Avalonia.Controls/Templates/IDataTemplateHost.cs new file mode 100644 index 0000000000..5cc12581d4 --- /dev/null +++ b/src/Avalonia.Controls/Templates/IDataTemplateHost.cs @@ -0,0 +1,27 @@ +// 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. + +using System; + +namespace Avalonia.Controls.Templates +{ + /// + /// Defines an element that has a collection. + /// + public interface IDataTemplateHost + { + /// + /// Gets the data templates for the element. + /// + DataTemplates DataTemplates { get; } + + /// + /// Gets a value indicating whether is initialized. + /// + /// + /// The property may be lazily initialized, if so this property + /// indicates whether it has been initialized. + /// + bool IsDataTemplatesInitialized { get; } + } +} diff --git a/src/Avalonia.Diagnostics/DevTools.xaml.cs b/src/Avalonia.Diagnostics/DevTools.xaml.cs index 6593a8cd42..be0863954a 100644 --- a/src/Avalonia.Diagnostics/DevTools.xaml.cs +++ b/src/Avalonia.Diagnostics/DevTools.xaml.cs @@ -71,7 +71,7 @@ namespace Avalonia.Diagnostics Width = 1024, Height = 512, Content = devTools, - DataTemplates = new DataTemplates + DataTemplates = { new ViewLocator(), } diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index e88d1881e6..f01eecf647 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -388,7 +388,7 @@ namespace Avalonia.Controls.UnitTests { Template = GetTemplate(), DataContext = "Base", - DataTemplates = new DataTemplates + DataTemplates = { new FuncDataTemplate(x => new Button { Content = x }) }, diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index e58542bfb4..a5f5f8d328 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -109,10 +109,10 @@ namespace Avalonia.Controls.UnitTests { Template = ListBoxTemplate(), DataContext = "Base", - DataTemplates = new DataTemplates - { - new FuncDataTemplate(x => new Button { Content = x }) - }, + DataTemplates = + { + new FuncDataTemplate(x => new Button { Content = x }) + }, Items = items, }; diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs index 3585109dee..5268c9ac0d 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs @@ -88,7 +88,7 @@ namespace Avalonia.Controls.UnitTests.Presenters root.Child = null; root = new TestRoot { - DataTemplates = new DataTemplates + DataTemplates = { new FuncDataTemplate(x => new Decorator()), }, diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index d11c989261..638d773cd0 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -174,7 +174,7 @@ namespace Avalonia.Controls.UnitTests { Template = new FuncControlTemplate(CreateTabControlTemplate), DataContext = "Base", - DataTemplates = new DataTemplates + DataTemplates = { new FuncDataTemplate(x => new Button { Content = x }) }, diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 44ef7192ff..29eac3d8f5 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -25,9 +25,9 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), Items = CreateTestTreeData(), - DataTemplates = CreateNodeDataTemplate(), }; + CreateNodeDataTemplate(target); ApplyTemplates(target); Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0)); @@ -69,9 +69,9 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), Items = CreateTestTreeData(), - DataTemplates = CreateNodeDataTemplate(), }; + CreateNodeDataTemplate(target); ApplyTemplates(target); var container = (TreeViewItem)target.ItemContainerGenerator.Containers.Single().ContainerControl; @@ -87,7 +87,6 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), Items = tree, - DataTemplates = CreateNodeDataTemplate(), }; // For TreeViewItem to find its parent TreeView, OnAttachedToLogicalTree needs @@ -95,6 +94,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(); root.Child = target; + CreateNodeDataTemplate(target); ApplyTemplates(target); var container = target.ItemContainerGenerator.Index.ContainerFromItem( @@ -116,11 +116,12 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), Items = tree, - DataTemplates = CreateNodeDataTemplate(), }; var visualRoot = new TestRoot(); visualRoot.Child = target; + + CreateNodeDataTemplate(target); ApplyTemplates(target); var item = tree[0].Children[1].Children[0]; @@ -146,11 +147,12 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), Items = tree, - DataTemplates = CreateNodeDataTemplate(), }; var visualRoot = new TestRoot(); visualRoot.Child = target; + + CreateNodeDataTemplate(target); ApplyTemplates(target); var item = tree[0].Children[1].Children[0]; @@ -191,12 +193,13 @@ namespace Avalonia.Controls.UnitTests var target = new TreeView { Template = CreateTreeViewTemplate(), - DataTemplates = CreateNodeDataTemplate(), Items = tree, }; var root = new TestRoot(); root.Child = target; + + CreateNodeDataTemplate(target); ApplyTemplates(target); Assert.Equal(5, target.ItemContainerGenerator.Index.Items.Count()); @@ -221,7 +224,7 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), DataContext = "Base", - DataTemplates = new DataTemplates + DataTemplates = { new FuncDataTemplate(x => new Button { Content = x }) }, @@ -291,9 +294,9 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), Items = data, - DataTemplates = CreateNodeDataTemplate(), }; + CreateNodeDataTemplate(target); ApplyTemplates(target); Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0)); @@ -328,7 +331,6 @@ namespace Avalonia.Controls.UnitTests { Template = CreateTreeViewTemplate(), Items = data, - DataTemplates = CreateNodeDataTemplate(), }; var button = new Button(); @@ -341,6 +343,7 @@ namespace Avalonia.Controls.UnitTests } }; + CreateNodeDataTemplate(target); ApplyTemplates(target); var item = data[0].Children[0]; @@ -411,12 +414,9 @@ namespace Avalonia.Controls.UnitTests }; } - private DataTemplates CreateNodeDataTemplate() + private void CreateNodeDataTemplate(IControl control) { - return new DataTemplates - { - new TestTreeDataTemplate() - }; + control.DataTemplates.Add(new TestTreeDataTemplate()); } private IControlTemplate CreateTreeViewTemplate() diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index e0b0d4a4c0..12ac8d3af6 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -276,7 +276,7 @@ namespace Avalonia.LeakTests { Content = target = new TreeView { - DataTemplates = new DataTemplates + DataTemplates = { new FuncTreeDataTemplate( x => new TextBlock { Text = x.Name },