From 090a1ec8cfd192da9fbb6f4f64e5979cb4f24a3b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 11:54:23 +0200 Subject: [PATCH 1/3] Lazily initialize Styles. Added an `IStyleHost.IsStylesInitialized` property to prevent the need for allocating empty `Styles` collections for many controls. --- src/Avalonia.Controls/Application.cs | 6 +++++- src/Avalonia.Controls/Control.cs | 13 ++++++------ .../Views/ControlDetailsView.cs | 2 +- src/Avalonia.Styling/Styling/IStyleHost.cs | 12 ++++++++++- .../Styling/StyleExtensions.cs | 11 ++++++---- src/Avalonia.Styling/Styling/Styler.cs | 5 ++++- .../Primitives/PopupTests.cs | 1 + .../Primitives/TemplatedControlTests.cs | 8 ++++---- .../TabControlTests.cs | 2 +- .../UserControlTests.cs | 2 +- .../FullLayoutTests.cs | 1 + .../ResourceTests.cs | 20 +++++++++---------- 12 files changed, 52 insertions(+), 31 deletions(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 3d13608226..6237a859a9 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -39,6 +39,7 @@ namespace Avalonia private readonly Lazy _clipboard = new Lazy(() => (IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))); private readonly Styler _styler = new Styler(); + private Styles _styles; /// /// Initializes a new instance of the class. @@ -109,13 +110,16 @@ namespace Avalonia /// /// Global styles apply to all windows in the application. /// - public Styles Styles { get; } = new Styles(); + public Styles Styles => _styles ?? (_styles = new Styles()); /// /// Gets the styling parent of the application, which is null. /// IStyleHost IStyleHost.StylingParent => null; + /// + bool IStyleHost.IsStylesInitialized => _styles != null; + /// /// Initializes the application by loading XAML etc. /// diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index eca5967a58..83c66d6349 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -97,8 +97,8 @@ namespace Avalonia.Controls private bool _isAttachedToLogicalTree; private IAvaloniaList _logicalChildren; private INameScope _nameScope; - private Styles _styles; private bool _styled; + private Styles _styles; private Subject _styleDetach = new Subject(); /// @@ -259,18 +259,14 @@ namespace Avalonia.Controls public bool IsInitialized { get; private set; } /// - /// Gets or sets the styles for the control. + /// Gets the styles for the control. /// /// /// Styles for the entire application are added to the Application.Styles collection, but /// each control may in addition define its own styles which are applied to the control /// itself and its children. /// - public Styles Styles - { - get { return _styles ?? (_styles = new Styles()); } - set { _styles = value; } - } + public Styles Styles => _styles ?? (_styles = new Styles()); /// /// Gets the control's logical parent. @@ -336,6 +332,9 @@ namespace Avalonia.Controls /// IObservable IStyleable.StyleDetach => _styleDetach; + /// + bool IStyleHost.IsStylesInitialized => _styles != null; + /// IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent; diff --git a/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs b/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs index d7bd6fd128..e58818d31d 100644 --- a/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs +++ b/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs @@ -42,7 +42,7 @@ namespace Avalonia.Diagnostics.Views { Content = _grid = new SimpleGrid { - Styles = new Styles + Styles = { new Style(x => x.Is()) { diff --git a/src/Avalonia.Styling/Styling/IStyleHost.cs b/src/Avalonia.Styling/Styling/IStyleHost.cs index 8422f18b46..b225419c2d 100644 --- a/src/Avalonia.Styling/Styling/IStyleHost.cs +++ b/src/Avalonia.Styling/Styling/IStyleHost.cs @@ -1,6 +1,8 @@ // 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.Styling { /// @@ -8,6 +10,15 @@ namespace Avalonia.Styling /// public interface IStyleHost { + /// + /// Gets a value indicating whether is initialized. + /// + /// + /// The property may be lazily initialized, if so this property + /// indicates whether it has been initialized. + /// + bool IsStylesInitialized { get; } + /// /// Gets the styles for the element. /// @@ -17,6 +28,5 @@ namespace Avalonia.Styling /// Gets the parent style host element. /// IStyleHost StylingParent { get; } - } } diff --git a/src/Avalonia.Styling/Styling/StyleExtensions.cs b/src/Avalonia.Styling/Styling/StyleExtensions.cs index e1073335a0..d53d00aed3 100644 --- a/src/Avalonia.Styling/Styling/StyleExtensions.cs +++ b/src/Avalonia.Styling/Styling/StyleExtensions.cs @@ -23,11 +23,14 @@ namespace Avalonia.Styling while (control != null) { - var result = control.Styles.FindResource(name); - - if (result != AvaloniaProperty.UnsetValue) + if (control.IsStylesInitialized) { - return result; + var result = control.Styles.FindResource(name); + + if (result != AvaloniaProperty.UnsetValue) + { + return result; + } } control = control.StylingParent; diff --git a/src/Avalonia.Styling/Styling/Styler.cs b/src/Avalonia.Styling/Styling/Styler.cs index 880e435b58..7ac5c89005 100644 --- a/src/Avalonia.Styling/Styling/Styler.cs +++ b/src/Avalonia.Styling/Styling/Styler.cs @@ -29,7 +29,10 @@ namespace Avalonia.Styling ApplyStyles(control, parentContainer); } - styleHost.Styles.Attach(control, styleHost); + if (styleHost.IsStylesInitialized) + { + styleHost.Styles.Attach(control, styleHost); + } } } } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index f192e87f08..ccbf04533d 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -265,6 +265,7 @@ namespace Avalonia.Controls.UnitTests.Primitives }; var globalStyles = new Mock(); + globalStyles.Setup(x => x.IsStylesInitialized).Returns(true); globalStyles.Setup(x => x.Styles).Returns(styles); var renderInterface = new Mock(); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs index 72c8073f21..be3c34ac2e 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs @@ -399,7 +399,7 @@ namespace Avalonia.Controls.UnitTests.Primitives TestTemplatedControl target; var root = new TestRoot { - Styles = new Styles + Styles = { new Style(x => x.OfType()) { @@ -435,7 +435,7 @@ namespace Avalonia.Controls.UnitTests.Primitives TestTemplatedControl target; var root = new TestRoot { - Styles = new Styles + Styles = { new Style(x => x.OfType()) { @@ -474,7 +474,7 @@ namespace Avalonia.Controls.UnitTests.Primitives var root = new TestRoot { - Styles = new Styles + Styles = { new Style(x => x.OfType()) { @@ -494,7 +494,7 @@ namespace Avalonia.Controls.UnitTests.Primitives var root2 = new TestRoot { - Styles = new Styles + Styles = { new Style(x => x.OfType()) { diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index 2bbd08bf42..d11c989261 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -135,7 +135,7 @@ namespace Avalonia.Controls.UnitTests { var root = new TestRoot { - Styles = new Styles + Styles = { new Style(x => x.OfType()) { diff --git a/tests/Avalonia.Controls.UnitTests/UserControlTests.cs b/tests/Avalonia.Controls.UnitTests/UserControlTests.cs index c74ffab223..738c54594e 100644 --- a/tests/Avalonia.Controls.UnitTests/UserControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/UserControlTests.cs @@ -21,7 +21,7 @@ namespace Avalonia.Controls.UnitTests var target = new UserControl(); var root = new TestRoot { - Styles = new Styles + Styles = { new Style(x => x.OfType()) { diff --git a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs index 6b7c73da2a..cdfb253bf4 100644 --- a/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs +++ b/tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs @@ -193,6 +193,7 @@ namespace Avalonia.Layout.UnitTests .Bind().ToConstant(new Avalonia.Controls.UnitTests.WindowingPlatformMock(() => windowImpl.Object)); var theme = new DefaultTheme(); + globalStyles.Setup(x => x.IsStylesInitialized).Returns(true); globalStyles.Setup(x => x.Styles).Returns(theme); } } diff --git a/tests/Avalonia.Styling.UnitTests/ResourceTests.cs b/tests/Avalonia.Styling.UnitTests/ResourceTests.cs index a2535e0fb5..1f9b925eb1 100644 --- a/tests/Avalonia.Styling.UnitTests/ResourceTests.cs +++ b/tests/Avalonia.Styling.UnitTests/ResourceTests.cs @@ -16,7 +16,7 @@ namespace Avalonia.Styling.UnitTests var tree = new Decorator { - Styles = new Styles + Styles = { new Style { @@ -29,7 +29,7 @@ namespace Avalonia.Styling.UnitTests }, Child = target = new Border { - Styles = new Styles + Styles = { new Style { @@ -60,16 +60,16 @@ namespace Avalonia.Styling.UnitTests var tree = target = new Border { - Styles = new Styles + Styles = + { + new Style { - new Style + Resources = new StyleResources { - Resources = new StyleResources - { - { "Foo", "foo" }, - } - }, - } + { "Foo", "foo" }, + } + }, + } }; Assert.Equal(AvaloniaProperty.UnsetValue, target.FindStyleResource("Baz")); From 22bda08a901fa2a6fd4c284ad3dfc574d00686ab Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 12:28:39 +0200 Subject: [PATCH 2/3] 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 }, From 4bba1ab0fbaffc8abea9f96632e09b685d333109 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Aug 2017 13:14:46 +0200 Subject: [PATCH 3/3] Lazy initialize Interactive._eventHandlers. --- src/Avalonia.Interactivity/Interactive.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index e38b348179..78df6f6151 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -16,14 +16,18 @@ namespace Avalonia.Interactivity /// public class Interactive : Layoutable, IInteractive { - private readonly Dictionary> _eventHandlers = - new Dictionary>(); + private Dictionary> _eventHandlers; /// /// Gets the interactive parent of the object for bubbling and tunnelling events. /// IInteractive IInteractive.InteractiveParent => ((IVisual)this).VisualParent as IInteractive; + private Dictionary> EventHandlers + { + get { return _eventHandlers ?? (_eventHandlers = new Dictionary>()); } + } + /// /// Adds a handler for the specified routed event. /// @@ -43,10 +47,10 @@ namespace Avalonia.Interactivity List subscriptions; - if (!_eventHandlers.TryGetValue(routedEvent, out subscriptions)) + if (!EventHandlers.TryGetValue(routedEvent, out subscriptions)) { subscriptions = new List(); - _eventHandlers.Add(routedEvent, subscriptions); + EventHandlers.Add(routedEvent, subscriptions); } var sub = new EventSubscription @@ -89,9 +93,9 @@ namespace Avalonia.Interactivity Contract.Requires(routedEvent != null); Contract.Requires(handler != null); - List subscriptions; + List subscriptions = null; - if (_eventHandlers.TryGetValue(routedEvent, out subscriptions)) + if (_eventHandlers?.TryGetValue(routedEvent, out subscriptions) == true) { subscriptions.RemoveAll(x => x.Handler == handler); } @@ -181,9 +185,9 @@ namespace Avalonia.Interactivity e.RoutedEvent.InvokeRaised(this, e); - List subscriptions; + List subscriptions = null; - if (_eventHandlers.TryGetValue(e.RoutedEvent, out subscriptions)) + if (_eventHandlers?.TryGetValue(e.RoutedEvent, out subscriptions) == true) { foreach (var sub in subscriptions.ToList()) {