diff --git a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs index 70d9dbec08..f9772cb399 100644 --- a/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/IItemContainerGenerator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Avalonia.Controls.Templates; +using Avalonia.Styling; namespace Avalonia.Controls.Generators { @@ -14,6 +15,11 @@ namespace Avalonia.Controls.Generators /// IEnumerable Containers { get; } + /// + /// Gets or sets the theme to be applied to the items in the control. + /// + ControlTheme? ItemContainerTheme { get; set; } + /// /// Gets or sets the data template used to display the items in the control. /// diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs index a76dcbe9c8..8b36b07cec 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator.cs @@ -4,6 +4,7 @@ using System.Linq; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Styling; namespace Avalonia.Controls.Generators { @@ -35,6 +36,11 @@ namespace Avalonia.Controls.Generators /// public event EventHandler? Recycled; + /// + /// Gets or sets the theme to be applied to the items in the control. + /// + public ControlTheme? ItemContainerTheme { get; set; } + /// /// Gets or sets the data template used to display the items in the control. /// @@ -190,10 +196,18 @@ namespace Avalonia.Controls.Generators result.SetValue( ContentPresenter.ContentTemplateProperty, ItemTemplate, - BindingPriority.TemplatedParent); + BindingPriority.Style); } } + if (ItemContainerTheme != null) + { + result.SetValue( + StyledElement.ThemeProperty, + ItemContainerTheme, + BindingPriority.TemplatedParent); + } + return result; } diff --git a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs index 635f3a7d37..3ff1b0702d 100644 --- a/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs +++ b/src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs @@ -44,28 +44,29 @@ namespace Avalonia.Controls.Generators { var container = item as T; - if (container != null) + if (container is null) { - return container; - } - else - { - var result = new T(); + container = new T(); if (ContentTemplateProperty != null) { - result.SetValue(ContentTemplateProperty, ItemTemplate, BindingPriority.Style); + container.SetValue(ContentTemplateProperty, ItemTemplate, BindingPriority.Style); } - result.SetValue(ContentProperty, item, BindingPriority.Style); + container.SetValue(ContentProperty, item, BindingPriority.Style); if (!(item is IControl)) { - result.DataContext = item; + container.DataContext = item; } + } - return result; + if (ItemContainerTheme != null) + { + container.SetValue(StyledElement.ThemeProperty, ItemContainerTheme, BindingPriority.Style); } + + return container; } /// diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs index 536a5fdd06..4e3deb5552 100644 --- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs @@ -71,6 +71,11 @@ namespace Avalonia.Controls.Generators var template = GetTreeDataTemplate(item, ItemTemplate); var result = new T(); + if (ItemContainerTheme != null) + { + result.SetValue(Control.ThemeProperty, ItemContainerTheme, BindingPriority.Style); + } + result.SetValue(ContentProperty, template.Build(item), BindingPriority.Style); var itemsSelector = template.ItemsSelector(item); diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 56b0014c05..427f5c8a9b 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -15,6 +15,7 @@ using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Metadata; using Avalonia.VisualTree; +using Avalonia.Styling; namespace Avalonia.Controls { @@ -36,6 +37,12 @@ namespace Avalonia.Controls public static readonly DirectProperty ItemsProperty = AvaloniaProperty.RegisterDirect(nameof(Items), o => o.Items, (o, v) => o.Items = v); + /// + /// Defines the property. + /// + public static readonly StyledProperty ItemContainerThemeProperty = + AvaloniaProperty.Register(nameof(ItemContainerTheme)); + /// /// Defines the property. /// @@ -88,6 +95,7 @@ namespace Avalonia.Controls { _itemContainerGenerator = CreateItemContainerGenerator(); + _itemContainerGenerator.ItemContainerTheme = ItemContainerTheme; _itemContainerGenerator.ItemTemplate = ItemTemplate; _itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e); _itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e); @@ -108,6 +116,15 @@ namespace Avalonia.Controls set { SetAndRaise(ItemsProperty, ref _items, value); } } + /// + /// Gets or sets the that is applied to the container element generated for each item. + /// + public ControlTheme? ItemContainerTheme + { + get { return GetValue(ItemContainerThemeProperty); } + set { SetValue(ItemContainerThemeProperty, value); } + } + /// /// Gets the number of items in . /// @@ -349,6 +366,10 @@ namespace Avalonia.Controls { UpdatePseudoClasses(change.GetNewValue()); } + else if (change.Property == ItemContainerThemeProperty && _itemContainerGenerator is not null) + { + _itemContainerGenerator.ItemContainerTheme = change.GetNewValue(); + } } /// diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index 490b0b3ce3..2e3aa037c2 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -121,6 +121,11 @@ namespace Avalonia.Controls { ItemTemplate = _treeView.ItemTemplate; } + + if (ItemContainerTheme == null && _treeView?.ItemContainerTheme != null) + { + ItemContainerTheme = _treeView.ItemContainerTheme; + } } protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index bfece7871c..f08653a4f8 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -1,15 +1,14 @@ +using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using Avalonia.Collections; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; +using Avalonia.Input; using Avalonia.LogicalTree; -using Avalonia.VisualTree; -using Xunit; -using System.Collections.ObjectModel; +using Avalonia.Styling; using Avalonia.UnitTests; -using Avalonia.Input; -using System.Collections.Generic; +using Xunit; namespace Avalonia.Controls.UnitTests { @@ -62,6 +61,25 @@ namespace Avalonia.Controls.UnitTests Assert.Null(container.TemplatedParent); } + [Fact] + public void Container_Should_Have_Theme_Set_To_ItemContainerTheme() + { + var theme = new ControlTheme(); + var target = new ItemsControl + { + ItemContainerTheme = theme, + }; + + target.Template = GetTemplate(); + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + target.Presenter.ApplyTemplate(); + + var container = (ContentPresenter)target.Presenter.Panel.Children[0]; + + Assert.Same(container.Theme, theme); + } + [Fact] public void Container_Should_Have_LogicalParent_Set_To_ItemsControl() { diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index e87990ebb1..afa153a593 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -94,6 +94,50 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Container_Should_Have_Theme_Set_To_ItemContainerTheme() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + var items = new[] { "Foo", "Bar", "Baz " }; + var theme = new ControlTheme(); + var target = new ListBox + { + Template = ListBoxTemplate(), + Items = items, + ItemContainerTheme = theme, + }; + + Prepare(target); + + var container = (ListBoxItem)target.Presenter.Panel.Children[0]; + + Assert.Same(container.Theme, theme); + } + } + + [Fact] + public void Inline_Item_Should_Have_Theme_Set_To_ItemContainerTheme() + { + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) + { + var items = new[] { "Foo", "Bar", "Baz " }; + var theme = new ControlTheme(); + var target = new ListBox + { + Template = ListBoxTemplate(), + Items = new[] { new ListBoxItem() }, + ItemContainerTheme = theme, + }; + + Prepare(target); + + var container = (ListBoxItem)target.Presenter.Panel.Children[0]; + + Assert.Same(container.Theme, theme); + } + } + [Fact] public void LogicalChildren_Should_Be_Set_For_DataTemplate_Generated_Items() { diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 2169b15cad..d784caf2db 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -72,6 +72,35 @@ namespace Avalonia.Controls.UnitTests Assert.All(items, x => Assert.IsType(x.HeaderPresenter.Child)); } + [Fact] + public void Items_Should_Be_Created_Using_ItemConatinerTheme_If_Present() + { + TreeView target; + var theme = new ControlTheme(); + + var root = new TestRoot + { + Child = target = new TreeView + { + Template = CreateTreeViewTemplate(), + Items = CreateTestTreeData(), + ItemContainerTheme = theme, + ItemTemplate = new FuncTreeDataTemplate( + (_, __) => new Canvas(), + x => x.Children), + } + }; + + ApplyTemplates(target); + + var items = target.ItemContainerGenerator.Index.Containers + .OfType() + .ToList(); + + Assert.Equal(5, items.Count); + Assert.All(items, x => Assert.Same(theme, x.ItemContainerTheme)); + } + [Fact] public void Root_ItemContainerGenerator_Containers_Should_Be_Root_Containers() {