From 0e8995e303e97d5fc44a2bd581a670d520489ec7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 19 May 2016 22:13:29 +0100 Subject: [PATCH 1/5] Added ContentControl.ContentTemplate. --- src/Avalonia.Controls/ContentControl.cs | 15 +++++++++++ src/Avalonia.Controls/IContentControl.cs | 6 +++++ .../Presenters/ContentPresenter.cs | 17 ++++++++++++- .../Templates/DataTemplateExtensions.cs | 25 ++++++++++++++++--- .../ContentControl.xaml | 3 ++- .../ContentControlTests.cs | 19 ++++++++++++++ 6 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs index 822d9da11a..0eba3c13ae 100644 --- a/src/Avalonia.Controls/ContentControl.cs +++ b/src/Avalonia.Controls/ContentControl.cs @@ -21,6 +21,12 @@ namespace Avalonia.Controls public static readonly StyledProperty ContentProperty = AvaloniaProperty.Register(nameof(Content)); + /// + /// Defines the property. + /// + public static readonly StyledProperty ContentTemplateProperty = + AvaloniaProperty.Register(nameof(ContentTemplate)); + /// /// Defines the property. /// @@ -51,6 +57,15 @@ namespace Avalonia.Controls set { SetValue(ContentProperty, value); } } + /// + /// Gets or sets the data template used to display the content of the control. + /// + public IDataTemplate ContentTemplate + { + get { return GetValue(ContentTemplateProperty); } + set { SetValue(ContentTemplateProperty, value); } + } + /// /// Gets the presenter from the control's template. /// diff --git a/src/Avalonia.Controls/IContentControl.cs b/src/Avalonia.Controls/IContentControl.cs index d09f5bd9c6..863c132778 100644 --- a/src/Avalonia.Controls/IContentControl.cs +++ b/src/Avalonia.Controls/IContentControl.cs @@ -1,6 +1,7 @@ // 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 Avalonia.Controls.Templates; using Avalonia.Layout; namespace Avalonia.Controls @@ -16,6 +17,11 @@ namespace Avalonia.Controls /// object Content { get; set; } + /// + /// Gets or sets the data template used to display the content of the control. + /// + IDataTemplate ContentTemplate { get; set; } + /// /// Gets or sets the horizontal alignment of the content within the control. /// diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 7f51229061..3470a43c1c 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -48,6 +48,12 @@ namespace Avalonia.Controls.Presenters public static readonly StyledProperty ContentProperty = ContentControl.ContentProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty ContentTemplateProperty = + ContentControl.ContentTemplateProperty.AddOwner(); + /// /// Defines the property. /// @@ -140,6 +146,15 @@ namespace Avalonia.Controls.Presenters set { SetValue(ContentProperty, value); } } + /// + /// Gets or sets the data template used to display the content of the control. + /// + public IDataTemplate ContentTemplate + { + get { return GetValue(ContentTemplateProperty); } + set { SetValue(ContentTemplateProperty, value); } + } + /// /// Gets or sets the radius of the border rounded corners. /// @@ -200,7 +215,7 @@ namespace Avalonia.Controls.Presenters { var old = Child; var content = Content; - var result = this.MaterializeDataTemplate(content); + var result = this.MaterializeDataTemplate(content, ContentTemplate); if (old != null) { diff --git a/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs b/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs index 782386df6a..9eff4243b1 100644 --- a/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs +++ b/src/Avalonia.Controls/Templates/DataTemplateExtensions.cs @@ -16,8 +16,15 @@ namespace Avalonia.Controls.Templates /// /// 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) + public static IControl MaterializeDataTemplate( + this IControl control, + object data, + IDataTemplate primary = null) { if (data == null) { @@ -33,7 +40,7 @@ namespace Avalonia.Controls.Templates } else { - IDataTemplate template = control.FindDataTemplate(data); + IDataTemplate template = control.FindDataTemplate(data, primary); IControl result; if (template != null) @@ -57,9 +64,21 @@ 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. + /// /// The data template or null if no matching data template was found. - public static IDataTemplate FindDataTemplate(this IControl control, object data) + public static IDataTemplate FindDataTemplate( + this IControl control, + object data, + IDataTemplate primary = null) { + if (primary?.Match(data) == true) + { + return primary; + } + foreach (var i in control.GetSelfAndLogicalAncestors().OfType()) { foreach (IDataTemplate dt in i.DataTemplates) diff --git a/src/Avalonia.Themes.Default/ContentControl.xaml b/src/Avalonia.Themes.Default/ContentControl.xaml index 1af449a447..27f6648548 100644 --- a/src/Avalonia.Themes.Default/ContentControl.xaml +++ b/src/Avalonia.Themes.Default/ContentControl.xaml @@ -5,7 +5,8 @@ Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" - Content="{TemplateBinding Content}" + Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}"/> diff --git a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs index 2ebdfc628c..5f86b9878d 100644 --- a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs @@ -116,6 +116,24 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new[] { child }, target.GetLogicalChildren()); } + [Fact] + public void Should_Use_ContentTemplate_To_Create_Control() + { + var target = new ContentControl + { + Template = GetTemplate(), + ContentTemplate = new FuncDataTemplate(_ => new Canvas()), + }; + + target.Content = "Foo"; + target.ApplyTemplate(); + ((ContentPresenter)target.Presenter).UpdateChild(); + + var child = target.Presenter.Child; + + Assert.IsType(child); + } + [Fact] public void DataTemplate_Created_Control_Should_Be_Logical_Child_After_ApplyTemplate() { @@ -266,6 +284,7 @@ namespace Avalonia.Controls.UnitTests { Name = "PART_ContentPresenter", [~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty], + [~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty], } }; }); From 5cd9b98057c16abe6140ac34fed9ac006c9d175c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 19 May 2016 22:48:50 +0100 Subject: [PATCH 2/5] Added ItemsControl.ItemTemplate property. --- src/Avalonia.Controls/DropDown.cs | 5 ++- .../Generators/IItemContainerGenerator.cs | 5 +++ .../Generators/ItemContainerGenerator.cs | 7 +++- .../Generators/ItemContainerGenerator`1.cs | 16 +++++++++- .../Generators/TreeItemContainerGenerator.cs | 4 ++- src/Avalonia.Controls/ItemsControl.cs | 32 ++++++++++++++++++- src/Avalonia.Controls/ListBox.cs | 5 ++- .../Presenters/ItemsPresenterBase.cs | 15 +++++++++ src/Avalonia.Controls/Primitives/TabStrip.cs | 5 ++- src/Avalonia.Controls/TreeView.cs | 1 + src/Avalonia.Controls/TreeViewItem.cs | 1 + src/Avalonia.Themes.Default/ContextMenu.xaml | 1 + src/Avalonia.Themes.Default/DropDown.xaml | 1 + src/Avalonia.Themes.Default/ItemsControl.xaml | 1 + src/Avalonia.Themes.Default/ListBox.xaml | 1 + src/Avalonia.Themes.Default/Menu.xaml | 1 + src/Avalonia.Themes.Default/MenuItem.xaml | 2 ++ src/Avalonia.Themes.Default/TabStrip.xaml | 3 +- .../ItemContainerGeneratorTypedTests.cs | 2 +- .../ItemsControlTests.cs | 16 ++++++++++ .../ListBoxTests.cs | 22 +++++++++++++ .../Presenters/CarouselPresenterTests.cs | 2 +- .../Presenters/ItemsPresenterTests.cs | 5 +-- 23 files changed, 141 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index 6dabe9f8e3..da5dd5daaf 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -68,7 +68,10 @@ namespace Avalonia.Controls /// protected override IItemContainerGenerator CreateItemContainerGenerator() { - return new ItemContainerGenerator(this, DropDownItem.ContentProperty); + return new ItemContainerGenerator( + this, + DropDownItem.ContentProperty, + DropDownItem.ContentTemplateProperty); } /// diff --git a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs index af7901d05d..8199333f47 100644 --- a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs @@ -18,6 +18,11 @@ namespace Avalonia.Controls.Generators /// IEnumerable Containers { get; } + /// + /// Gets or sets the data template used to display the items in the control. + /// + IDataTemplate ItemTemplate { get; set; } + /// /// Signalled whenever new containers are materialized. /// diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs index d3032d7c90..114d31c5a6 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs @@ -37,6 +37,11 @@ namespace Avalonia.Controls.Generators /// public event EventHandler Dematerialized; + /// + /// Gets or sets the data template used to display the items in the control. + /// + public IDataTemplate ItemTemplate { get; set; } + /// /// Gets the owner control. /// @@ -156,7 +161,7 @@ namespace Avalonia.Controls.Generators /// The created container control. protected virtual IControl CreateContainer(object item) { - var result = Owner.MaterializeDataTemplate(item); + var result = Owner.MaterializeDataTemplate(item, ItemTemplate); if (result != null && !(item is IControl)) { diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs index c1521124f5..c4bc730e15 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs @@ -19,15 +19,18 @@ namespace Avalonia.Controls.Generators /// /// The owner control. /// The container's Content property. + /// The container's ContentTemplate property. public ItemContainerGenerator( IControl owner, - AvaloniaProperty contentProperty) + AvaloniaProperty contentProperty, + AvaloniaProperty contentTemplateProperty) : base(owner) { Contract.Requires(owner != null); Contract.Requires(contentProperty != null); ContentProperty = contentProperty; + ContentTemplateProperty = contentTemplateProperty; } /// @@ -35,6 +38,11 @@ namespace Avalonia.Controls.Generators /// protected AvaloniaProperty ContentProperty { get; } + /// + /// Gets the container's ContentTemplate property. + /// + protected AvaloniaProperty ContentTemplateProperty { get; } + /// protected override IControl CreateContainer(object item) { @@ -51,6 +59,12 @@ namespace Avalonia.Controls.Generators else { var result = new T(); + + if (ContentTemplateProperty != null) + { + result.SetValue(ContentTemplateProperty, ItemTemplate); + } + result.SetValue(ContentProperty, item); if (!(item is IControl)) diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs index 307815a00e..83b574b5c8 100644 --- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs @@ -20,16 +20,18 @@ namespace Avalonia.Controls.Generators /// /// The owner control. /// The container's Content property. + /// The container's ContentTemplate property. /// The container's Items property. /// The container's IsExpanded property. /// The container index for the tree public TreeItemContainerGenerator( IControl owner, AvaloniaProperty contentProperty, + AvaloniaProperty contentTemplateProperty, AvaloniaProperty itemsProperty, AvaloniaProperty isExpandedProperty, TreeContainerIndex index) - : base(owner, contentProperty) + : base(owner, contentProperty, contentTemplateProperty) { Contract.Requires(owner != null); Contract.Requires(contentProperty != null); diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 576437fe0a..3a77f61f1b 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -41,6 +41,12 @@ namespace Avalonia.Controls public static readonly StyledProperty> ItemsPanelProperty = AvaloniaProperty.Register>(nameof(ItemsPanel), DefaultPanel); + /// + /// Defines the property. + /// + public static readonly StyledProperty ItemTemplateProperty = + AvaloniaProperty.Register(nameof(ItemTemplate)); + /// /// Defines the property. /// @@ -65,6 +71,7 @@ namespace Avalonia.Controls { PseudoClasses.Add(":empty"); SubscribeToItems(_items); + ItemTemplateProperty.Changed.AddClassHandler(x => x.ItemTemplateChanged); } /// @@ -80,6 +87,7 @@ namespace Avalonia.Controls if (_itemContainerGenerator != null) { + _itemContainerGenerator.ItemTemplate = ItemTemplate; _itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e); _itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e); } @@ -108,6 +116,15 @@ namespace Avalonia.Controls set { SetValue(ItemsPanelProperty, value); } } + /// + /// Gets or sets the data template used to display the items in the control. + /// + public IDataTemplate ItemTemplate + { + get { return GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + /// /// Selects a member from to use as the list item. /// @@ -354,7 +371,7 @@ namespace Avalonia.Controls /// /// Subscribes to an collection. /// - /// + /// The items collection. private void SubscribeToItems(IEnumerable items) { PseudoClasses.Set(":empty", items == null || items.Count() == 0); @@ -366,5 +383,18 @@ namespace Avalonia.Controls incc.CollectionChanged += ItemsCollectionChanged; } } + + /// + /// Called when the changes. + /// + /// The event args. + private void ItemTemplateChanged(AvaloniaPropertyChangedEventArgs e) + { + if (_itemContainerGenerator != null) + { + _itemContainerGenerator.ItemTemplate = (IDataTemplate)e.NewValue; + // TODO: Rebuild the item containers. + } + } } } diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index 06f6e23934..0f32bc91da 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -41,7 +41,10 @@ namespace Avalonia.Controls /// protected override IItemContainerGenerator CreateItemContainerGenerator() { - return new ItemContainerGenerator(this, ListBoxItem.ContentProperty); + return new ItemContainerGenerator( + this, + ListBoxItem.ContentProperty, + ListBoxItem.ContentTemplateProperty); } /// diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs index a0db90da6c..91ffec9c4c 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs @@ -27,6 +27,12 @@ namespace Avalonia.Controls.Presenters public static readonly StyledProperty> ItemsPanelProperty = ItemsControl.ItemsPanelProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty ItemTemplateProperty = + ItemsControl.ItemTemplateProperty.AddOwner(); + /// /// Defines the property. /// @@ -122,6 +128,15 @@ namespace Avalonia.Controls.Presenters set { SetValue(ItemsPanelProperty, value); } } + /// + /// Gets or sets the data template used to display the items in the control. + /// + public IDataTemplate ItemTemplate + { + get { return GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + /// /// Selects a member from to use as the list item. /// diff --git a/src/Avalonia.Controls/Primitives/TabStrip.cs b/src/Avalonia.Controls/Primitives/TabStrip.cs index 4850b7f8a3..4a6c9af95f 100644 --- a/src/Avalonia.Controls/Primitives/TabStrip.cs +++ b/src/Avalonia.Controls/Primitives/TabStrip.cs @@ -20,7 +20,10 @@ namespace Avalonia.Controls.Primitives protected override IItemContainerGenerator CreateItemContainerGenerator() { - return new ItemContainerGenerator(this, ContentControl.ContentProperty); + return new ItemContainerGenerator( + this, + ContentControl.ContentProperty, + ContentControl.ContentTemplateProperty); } /// diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 1c0bd964b1..462e844bb6 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -96,6 +96,7 @@ namespace Avalonia.Controls var result = new TreeItemContainerGenerator( this, TreeViewItem.HeaderProperty, + null, TreeViewItem.ItemsProperty, TreeViewItem.IsExpandedProperty, new TreeContainerIndex()); diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index fb3917b6d9..a9be3f4796 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -80,6 +80,7 @@ namespace Avalonia.Controls return new TreeItemContainerGenerator( this, TreeViewItem.HeaderProperty, + null, TreeViewItem.ItemsProperty, TreeViewItem.IsExpandedProperty, _treeView?.ItemContainerGenerator.Index ?? new TreeContainerIndex()); diff --git a/src/Avalonia.Themes.Default/ContextMenu.xaml b/src/Avalonia.Themes.Default/ContextMenu.xaml index 88454036dc..41d53aa6b3 100644 --- a/src/Avalonia.Themes.Default/ContextMenu.xaml +++ b/src/Avalonia.Themes.Default/ContextMenu.xaml @@ -13,6 +13,7 @@ diff --git a/src/Avalonia.Themes.Default/DropDown.xaml b/src/Avalonia.Themes.Default/DropDown.xaml index 20c497bfd4..5b8649457e 100644 --- a/src/Avalonia.Themes.Default/DropDown.xaml +++ b/src/Avalonia.Themes.Default/DropDown.xaml @@ -38,6 +38,7 @@ BorderThickness="1"> diff --git a/src/Avalonia.Themes.Default/ItemsControl.xaml b/src/Avalonia.Themes.Default/ItemsControl.xaml index aa56a21e82..7b6671b42c 100644 --- a/src/Avalonia.Themes.Default/ItemsControl.xaml +++ b/src/Avalonia.Themes.Default/ItemsControl.xaml @@ -4,6 +4,7 @@ diff --git a/src/Avalonia.Themes.Default/ListBox.xaml b/src/Avalonia.Themes.Default/ListBox.xaml index ad410c1b5f..782cc36107 100644 --- a/src/Avalonia.Themes.Default/ListBox.xaml +++ b/src/Avalonia.Themes.Default/ListBox.xaml @@ -11,6 +11,7 @@ diff --git a/src/Avalonia.Themes.Default/Menu.xaml b/src/Avalonia.Themes.Default/Menu.xaml index 397f247a4f..a1a5afb4a9 100644 --- a/src/Avalonia.Themes.Default/Menu.xaml +++ b/src/Avalonia.Themes.Default/Menu.xaml @@ -8,6 +8,7 @@ diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index bb7c6337a6..a7af6c51cf 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -53,6 +53,7 @@ + ItemsPanel="{TemplateBinding ItemsPanel}" + ItemTemplate="{TemplateBinding ItemTemplate}"/> diff --git a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs index c5b0a36657..2522f1ef03 100644 --- a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs @@ -14,7 +14,7 @@ namespace Avalonia.Controls.UnitTests.Generators { var items = new[] { "foo", "bar", "baz" }; var owner = new Decorator(); - var target = new ItemContainerGenerator(owner, ListBoxItem.ContentProperty); + var target = new ItemContainerGenerator(owner, ListBoxItem.ContentProperty, null); var containers = target.Materialize(0, items, null); var result = containers .Select(x => x.ContainerControl) diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index 3ff4ee03fc..92c89039f5 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -14,6 +14,22 @@ namespace Avalonia.Controls.UnitTests { public class ItemsControlTests { + [Fact] + public void Should_Use_ItemTemplate_To_Create_Control() + { + var target = new ItemsControl + { + Template = GetTemplate(), + ItemTemplate = new FuncDataTemplate(_ => new Canvas()), + }; + + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + + Assert.IsType(target.Presenter.Panel.Children[0]); + } + [Fact] public void Panel_Should_Have_TemplatedParent_Set_To_ItemsControl() { diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 996423d3b7..d5fd59d1fc 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -14,6 +14,27 @@ namespace Avalonia.Controls.UnitTests { public class ListBoxTests { + [Fact] + public void Should_Use_ItemTemplate_To_Create_Item_Content() + { + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + ItemTemplate = new FuncDataTemplate(_ => new Canvas()), + }; + + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + + var container = (ListBoxItem)target.Presenter.Panel.Children[0]; + container.Template = ListBoxItemTemplate(); + container.ApplyTemplate(); + ((ContentPresenter)container.Presenter).UpdateChild(); + + Assert.IsType(container.Presenter.Child); + } + [Fact] public void ListBox_Should_Find_ItemsPresenter_In_ScrollViewer() { @@ -123,6 +144,7 @@ namespace Avalonia.Controls.UnitTests { Name = "PART_ContentPresenter", [!ContentPresenter.ContentProperty] = parent[!ListBoxItem.ContentProperty], + [!ContentPresenter.ContentTemplateProperty] = parent[!ListBoxItem.ContentTemplateProperty], }); } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs index 3c5dbfbb21..33bbf83140 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs @@ -201,7 +201,7 @@ namespace Avalonia.Controls.UnitTests.Presenters { protected override IItemContainerGenerator CreateItemContainerGenerator() { - return new ItemContainerGenerator(this, TestItem.ContentProperty); + return new ItemContainerGenerator(this, TestItem.ContentProperty, null); } } } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs index 89336131ea..519b9b3d1d 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs @@ -54,7 +54,8 @@ namespace Avalonia.Controls.UnitTests.Presenters target.ItemContainerGenerator = new ItemContainerGenerator( target, - ListBoxItem.ContentProperty); + ListBoxItem.ContentProperty, + null); target.ApplyTemplate(); Assert.Equal(2, target.Panel.Children.Count); @@ -332,7 +333,7 @@ namespace Avalonia.Controls.UnitTests.Presenters { protected override IItemContainerGenerator CreateItemContainerGenerator() { - return new ItemContainerGenerator(this, TestItem.ContentProperty); + return new ItemContainerGenerator(this, TestItem.ContentProperty, null); } } } From a1f355814578082f80e1efbe4fd3f62fe3cbc9a9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 19 May 2016 22:58:44 +0100 Subject: [PATCH 3/5] Added ContentTemplate binding to control templates. --- src/Avalonia.Themes.Default/Button.xaml | 1 + src/Avalonia.Themes.Default/CheckBox.xaml | 1 + src/Avalonia.Themes.Default/DropDown.xaml | 1 + src/Avalonia.Themes.Default/DropDownItem.xaml | 1 + src/Avalonia.Themes.Default/Expander.xaml | 4 ++++ src/Avalonia.Themes.Default/LayoutTransformControl.xaml | 1 + src/Avalonia.Themes.Default/ListBoxItem.xaml | 1 + src/Avalonia.Themes.Default/MenuItem.xaml | 3 +++ src/Avalonia.Themes.Default/PopupRoot.xaml | 1 + src/Avalonia.Themes.Default/RadioButton.xaml | 1 + src/Avalonia.Themes.Default/TabStripItem.xaml | 1 + src/Avalonia.Themes.Default/ToggleButton.xaml | 1 + src/Avalonia.Themes.Default/ToolTip.xaml | 1 + src/Avalonia.Themes.Default/Window.xaml | 5 ++++- 14 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Default/Button.xaml b/src/Avalonia.Themes.Default/Button.xaml index bf8072d588..daa2973b21 100644 --- a/src/Avalonia.Themes.Default/Button.xaml +++ b/src/Avalonia.Themes.Default/Button.xaml @@ -14,6 +14,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}" TextBlock.Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" diff --git a/src/Avalonia.Themes.Default/CheckBox.xaml b/src/Avalonia.Themes.Default/CheckBox.xaml index b1596e6b74..e88e93eac6 100644 --- a/src/Avalonia.Themes.Default/CheckBox.xaml +++ b/src/Avalonia.Themes.Default/CheckBox.xaml @@ -22,6 +22,7 @@ diff --git a/src/Avalonia.Themes.Default/DropDown.xaml b/src/Avalonia.Themes.Default/DropDown.xaml index 5b8649457e..3531605a65 100644 --- a/src/Avalonia.Themes.Default/DropDown.xaml +++ b/src/Avalonia.Themes.Default/DropDown.xaml @@ -11,6 +11,7 @@ BorderThickness="{TemplateBinding BorderThickness}"> diff --git a/src/Avalonia.Themes.Default/DropDownItem.xaml b/src/Avalonia.Themes.Default/DropDownItem.xaml index f4e7e3a5b5..98e51a674f 100644 --- a/src/Avalonia.Themes.Default/DropDownItem.xaml +++ b/src/Avalonia.Themes.Default/DropDownItem.xaml @@ -10,6 +10,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" Padding="{TemplateBinding Padding}"/> diff --git a/src/Avalonia.Themes.Default/Expander.xaml b/src/Avalonia.Themes.Default/Expander.xaml index 4123d1a9df..9bf9958ecc 100644 --- a/src/Avalonia.Themes.Default/Expander.xaml +++ b/src/Avalonia.Themes.Default/Expander.xaml @@ -17,6 +17,7 @@ Grid.Row="1" IsVisible="{TemplateBinding IsExpanded}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" /> @@ -34,6 +35,7 @@ Grid.Row="0" IsVisible="{TemplateBinding IsExpanded}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" /> @@ -51,6 +53,7 @@ Grid.Column="1" IsVisible="{TemplateBinding IsExpanded}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" /> @@ -68,6 +71,7 @@ Grid.Column="0" IsVisible="{TemplateBinding IsExpanded}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" /> diff --git a/src/Avalonia.Themes.Default/LayoutTransformControl.xaml b/src/Avalonia.Themes.Default/LayoutTransformControl.xaml index b1ebb2a819..c9df62625f 100644 --- a/src/Avalonia.Themes.Default/LayoutTransformControl.xaml +++ b/src/Avalonia.Themes.Default/LayoutTransformControl.xaml @@ -6,6 +6,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}"/> diff --git a/src/Avalonia.Themes.Default/ListBoxItem.xaml b/src/Avalonia.Themes.Default/ListBoxItem.xaml index af384ae83b..61466abd1b 100644 --- a/src/Avalonia.Themes.Default/ListBoxItem.xaml +++ b/src/Avalonia.Themes.Default/ListBoxItem.xaml @@ -7,6 +7,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}"/> diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index a7af6c51cf..01e4c3f56d 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -13,6 +13,7 @@ @@ -82,6 +84,7 @@ diff --git a/src/Avalonia.Themes.Default/PopupRoot.xaml b/src/Avalonia.Themes.Default/PopupRoot.xaml index b0b75f3652..f500de6ef3 100644 --- a/src/Avalonia.Themes.Default/PopupRoot.xaml +++ b/src/Avalonia.Themes.Default/PopupRoot.xaml @@ -5,6 +5,7 @@ diff --git a/src/Avalonia.Themes.Default/RadioButton.xaml b/src/Avalonia.Themes.Default/RadioButton.xaml index 25a2510078..182c710b7c 100644 --- a/src/Avalonia.Themes.Default/RadioButton.xaml +++ b/src/Avalonia.Themes.Default/RadioButton.xaml @@ -21,6 +21,7 @@ VerticalAlignment="Center"/> diff --git a/src/Avalonia.Themes.Default/TabStripItem.xaml b/src/Avalonia.Themes.Default/TabStripItem.xaml index 8a33c7f8c1..35848bda59 100644 --- a/src/Avalonia.Themes.Default/TabStripItem.xaml +++ b/src/Avalonia.Themes.Default/TabStripItem.xaml @@ -9,6 +9,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}"/> diff --git a/src/Avalonia.Themes.Default/ToggleButton.xaml b/src/Avalonia.Themes.Default/ToggleButton.xaml index 32b9b4b991..0c8a3fa425 100644 --- a/src/Avalonia.Themes.Default/ToggleButton.xaml +++ b/src/Avalonia.Themes.Default/ToggleButton.xaml @@ -14,6 +14,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}" TextBlock.Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" diff --git a/src/Avalonia.Themes.Default/ToolTip.xaml b/src/Avalonia.Themes.Default/ToolTip.xaml index 97a326f033..46e297fbc2 100644 --- a/src/Avalonia.Themes.Default/ToolTip.xaml +++ b/src/Avalonia.Themes.Default/ToolTip.xaml @@ -10,6 +10,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}"/> diff --git a/src/Avalonia.Themes.Default/Window.xaml b/src/Avalonia.Themes.Default/Window.xaml index 9953cba32a..49cd4ee747 100644 --- a/src/Avalonia.Themes.Default/Window.xaml +++ b/src/Avalonia.Themes.Default/Window.xaml @@ -6,7 +6,10 @@ - + From 57a0122549d23e25f6091de9101d639ea6ee84a4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 20 May 2016 15:38:29 +0200 Subject: [PATCH 4/5] Make ItemTemplate work for TreeView. --- .../Generators/TreeItemContainerGenerator.cs | 6 +-- src/Avalonia.Controls/TreeView.cs | 2 +- src/Avalonia.Controls/TreeViewItem.cs | 7 ++- .../TreeViewTests.cs | 45 +++++++++++++++++-- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs index 83b574b5c8..681bea865a 100644 --- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs @@ -75,7 +75,7 @@ namespace Avalonia.Controls.Generators } else { - var template = GetTreeDataTemplate(item); + var template = GetTreeDataTemplate(item, ItemTemplate); var result = new T(); result.SetValue(ContentProperty, template.Build(item)); @@ -123,9 +123,9 @@ namespace Avalonia.Controls.Generators /// /// The item. /// The template. - private ITreeDataTemplate GetTreeDataTemplate(object item) + private ITreeDataTemplate GetTreeDataTemplate(object item, IDataTemplate primary) { - var template = Owner.FindDataTemplate(item) ?? FuncDataTemplate.Default; + var template = Owner.FindDataTemplate(item, primary) ?? FuncDataTemplate.Default; var treeTemplate = template as ITreeDataTemplate ?? new FuncTreeDataTemplate(typeof(object), template.Build, x => null); return treeTemplate; diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 462e844bb6..b966d09b1f 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -96,7 +96,7 @@ namespace Avalonia.Controls var result = new TreeItemContainerGenerator( this, TreeViewItem.HeaderProperty, - null, + TreeViewItem.ItemTemplateProperty, TreeViewItem.ItemsProperty, TreeViewItem.IsExpandedProperty, new TreeContainerIndex()); diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index a9be3f4796..bed27ef033 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -80,7 +80,7 @@ namespace Avalonia.Controls return new TreeItemContainerGenerator( this, TreeViewItem.HeaderProperty, - null, + TreeViewItem.ItemTemplateProperty, TreeViewItem.ItemsProperty, TreeViewItem.IsExpandedProperty, _treeView?.ItemContainerGenerator.Index ?? new TreeContainerIndex()); @@ -91,6 +91,11 @@ namespace Avalonia.Controls { base.OnAttachedToLogicalTree(e); _treeView = this.GetLogicalAncestors().OfType().FirstOrDefault(); + + if (ItemTemplate == null && _treeView?.ItemTemplate != null) + { + ItemTemplate = _treeView.ItemTemplate; + } } protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 5404218a4b..7b19a6a4e2 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -35,6 +35,33 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2)); } + [Fact] + public void Items_Should_Be_Created_Using_ItemTemplate_If_Present() + { + TreeView target; + + var root = new TestRoot + { + Child = target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = CreateTestTreeData(), + ItemTemplate = new FuncTreeDataTemplate( + _ => new Canvas(), + x => x.Children), + } + }; + + ApplyTemplates(target); + + var items = target.ItemContainerGenerator.Index.Items + .OfType() + .ToList(); + + Assert.Equal(4, items.Count); + Assert.All(items, x => Assert.IsType(x.HeaderPresenter.Child)); + } + [Fact] public void Root_ItemContainerGenerator_Containers_Should_Be_Root_Containers() { @@ -302,6 +329,7 @@ namespace Avalonia.Controls.UnitTests control.Template = CreateTreeViewItemTemplate(); control.ApplyTemplate(); control.Presenter.ApplyTemplate(); + control.HeaderPresenter.ApplyTemplate(); ApplyTemplates(control.Presenter.Panel.Children); } } @@ -354,10 +382,21 @@ namespace Avalonia.Controls.UnitTests private IControlTemplate CreateTreeViewItemTemplate() { - return new FuncControlTemplate(parent => new ItemsPresenter + return new FuncControlTemplate(parent => new Panel { - Name = "PART_ItemsPresenter", - [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], + Children = new Controls + { + new ContentPresenter + { + Name = "PART_HeaderPresenter", + [~ContentPresenter.ContentProperty] = parent[~TreeViewItem.HeaderProperty], + }, + new ItemsPresenter + { + Name = "PART_ItemsPresenter", + [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], + } + } }); } From ac7e224c3cb1aa3d75d37a43c87eded5939f7c9c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 20 May 2016 15:38:53 +0200 Subject: [PATCH 5/5] Use ItemTemplates in test app. --- samples/XamlTestApplicationPcl/Views/MainWindow.xaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/XamlTestApplicationPcl/Views/MainWindow.xaml b/samples/XamlTestApplicationPcl/Views/MainWindow.xaml index 5b0536b668..b93072d2d5 100644 --- a/samples/XamlTestApplicationPcl/Views/MainWindow.xaml +++ b/samples/XamlTestApplicationPcl/Views/MainWindow.xaml @@ -143,14 +143,14 @@ - + - + @@ -168,14 +168,14 @@ - + - +