Browse Source

Lazily initialize DataTemplates.

Added an `IDataTemplateHost` interface with a `IsDataTemplatesInitialized` property to prevent the need for allocating empty `DataTemplates` collections for many controls.
pull/1135/head
Steven Kirk 9 years ago
parent
commit
22bda08a90
  1. 9
      src/Avalonia.Controls/Application.cs
  2. 9
      src/Avalonia.Controls/Control.cs
  3. 14
      src/Avalonia.Controls/IControl.cs
  4. 6
      src/Avalonia.Controls/IGlobalDataTemplates.cs
  5. 24
      src/Avalonia.Controls/Templates/DataTemplateExtensions.cs
  6. 27
      src/Avalonia.Controls/Templates/IDataTemplateHost.cs
  7. 2
      src/Avalonia.Diagnostics/DevTools.xaml.cs
  8. 2
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  9. 8
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  10. 2
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs
  11. 2
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  12. 28
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  13. 2
      tests/Avalonia.LeakTests/ControlTests.cs

9
src/Avalonia.Controls/Application.cs

@ -66,11 +66,7 @@ namespace Avalonia
/// <value> /// <value>
/// The application's global data templates. /// The application's global data templates.
/// </value> /// </value>
public DataTemplates DataTemplates public DataTemplates DataTemplates => _dataTemplates ?? (_dataTemplates = new DataTemplates());
{
get { return _dataTemplates ?? (_dataTemplates = new DataTemplates()); }
set { _dataTemplates = value; }
}
/// <summary> /// <summary>
/// Gets the application's focus manager. /// Gets the application's focus manager.
@ -112,6 +108,9 @@ namespace Avalonia
/// </remarks> /// </remarks>
public Styles Styles => _styles ?? (_styles = new Styles()); public Styles Styles => _styles ?? (_styles = new Styles());
/// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
/// <summary> /// <summary>
/// Gets the styling parent of the application, which is null. /// Gets the styling parent of the application, which is null.
/// </summary> /// </summary>

9
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 /// Each control may define data templates which are applied to the control itself and its
/// children. /// children.
/// </remarks> /// </remarks>
public DataTemplates DataTemplates public DataTemplates DataTemplates => _dataTemplates ?? (_dataTemplates = new DataTemplates());
{
get { return _dataTemplates ?? (_dataTemplates = new DataTemplates()); }
set { _dataTemplates = value; }
}
/// <summary> /// <summary>
/// Gets a value that indicates whether the element has finished initialization. /// Gets a value that indicates whether the element has finished initialization.
@ -300,6 +296,9 @@ namespace Avalonia.Controls
internal set { SetValue(TemplatedParentProperty, value); } internal set { SetValue(TemplatedParentProperty, value); }
} }
/// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
/// <summary> /// <summary>
/// Gets a value indicating whether the element is attached to a rooted logical tree. /// Gets a value indicating whether the element is attached to a rooted logical tree.
/// </summary> /// </summary>

14
src/Avalonia.Controls/IControl.cs

@ -14,7 +14,14 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Interface for Avalonia controls. /// Interface for Avalonia controls.
/// </summary> /// </summary>
public interface IControl : IVisual, ILogical, ILayoutable, IInputElement, INamed, IStyleable, IStyleHost public interface IControl : IVisual,
IDataTemplateHost,
ILogical,
ILayoutable,
IInputElement,
INamed,
IStyleable,
IStyleHost
{ {
/// <summary> /// <summary>
/// Occurs when the control has finished initialization. /// Occurs when the control has finished initialization.
@ -31,11 +38,6 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
object DataContext { get; set; } object DataContext { get; set; }
/// <summary>
/// Gets the data templates for the control.
/// </summary>
DataTemplates DataTemplates { get; }
/// <summary> /// <summary>
/// Gets a value that indicates whether the element has finished initialization. /// Gets a value that indicates whether the element has finished initialization.
/// </summary> /// </summary>

6
src/Avalonia.Controls/IGlobalDataTemplates.cs

@ -8,11 +8,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the application-global data templates. /// Defines the application-global data templates.
/// </summary> /// </summary>
public interface IGlobalDataTemplates public interface IGlobalDataTemplates : IDataTemplateHost
{ {
/// <summary>
/// Gets the application-global data templates.
/// </summary>
DataTemplates DataTemplates { get; }
} }
} }

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

@ -17,8 +17,8 @@ namespace Avalonia.Controls.Templates
/// <param name="control">The control searching for the data template.</param> /// <param name="control">The control searching for the data template.</param>
/// <param name="data">The data.</param> /// <param name="data">The data.</param>
/// <param name="primary"> /// <param name="primary">
/// An optional primary template that can will be tried before the /// An optional primary template that can will be tried before the DataTemplates in the
/// <see cref="IControl.DataTemplates"/> in the tree are searched. /// tree are searched.
/// </param> /// </param>
/// <returns>The data template or null if no matching data template was found.</returns> /// <returns>The data template or null if no matching data template was found.</returns>
public static IDataTemplate FindDataTemplate( public static IDataTemplate FindDataTemplate(
@ -31,13 +31,16 @@ namespace Avalonia.Controls.Templates
return primary; return primary;
} }
foreach (var i in control.GetSelfAndLogicalAncestors().OfType<IControl>()) foreach (var i in control.GetSelfAndLogicalAncestors().OfType<IDataTemplateHost>())
{ {
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) 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;
}
} }
} }
} }

27
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
{
/// <summary>
/// Defines an element that has a <see cref="DataTemplates"/> collection.
/// </summary>
public interface IDataTemplateHost
{
/// <summary>
/// Gets the data templates for the element.
/// </summary>
DataTemplates DataTemplates { get; }
/// <summary>
/// Gets a value indicating whether <see cref="DataTemplates"/> is initialized.
/// </summary>
/// <remarks>
/// The <see cref="DataTemplates"/> property may be lazily initialized, if so this property
/// indicates whether it has been initialized.
/// </remarks>
bool IsDataTemplatesInitialized { get; }
}
}

2
src/Avalonia.Diagnostics/DevTools.xaml.cs

@ -71,7 +71,7 @@ namespace Avalonia.Diagnostics
Width = 1024, Width = 1024,
Height = 512, Height = 512,
Content = devTools, Content = devTools,
DataTemplates = new DataTemplates DataTemplates =
{ {
new ViewLocator<ViewModelBase>(), new ViewLocator<ViewModelBase>(),
} }

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

@ -388,7 +388,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = GetTemplate(), Template = GetTemplate(),
DataContext = "Base", DataContext = "Base",
DataTemplates = new DataTemplates DataTemplates =
{ {
new FuncDataTemplate<Item>(x => new Button { Content = x }) new FuncDataTemplate<Item>(x => new Button { Content = x })
}, },

8
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -109,10 +109,10 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = ListBoxTemplate(), Template = ListBoxTemplate(),
DataContext = "Base", DataContext = "Base",
DataTemplates = new DataTemplates DataTemplates =
{ {
new FuncDataTemplate<Item>(x => new Button { Content = x }) new FuncDataTemplate<Item>(x => new Button { Content = x })
}, },
Items = items, Items = items,
}; };

2
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs

@ -88,7 +88,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
root.Child = null; root.Child = null;
root = new TestRoot root = new TestRoot
{ {
DataTemplates = new DataTemplates DataTemplates =
{ {
new FuncDataTemplate<string>(x => new Decorator()), new FuncDataTemplate<string>(x => new Decorator()),
}, },

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

@ -174,7 +174,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate), Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate),
DataContext = "Base", DataContext = "Base",
DataTemplates = new DataTemplates DataTemplates =
{ {
new FuncDataTemplate<Item>(x => new Button { Content = x }) new FuncDataTemplate<Item>(x => new Button { Content = x })
}, },

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

@ -25,9 +25,9 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = CreateTreeViewTemplate(), Template = CreateTreeViewTemplate(),
Items = CreateTestTreeData(), Items = CreateTestTreeData(),
DataTemplates = CreateNodeDataTemplate(),
}; };
CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0)); Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0));
@ -69,9 +69,9 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = CreateTreeViewTemplate(), Template = CreateTreeViewTemplate(),
Items = CreateTestTreeData(), Items = CreateTestTreeData(),
DataTemplates = CreateNodeDataTemplate(),
}; };
CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
var container = (TreeViewItem)target.ItemContainerGenerator.Containers.Single().ContainerControl; var container = (TreeViewItem)target.ItemContainerGenerator.Containers.Single().ContainerControl;
@ -87,7 +87,6 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = CreateTreeViewTemplate(), Template = CreateTreeViewTemplate(),
Items = tree, Items = tree,
DataTemplates = CreateNodeDataTemplate(),
}; };
// For TreeViewItem to find its parent TreeView, OnAttachedToLogicalTree needs // For TreeViewItem to find its parent TreeView, OnAttachedToLogicalTree needs
@ -95,6 +94,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(); var root = new TestRoot();
root.Child = target; root.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
var container = target.ItemContainerGenerator.Index.ContainerFromItem( var container = target.ItemContainerGenerator.Index.ContainerFromItem(
@ -116,11 +116,12 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = CreateTreeViewTemplate(), Template = CreateTreeViewTemplate(),
Items = tree, Items = tree,
DataTemplates = CreateNodeDataTemplate(),
}; };
var visualRoot = new TestRoot(); var visualRoot = new TestRoot();
visualRoot.Child = target; visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
var item = tree[0].Children[1].Children[0]; var item = tree[0].Children[1].Children[0];
@ -146,11 +147,12 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = CreateTreeViewTemplate(), Template = CreateTreeViewTemplate(),
Items = tree, Items = tree,
DataTemplates = CreateNodeDataTemplate(),
}; };
var visualRoot = new TestRoot(); var visualRoot = new TestRoot();
visualRoot.Child = target; visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
var item = tree[0].Children[1].Children[0]; var item = tree[0].Children[1].Children[0];
@ -191,12 +193,13 @@ namespace Avalonia.Controls.UnitTests
var target = new TreeView var target = new TreeView
{ {
Template = CreateTreeViewTemplate(), Template = CreateTreeViewTemplate(),
DataTemplates = CreateNodeDataTemplate(),
Items = tree, Items = tree,
}; };
var root = new TestRoot(); var root = new TestRoot();
root.Child = target; root.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
Assert.Equal(5, target.ItemContainerGenerator.Index.Items.Count()); Assert.Equal(5, target.ItemContainerGenerator.Index.Items.Count());
@ -221,7 +224,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = CreateTreeViewTemplate(), Template = CreateTreeViewTemplate(),
DataContext = "Base", DataContext = "Base",
DataTemplates = new DataTemplates DataTemplates =
{ {
new FuncDataTemplate<Node>(x => new Button { Content = x }) new FuncDataTemplate<Node>(x => new Button { Content = x })
}, },
@ -291,9 +294,9 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = CreateTreeViewTemplate(), Template = CreateTreeViewTemplate(),
Items = data, Items = data,
DataTemplates = CreateNodeDataTemplate(),
}; };
CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0)); Assert.Equal(new[] { "Root" }, ExtractItemHeader(target, 0));
@ -328,7 +331,6 @@ namespace Avalonia.Controls.UnitTests
{ {
Template = CreateTreeViewTemplate(), Template = CreateTreeViewTemplate(),
Items = data, Items = data,
DataTemplates = CreateNodeDataTemplate(),
}; };
var button = new Button(); var button = new Button();
@ -341,6 +343,7 @@ namespace Avalonia.Controls.UnitTests
} }
}; };
CreateNodeDataTemplate(target);
ApplyTemplates(target); ApplyTemplates(target);
var item = data[0].Children[0]; 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 control.DataTemplates.Add(new TestTreeDataTemplate());
{
new TestTreeDataTemplate()
};
} }
private IControlTemplate CreateTreeViewTemplate() private IControlTemplate CreateTreeViewTemplate()

2
tests/Avalonia.LeakTests/ControlTests.cs

@ -276,7 +276,7 @@ namespace Avalonia.LeakTests
{ {
Content = target = new TreeView Content = target = new TreeView
{ {
DataTemplates = new DataTemplates DataTemplates =
{ {
new FuncTreeDataTemplate<Node>( new FuncTreeDataTemplate<Node>(
x => new TextBlock { Text = x.Name }, x => new TextBlock { Text = x.Name },

Loading…
Cancel
Save