Skip to content

Add multi-level column grouping support (Windows only) - #425

Merged
w-ahmad merged 16 commits into
mainfrom
feat/grouping
Aug 26, 2026
Merged

w-ahmad merged 16 commits into
mainfrom
feat/grouping

Conversation

@w-ahmad

@w-ahmad w-ahmad commented Aug 20, 2026 •

Copy link
Copy Markdown
Owner

Description

Adds Excel-like multi-level column grouping to TableView on Windows targets. Rows can be grouped by one or more column values via each column header's options flyout (or programmatically via GroupDescriptions), with per-group expand/collapse, group headers showing key + item count, indentation for nested levels, and full integration with existing column sorting.

Highlights:

  • GroupDescriptions / CollectionGroups on CollectionView, with ColumnGroupDescription driving header-based grouping
  • TableViewGroupHeaderRow renders group headers with expand/collapse and per-level indentation
  • New TableView APIs: CanGroupColumns, DefaultGroupState, DefaultGroupStyle, ShowGroupExpandCollapseButton, AreStickyGroupHeadersEnabled, IsGrouped, UngroupAll(), and a Grouping event
  • Per-column CanGroup, plus a "Group"/"Ungroup" option in each column header's options flyout
  • Grouping and sorting are unified for a grouped column — grouping drives order and shows a sort indicator; see the docs for the three-state → two-state cycling behavior once a column is grouped
  • New "Grouping" sample page in the sample app
  • New tests: CollectionView grouping behavior (single/multi-level, state transitions and persistence, ordering) and TableView selection / row/cell access in grouped scenarios
  • New docs/docs/grouping.md article, plus updates across README, overview, sorting, performance, accessibility, AOT-compatibility, migration guides (WCT/WPF), feature comparison, and the Uno-platform doc to reflect the new feature

Related Issue

Closes #69

Type of Change

  • ✨ New feature
  • 📝 Documentation update
  • 🧪 Test

Checklist

  • This PR is not from my main branch
  • Tested with WinUI target
  • Tested with Uno Platform target
  • Unit / integration tests added or updated
  • Documentation updated to reflect changes
  • Code follows the project's coding conventions

Screenshots / Recordings

Recording.2026-08-20.183028.mp4

Additional Notes

Grouping is Windows-only for now. Non-Windows Uno Platform targets compile out the grouping code paths entirely rather than exposing a degraded/no-op experience in the UI beyond the flyout option being hidden.

Introduces Excel-like grouping to TableView for WinUI 3 (Windows only):
- Adds GroupDescriptions, CollectionGroups, and group state APIs/events.
- Implements TableViewGroupHeaderRow with expand/collapse and indentation.
- Adds CanGroup to TableViewColumn and UI commands for grouping/ungrouping.
- Updates CollectionView for grouped/filtered/sorted views and state changes.
- Updates XAML styles, row alignment, and selection logic for grouping.
- All grouping features are conditionally compiled for Windows only.
Expanded CollectionViewTests.cs with comprehensive UI tests for grouping behaviors, including single/multi-level grouping, group state transitions, persistence, and group ordering. Added TableViewGroupingSelectionTests.cs to test TableView selection and row/cell access in grouped and ungrouped scenarios, covering selection, group expand/collapse, ScrollRowIntoView, and cell retrieval after group changes. Includes helper methods for test data and group state manipulation.
Added detailed docs for new multi-level column grouping, including a dedicated grouping.md article. Updated README, overview, and feature comparison to highlight grouping (Windows-only) and linked to new docs. Clarified grouping's Windows-only status in Uno-related and performance docs. Expanded API/event reference for Grouping event and TableViewGroupingEventArgs. Updated migration guides to map grouping APIs and note differences from WPF/WCT. Added grouping notes to accessibility, AOT, and performance docs. Updated toc.yml to include grouping article.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Excel-like multi-level grouping for TableView on Windows targets by extending the internal CollectionView with GroupDescriptions/CollectionGroups, rendering group header rows with expand/collapse + indentation, and integrating grouping with the existing sorting UI/behavior. This PR also adds a new sample page and broad documentation coverage for the new feature set.

Changes:

  • Add Windows-only grouping pipeline in CollectionView (group descriptions, flattened nested groups, persisted expand/collapse state) and wire it into TableView + column header commands.
  • Introduce group header row visuals/styles and row indentation to align grouped headers with columns/cells.
  • Add tests, sample page, and documentation updates describing grouping APIs, behavior, and limitations.

Reviewed changes

Copilot reviewed 49 out of 49 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
tests/TableViewGroupingSelectionTests.cs New UI tests for selection/current-cell/scrolling behavior under grouping and collapse/expand.
tests/CollectionViewTests.cs New UI tests validating single/multi-level grouping, ordering, state persistence, and transitions.
src/Themes/TableViewRowPresenter.xaml Adds an indent placeholder and adjusts vertical gridline alignment to support grouped indentation.
src/Themes/TableViewHeaderRow.xaml Adds an “Ungroup all” menu item to the header options flyout.
src/Themes/TableViewGroupHeaderRow.xaml New default template + style resources for group header rows and default GroupStyle.
src/Themes/TableViewColumnHeader.xaml Adds a “Group/Ungroup” menu option to the per-column options flyout.
src/Themes/TableView.xaml Merges group header resources and sets a default DefaultGroupStyle in the default TableView style.
src/Themes/Resources.xaml Adds theme resources for group header row look-and-feel (background/foreground/margins/font size).
src/Themes/Generic.xaml Merges group header row resources and adds default style mapping for TableViewGroupHeaderRow.
src/TableViewRowPresenter.cs Implements per-row cell indentation based on grouping depth.
src/TableViewHeaderRow.OptionComamnds.cs Adds an “Ungroup all” command wiring for the header flyout.
src/TableViewHeaderRow.cs Prevents select-all checkbox recursion by guarding programmatic IsChecked updates.
src/TableViewGroupState.cs New enum defining default group expanded/collapsed state.
src/TableViewGroupHeaderRow.cs New group header container with expand/collapse toggle and gridline integration (Windows-only).
src/TableViewColumnHeader.OptionComamnds.cs Wires up per-column “Group/Ungroup” command and adjusts “Clear sorting” availability for grouped columns.
src/TableViewColumnHeader.cs Implements group/ungroup behavior, grouping-driven sort toggling, and two-state vs three-state sort cycling when grouped.
src/TableView.Properties.cs Adds new grouping-related dependency properties and APIs (Windows-only).
src/TableView.Events.cs Adds TableView.Grouping event (Windows-only).
src/TableView.cs Hooks grouping-related CollectionView events, adds UngroupAll(), updates navigation math to use visible Items.Count, and refreshes row indent on grouping resets.
src/ItemsSource/TableViewGroupInfo.cs New bindable DependencyObject carrying group key/count/level/expanded state and notifying CollectionView on expand/collapse.
src/ItemsSource/SortDescription.cs Makes Direction settable and adds a default direction value.
src/ItemsSource/GroupDescription.cs New type deriving from SortDescription to represent grouping operations.
src/ItemsSource/ColumnSortDescription.cs Adds default sort direction parameter.
src/ItemsSource/ColumnGroupDescription.cs New group description tied to a TableViewColumn (bound property vs rendered content behavior).
src/ItemsSource/CollectionViewGroup.cs New ICollectionViewGroup implementation for flattened group headers (Windows-only).
src/ItemsSource/CollectionView.Properties.cs Adds GroupDescriptions, DefaultGroupState, and a settable CollectionGroups (Windows-only).
src/ItemsSource/CollectionView.Events.cs Adds PropertyChanging event support for grouping-related rebinding scenarios.
src/ItemsSource/CollectionView.cs Implements grouping build/rebuild logic, flattened nested groups, and expanded-state persistence (Windows-only).
src/EventArgs/TableViewGroupingEventArgs.cs New handleable event args for the grouping event.
src/Columns/TableViewColumn.cs Adds CanGroup DP to allow per-column grouping enablement.
src/Collections/ObservableVector.cs New IObservableVector<T> implementation used by grouping and group item vectors.
samples/WinUI.TableView.SampleApp/Pages/GroupingPage.xaml.cs New sample page code-behind to exercise grouping with add/insert/move/delete operations.
samples/WinUI.TableView.SampleApp/Pages/GroupingPage.xaml New sample page UI showcasing grouping and grouping-related options.
samples/WinUI.TableView.SampleApp/MainWindow.xaml.cs Adds navigation routing entry for the new Grouping sample page.
samples/WinUI.TableView.SampleApp/MainWindow.xaml Adds “Grouping” nav item to the sample app UI.
README.md Adds grouping to the feature list and documentation links.
docs/index.md Adds grouping to the docs landing page feature matrix.
docs/docs/toc.yml Adds grouping to the docs TOC.
docs/docs/sorting.md Documents sorting behavior changes when a column is grouped.
docs/docs/performance.md Documents grouping performance characteristics and tradeoffs.
docs/docs/overview.md Adds high-level overview section for grouping and platform availability.
docs/docs/migration-wpf.md Updates migration guide with new grouping-related APIs/events.
docs/docs/migration-wct.md Updates migration guide with new grouping-related APIs/events and guidance.
docs/docs/grouping.md New primary documentation page for grouping usage and APIs.
docs/docs/getting-started-with-uno.md Documents grouping as Windows-only for Uno targets.
docs/docs/datagrid-feature-comparison.md Updates feature comparison to include grouping support details/limitations.
docs/docs/commands-events.md Adds grouping event/type docs and TableViewGroupState reference.
docs/docs/aot-compatibility.md Notes grouping’s reflection path implications for AOT (similar to sorting).
docs/docs/accessibility.md Adds notes about group header accessibility/automation behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/ItemsSource/ColumnGroupDescription.cs
Comment thread src/ItemsSource/CollectionView.cs Outdated
Comment thread src/TableView.cs
Comment thread src/TableView.cs
Comment thread src/TableViewColumnHeader.cs
Comment thread src/Themes/TableViewColumnHeader.xaml
Comment thread src/Themes/TableViewHeaderRow.xaml
Comment thread src/TableView.Properties.cs
Comment thread src/TableViewHeaderRow.OptionComamnds.cs
@w-ahmad w-ahmad mentioned this pull request Aug 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Programmatic grouping is inaccessible externally, live regrouping misses property changes, and group-header accessibility and command-state issues remain.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (10)

Previously missed (8) — in code that hasn't changed since the last review.

src/ItemsSource/CollectionView.cs:338

  • Live shaping only rebuilds groups when the changed property is also in SortDescriptions. A column grouped without a prior independent sort has no sort description, so editing its grouping property leaves the item under the old group and leaves counts stale. Check relevant GroupDescriptions when no matching sort description handled the change.
            if (GroupDescriptions.Count > 0)
            {
                HandleGroupChanged();
                return;
            }

src/TableViewGroupHeaderRow.cs:96

  • Opacity and IsHitTestVisible only hide this from pointer users; the invisible button remains in keyboard tab order and Space/Enter can still collapse the group. Disable keyboard focus and invocation as well while preserving the layout slot.
    src/Themes/TableViewGroupHeaderRow.xaml:68
  • This glyph-only Button has no accessible action name and does not expose the current expanded/collapsed state, so Narrator users cannot identify what it does or the group's state. Use an appropriately bound toggle/expand-collapse pattern and localized automation text.
    src/TableViewColumnHeader.OptionComamnds.cs:54
  • Once a column is grouped, setting either grouping permission to false disables its Ungroup command, and Group() also rejects the call before reaching the ungroup branch. Permissions should prevent adding a group, not prevent removing an existing one; allow execution when IsGrouped and move the CanGroup guard after ungrouping.
    src/TableView.cs:34
  • Correct the typo in this field name and all references.
    docs/docs/grouping.md:5
  • On non-Windows targets CanGroupColumns is compiled out, so using it does not produce a no-op; it fails to compile. State that the property is unavailable and that only the header option is omitted.

This issue also appears on line 47 of the same file.

> **Note:** Grouping is currently a **Windows-only** feature. It is not available when running on non-Windows Uno Platform targets (macOS, Linux, WASM, iOS, Android). `CanGroupColumns` and the "Group" column header option are hidden/no-ops on those targets.

docs/docs/getting-started-with-uno.md:161

  • CanGroupColumns is absent from non-Windows builds rather than being a property that has no effect, so the current guidance would lead Uno callers to a compile error.
- **Grouping is not available.** [Column grouping](grouping.md) is a Windows-only feature; `CanGroupColumns` and the column header's "Group" option have no effect on non-Windows Uno targets.

README.md:170

  • Remove the unnecessary article in this sentence.
- **Sorting, Grouping, and Filtering**: Enable sorting, grouping, and filtering on specific columns or for the all columns.

src/TableViewColumnHeader.cs:129

  • This ungroup path returns before raising Grouping, while the new commands/events and migration documentation says that event fires before both group and ungroup actions. Either invoke the handleable event before this branch or document that ungrouping is not interceptable.
    docs/docs/grouping.md:47
  • Clearing GroupDescriptions directly does not have the same effect as UngroupAll(): it leaves a header-created column's companion SortDescription and SortDirection intact. Document that distinction so callers do not expect the sort indicator and ordering to be reset.
Or clear the `GroupDescriptions` collection directly - both have the same effect, and both reset the sort indicator on any column that was driving a group.
  • Files reviewed: 61/61 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread src/ItemsSource/CollectionView.cs
Comment thread src/Columns/TableViewColumn.cs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Sorting-state duplication, stale live grouping, documentation discrepancies, and group-header accessibility issues remain unresolved.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (7)

Previously missed (7) — in code that hasn't changed since the last review.

src/TableViewGroupHeaderRow.cs:58

  • Indentation is only assigned in OnContentChanged, but a container's content can be prepared before its template, when _indentPlaceholder is still null. OnApplyTemplate restores the visual state but not the indent, leaving nested headers aligned at level zero. Apply groupInfo.Level * GroupIndentSize here after resolving the template child.
    src/TableViewGroupHeaderRow.cs:96
  • Opacity and hit testing do not remove a button from keyboard navigation, so setting ShowGroupExpandCollapseButton="False" leaves an invisible focusable control that can still be activated with Space/Enter. Disable keyboard focus and the control while hidden, preserving its layout slot as intended.
    docs/docs/grouping.md:44
  • Clearing GroupDescriptions directly does not have the same effect as UngroupAll(): it does not clear grouped columns' SortDirection and can leave the associated ColumnGroupDescription in SortDescriptions. Document that callers should use UngroupAll() when they also want the sort state reset.
Or clear the `GroupDescriptions` collection directly - both have the same effect, and both reset the sort indicator on any column that was driving a group.

src/ItemsSource/CollectionView.cs:605

  • Each item restored after a filter change calls HandleItemAdded, which performs a full HandleGroupChanged() rebuild when grouping is active. Removing a restrictive filter can therefore regroup the entire growing collection once per restored item (quadratic work and repeated UI resets). Batch these mutations and rebuild groups once after the loop.
            if (HandleItemAdded(i, item, sourceIndex))
            {
                sourceIndex++;

src/Themes/TableViewGroupHeaderRow.xaml:68

  • This icon-only button has no accessible name, so assistive-technology users cannot determine its purpose. Provide a localized automation name (and ideally expose its expanded/collapsed state through an appropriate automation pattern).
    docs/docs/grouping.md:5
  • CanGroupColumns is compiled out under #if WINDOWS; it is not a hidden/no-op property on non-Windows targets, and shared code that references it will fail to compile. State that the API is unavailable there (while the per-column CanGroup property remains present but has no grouping effect).
> **Note:** Grouping is currently a **Windows-only** feature. It is not available when running on non-Windows Uno Platform targets (macOS, Linux, WASM, iOS, Android). `CanGroupColumns` and the "Group" column header option are hidden/no-ops on those targets.

docs/docs/getting-started-with-uno.md:161

  • CanGroupColumns is not present on non-Windows builds because its declaration is guarded by #if WINDOWS, so describing it as having “no effect” incorrectly suggests cross-target code can still access it. Clarify that this API is unavailable and must be conditionally referenced.
- **Grouping is not available.** [Column grouping](grouping.md) is a Windows-only feature; `CanGroupColumns` and the column header's "Group" option have no effect on non-Windows Uno targets.
  • Files reviewed: 63/63 changed files
  • Comments generated: 3
  • Review effort level: Balanced

Comment thread src/TableViewColumnHeader.cs
Comment thread src/TableViewColumnHeader.cs Outdated
Comment thread src/ItemsSource/CollectionView.cs
@w-ahmad
w-ahmad merged commit 389b9d7 into main Aug 26, 2026
11 checks passed
@w-ahmad
w-ahmad deleted the feat/grouping branch August 26, 2026 10:47
TekuSP added a commit to Luxonit-GmbH/WinUI.TableView that referenced this pull request Sep 3, 2026
…vergence

OnCurrentCellChanged runs inside CurrentCellSlot's property-changed
callback, synchronously within that SetValue. Driving ScrollIntoView
and layout re-entrantly from there can fail natively, where .NET
cannot catch it. Yield first so the SetValue unwinds (lifted from
upstream be2aa5d). Placed after the generation is captured, it also
collapses a held-arrow-key storm earlier than the existing guard did:
every queued navigation yields here and all but the latest bail before
touching the scroll machinery.

Also documents the fork's divergence from upstream: upstream's grouping
(PR w-ahmad#425) is the CollectionView's grouping and cannot run on the
direct-binding path, so it is excluded permanently and ours stays. No
further full merges; fixes by cherry-pick, features by reimplementation.
The standing rejects and the procedure are written down.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Data grouping

2 participants