-
Notifications
You must be signed in to change notification settings - Fork 2k
[Windows] Fix for Grouping collection view without data template results in displaying the default string representation of the object #28617
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
46dbbf5
c566055
5673f1e
e400f3c
f35d4fc
ef1ba30
7c8e398
8750e16
09f05bd
47808cc
9a9bc0d
7d89c8d
b3cef0c
de128d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,5 @@ | ||
| #nullable disable | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Text; | ||
| using Microsoft.Maui.Controls.Platform; | ||
| using Microsoft.Maui.Handlers; | ||
| using Microsoft.UI.Xaml.Controls; | ||
|
|
@@ -41,6 +39,19 @@ protected override void UpdateItemTemplate() | |
| { | ||
| base.UpdateItemTemplate(); | ||
|
|
||
| // When a GroupFooterTemplate is set but no ItemTemplate, the fake footer item | ||
| // (GroupFooterItemTemplateContext) appended to each group's Items list needs a | ||
| // template to render correctly. Without a selector WinUI calls ToString() on it, | ||
| // displaying the class name instead of the bound footer content. | ||
| if (Element.ItemTemplate is null && ItemsView.IsGrouped && ItemsView.GroupFooterTemplate is not null) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [major] Handler Mapper and Property Patterns — This selector is only updated from
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [major] Handler Mapper and Property Patterns - The footer
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| { | ||
| ListViewBase.ItemTemplateSelector = new GroupFooterDataTemplateSelector(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [major] Handler Mapper and Property Patterns The footer selector is only updated from |
||
| } | ||
| else | ||
| { | ||
| ListViewBase.ItemTemplateSelector = null; | ||
| } | ||
|
|
||
| ListViewBase.GroupStyleSelector = new GroupHeaderStyleSelector(); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| using UWPApp = Microsoft.UI.Xaml.Application; | ||
| using UWPDataTemplate = Microsoft.UI.Xaml.DataTemplate; | ||
| using WinDataTemplateSelector = Microsoft.UI.Xaml.Controls.DataTemplateSelector; | ||
|
|
||
| namespace Microsoft.Maui.Controls.Platform; | ||
|
|
||
| // Selects ItemsViewDefaultTemplate for fake group-footer items (GroupFooterItemTemplateContext) | ||
| // so that the GroupFooterTemplate is applied when no ItemTemplate is set on the CollectionView. | ||
| // All other items return null, letting WinUI fall back to its default ToString() rendering. | ||
| internal partial class GroupFooterDataTemplateSelector : WinDataTemplateSelector | ||
| { | ||
| readonly UWPDataTemplate? _footerTemplate; | ||
|
|
||
| public GroupFooterDataTemplateSelector() | ||
| { | ||
| _footerTemplate = UWPApp.Current.Resources["ItemsViewDefaultTemplate"] as UWPDataTemplate; | ||
| } | ||
|
|
||
| protected override UWPDataTemplate SelectTemplateCore(object item) | ||
| { | ||
| if (item is GroupFooterItemTemplateContext) | ||
| { | ||
| return _footerTemplate!; | ||
| } | ||
|
|
||
| return null!; | ||
| } | ||
|
|
||
| protected override UWPDataTemplate SelectTemplateCore(object item, Microsoft.UI.Xaml.DependencyObject container) | ||
| { | ||
| return SelectTemplateCore(item); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| #nullable disable | ||
| using System; | ||
| using System.Collections; | ||
| using System.Collections.Generic; | ||
| using System.Collections.ObjectModel; | ||
| using System.Collections.Specialized; | ||
|
|
||
|
|
@@ -67,13 +68,33 @@ public void Dispose() | |
| GroupTemplateContext CreateGroupTemplateContext(object group) | ||
| { | ||
| var groupHeaderTemplateContext = _groupHeaderTemplate != null | ||
| ? new ItemTemplateContext(_groupHeaderTemplate, group, _container, mauiContext: _mauiContext) : null; | ||
| ? new ItemTemplateContext(_groupHeaderTemplate, group, _container, mauiContext: _mauiContext) : null; | ||
|
|
||
| var groupFooterTemplateContext = _groupFooterTemplate != null | ||
| ? new GroupFooterItemTemplateContext(_groupFooterTemplate, group, _container, mauiContext: _mauiContext) : null; | ||
|
|
||
| // This is where we'll eventually look at GroupItemPropertyName | ||
| var groupItemsList = TemplatedItemSourceFactory.Create(group as IEnumerable, _itemTemplate, _container, mauiContext: _mauiContext); | ||
| object groupItemsList; | ||
| if (_itemTemplate is not null) | ||
| { | ||
| groupItemsList = TemplatedItemSourceFactory.Create(group as IEnumerable, _itemTemplate, _container, mauiContext: _mauiContext); | ||
| } | ||
| else | ||
| { | ||
| // When no ItemTemplate is set, copy the raw group items into a new list so that: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [major] CollectionView Shared Models - Copying grouped items into a new |
||
| // WinUI calls ToString() on the actual data objects (not on ItemTemplateContext wrappers). | ||
| var rawItems = new List<object>(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [major] CollectionView Shared Models — Copying the group into a plain
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [major] CollectionView Windows Copying no-template group items into a new
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [major] CollectionView Shared Models - Copying no-template group contents into a new
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
❌ [major] CollectionView Windows regression — Copying the group into a detached |
||
| var groupEnumerable = group as IEnumerable; | ||
| if (groupEnumerable is not null) | ||
| { | ||
| foreach (var item in groupEnumerable) | ||
| { | ||
| rawItems.Add(item); | ||
| } | ||
| } | ||
|
|
||
| groupItemsList = rawItems; | ||
| } | ||
|
|
||
| return new GroupTemplateContext(groupHeaderTemplateContext, groupFooterTemplateContext, groupItemsList); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| namespace Controls.TestCases.HostApp.Issues; | ||
|
|
||
| [Issue(IssueTracker.Github, 23293, "Grouping collection view without data template results in displaying the default string representation of the object", PlatformAffected.UWP)] | ||
| public class Issue23293 : ContentPage | ||
| { | ||
| public List<Issue23293AnimalGroup> Animals { get; set; } = new List<Issue23293AnimalGroup>(); | ||
|
|
||
| public Issue23293() | ||
| { | ||
| CreateAnimalsCollection(); | ||
|
|
||
| CollectionView collectionViewWithoutDataTemplate = new CollectionView | ||
| { | ||
| AutomationId = "CollectionViewWithoutDataTemplate", | ||
| ItemsSource = Animals, | ||
| IsGrouped = true, | ||
| }; | ||
|
|
||
| CollectionView collectionViewWithGroupTemplates = new CollectionView | ||
| { | ||
| AutomationId = "CollectionViewWithGroupTemplates", | ||
| ItemsSource = Animals, | ||
| IsGrouped = true, | ||
| GroupHeaderTemplate = new DataTemplate(() => | ||
| { | ||
| Label label = new Label { FontSize = 14, FontAttributes = FontAttributes.Bold, BackgroundColor = Colors.LightGray }; | ||
| label.SetBinding(Label.TextProperty, "Name"); | ||
| return label; | ||
| }), | ||
| GroupFooterTemplate = new DataTemplate(() => | ||
| { | ||
| Label label = new Label { FontSize = 12, BackgroundColor = Colors.LightBlue, AutomationId = "GroupFooterLabel" }; | ||
| label.SetBinding(Label.TextProperty, "Name", stringFormat: "End of {0}"); | ||
| return label; | ||
| }), | ||
| }; | ||
|
|
||
| Grid grid = new Grid | ||
| { | ||
| Padding = 10, | ||
| }; | ||
|
|
||
| grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); | ||
| grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Star }); | ||
| grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); | ||
| grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Star }); | ||
|
|
||
| grid.Add(new Label { Text = "No ItemTemplate - items should show animal names", FontSize = 12, FontAttributes = FontAttributes.Bold }, 0, 0); | ||
| grid.Add(collectionViewWithoutDataTemplate, 0, 1); | ||
| grid.Add(new Label { Text = "No ItemTemplate with Header/Footer - header shows group name, items show animal name, footer shows 'End of Bears'", FontSize = 12, FontAttributes = FontAttributes.Bold }, 0, 2); | ||
| grid.Add(collectionViewWithGroupTemplates, 0, 3); | ||
|
|
||
| Content = grid; | ||
| } | ||
|
|
||
| void CreateAnimalsCollection() | ||
| { | ||
| Animals.Add(new Issue23293AnimalGroup("Bears", new List<string> | ||
| { | ||
| "American Black Bear", | ||
| })); | ||
| } | ||
| } | ||
|
|
||
| public class Issue23293AnimalGroup : List<string> | ||
| { | ||
| public string Name { get; set; } | ||
|
|
||
| public Issue23293AnimalGroup(string name, List<string> animals) : base(animals) | ||
| { | ||
| Name = name; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| using NUnit.Framework; | ||
| using UITest.Appium; | ||
| using UITest.Core; | ||
|
|
||
| namespace Microsoft.Maui.TestCases.Tests.Issues; | ||
|
|
||
| public class Issue23293 : _IssuesUITest | ||
| { | ||
| public Issue23293(TestDevice device) : base(device) { } | ||
|
|
||
| public override string Issue => "Grouping collection view without data template results in displaying the default string representation of the object"; | ||
|
|
||
| [Test] | ||
| [Category(UITestCategories.CollectionView)] | ||
| public void GroupedCollectionViewWithoutItemTemplateRendersCorrectly() | ||
| { | ||
| App.WaitForElement("GroupFooterLabel"); | ||
| VerifyScreenshot(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jsuarezruiz , I have committed the snapshots.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [moderate] Regression Prevention and Test Coverage - This issue is Windows-specific and the test calls
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [major] Regression Prevention This new test calls
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [major] Regression Prevention and Test Coverage - This shared UI test calls
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
❌ [major] Regression test coverage — This Windows-specific screenshot test calls |
||
| } | ||
| } | ||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[major] Handler Mapper and Property Patterns - The new
ItemTemplateSelectorstate is only recalculated fromUpdateItemTemplate(), but the condition also depends onIsGroupedandGroupFooterTemplate, whose mapper path callsUpdateItemsSource()instead. Concrete scenario: start grouped with no footer, then setGroupFooterTemplateat runtime; the grouped source is rebuilt withGroupFooterItemTemplateContextitems, but the selector remains null so Windows still renders the footer context type name. The inverse also leaves a stale selector after clearing the footer or toggling grouping. Refresh this selector from the mapper paths for all dependent properties, or centralize the selector update inUpdateItemsSource()/a shared helper.