mirror of
https://github.com/microsoft/microsoft-ui-xaml.git
synced 2026-10-28 16:35:19 +08:00
42b575bc75edcdec7d0225aa2c0af9a67427edd4
41
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
42b575bc75 |
Merge main into user/sahjaiswal/tableview-tip-followup
Brings in #12307 (TableView: templated column headers, UIA resize, sort fixes, recycle perf, theming) and 11 other commits from main. Conflicts, resolved by keeping both sides: - TableView_Editing.cpp (BeginEdit): the TiP try/catch now wraps #12307's unwind-on-throw guard and ResetPendingBeginEdit. - TableView_Sort.cpp: the typed Sorted rethrow and failure capture keep #12307's explicitClear, keptSourceSort and reconcile changes, and the comparer tally reset follows its early return for a source another table owns. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 53d996ee-1cb6-468a-bfc9-e11af03b1f80 |
||
|
|
d5132fd2e4 |
TableView: templated column headers, UIA resize, sort fixes, recycle perf, theming (#12307)
## Fixes | Internal issue | Type | Change | |---|---|---| | https://task.ms/64475625 | Bug | Column headers are a templated, restylable `TableViewColumnHeader` with visual states | | https://task.ms/64475623 | Bug | Header typography comes from the header Style, not C++ local values | | https://task.ms/64143451 | Bug | Header column separators are always drawn (no new property); hide them by overriding `TabularSurfaceHeaderSeparatorBrush` | | https://task.ms/63996584 | Task | Column headers expose the UIA Transform pattern so UIA clients can resize columns | | https://task.ms/64475611 | Bug | Outer border uses new `TabularSurfaceOuterBorderBrush` (`CardStrokeColorDefault`); grid-line values were in #12349 | | https://task.ms/64475629 | Bug | Default group header uses theme tokens (SemiBold key style, 8px spacing); the item count inherits the header `Foreground` | | https://task.ms/64652961 | Task | Row recycling no longer regenerates cells or refreshes row chrome twice (scroll performance) | | https://task.ms/63790855 | Task | Sort: `CanSort` is enforced and `SortByColumn(column, None)` always clears; a sort through a source another table owns returns false; no spurious `Sorted`; an app-owned sort survives a `CanSort`/`CanUserSortColumns` opt-out | | https://task.ms/63787021 | Task | Cell automation-peer constructor validates its arguments; `BeginEdit` rolls back when it throws | Not changed: https://task.ms/64512732 (by design: Auto columns follow content width), https://task.ms/64475627 (design decision: keep chevron sort glyphs), https://task.ms/64513223 (investigation only; fix deferred). ## PR Type - [x] Bugfix - [x] Feature - [ ] Code style update (formatting, renaming) - [ ] Refactoring (no functional changes, no API changes) - [ ] Build related changes - [x] Documentation content changes - [ ] Other (please describe): ## Description Follow-up to #12349 (merged) and #12285. Column headers move from code-built Grids to a new templated `TableViewColumnHeader`, restylable through `TableView.ColumnHeaderStyle` or `TableViewColumn.HeaderStyle`. Header peers gain the UIA Transform pattern for column resize. Sort behavior is tightened around `CanSort`, opt-outs and shared `TableViewSource` ownership. Row recycling reuses cells. Outer border, header lines and the default group header follow Fluent tokens. Review-driven fixes included here: - The header Style's `TargetType` `TypeName` is kept alive while it is compared; a Style with only `BasedOn` typed is applied, and its setters are read before applying it seals the Style. Incompatible, untyped or cyclic `BasedOn` Styles are ignored with a debug diagnostic. - A header disabled by its Style reports `IsEnabled=false` and is not invokable through UIA. - UIA `Transform.Resize` converts physical pixels through the table's full visual scale (not only `RasterizationScale`), and a width change below the drag jitter deadband is written once, exactly. - Disabling a header in its High Contrast hover/pressed state keeps the disabled foreground (state order fixed); the disabled sort glyph follows the text brush across theme changes. - A `CanSort` opt-out made inside `BeginningEdit` is replayed after the edit it let open closes, and rebuilds headers even when a `Sorted` handler throws. - Header rebuilds caused by `Content`/`Header` or `Columns` changes keep focus in the header band, respect an app's own focus move, and release `UIElement` headers back to the app. - A disabled group header does not dim its item count twice. The count opacity follows `ContentTemplate` changes while the header is disabled. <details><summary>Product changes by file</summary> | File | +/- | What changed | Items | |---|---|---|---| | `TableView/TableView.cpp` (+ .h) | +684 / −382 | `RebuildHeaders` creates `TableViewColumnHeader` controls; applies `ColumnHeaderStyle`/`HeaderStyle` with TargetType/BasedOn checks; density MinHeight/Padding only when the Style doesn't set them<br>`ReleaseHeaderCells` and header-focus handling for removed/replaced columns, re-entrant rebuilds and app focus moves<br>Header bottom rule thickness set on `PART_HeaderRow`; header lines no longer follow `GridLinesVisibility`<br>Row prepare/clear refresh chrome once; `TryResizeColumnFromAutomation` writes small widths exactly | 64475625, 64475623, 64143451, 64652961, 63996584, 63790855 | | `TableView/TableViewColumnHeader.cpp` (+ .h; replaces `TableViewHeaderCell.h`) | +435 / −210 | New sealed templated `ContentControl` with read-only `Column`<br>Resolves `PART_ContentPresenter`, `PART_GridLine`, `PART_SortIndicatorHost`, `PART_ResizeGripperHost`; hosts the sort glyph and gripper<br>Drives `CommonStates`/`HighContrastStates`/`SortStates`; disabled sort glyph bound to the text brush | 64475625, 64475623 | | `TableView/TableViewRow.cpp` (+ .h) | +191 / −35 | Recycled rows keep their cells unless cell automation peers exist; chrome refreshed once; tooltips re-applied on recycle<br>`BeginCellEdit` restores display element, edit state and focus on throw | 64652961, 63787021 | | `TableView/TableView.xaml` | +128 / −18 | Default `TableViewColumnHeader` Style/template: typography setters, visual states, parts<br>Outer border uses `TabularSurfaceOuterBorderBrush`; header lines use `TabularSurfaceHeaderSeparatorBrush`<br>Group header: `TableViewGroupExpanderSpacing` (8), padding 8,8,12,8, key/count text styles, count inherits `Foreground` | 64475625, 64475623, 64475611, 64475629 | | `TableView/TableViewColumnHeaderAutomationPeer.cpp` (+ .h) | +127 / −9 | `ITransformProvider`: `Resize` converts physical px through the visual scale; `CanResize` tracks gripper availability (pinned → false)<br>`IsEnabled` follows the header; name read from the content presenter | 63996584, 64475625 | | `TableView/TableView_Sort.cpp` | +113 / −39 | `SortByColumn(None)` bypasses the `CanSort` gate; other sorts refused<br>Sort through a source owned by another table returns false; implicit clears keep the app's sort and raise no `Sorted`; app-lit columns left alone on opt-out | 63790855 | | `TableView/TableView_Layout.cpp` | +74 / −14 | Realized-row walk only when a visible Auto column exists<br>Transform `CanResize` changes raised for existing header peers | 64652961, 63996584 | | `TableView/TableView_Editing.cpp` | +63 / −15 | `BeginEdit` unwinds on throw; `CanSort` opt-out during `BeginningEdit` queued until the edit closes | 63787021, 63790855 | | `TableView/TableViewCellAutomationPeer.cpp` (+ .h, `TableViewCell.h`) | +48 / −0 | Public constructor throws `E_INVALIDARG` for null args, negative/out-of-range index or a foreign column; internal path uses `TrustedOwnerTag` | 63787021 | | `TableView/TableViewGroupHeader.cpp` (+ .h) | +39 / −0 | Default count's opacity is not applied twice when disabled | 64475629 | | `TableView/TableView.idl` | +31 / −13 | `TableViewColumnHeader`, `TableView.ColumnHeaderStyle`, `TableViewColumn.HeaderStyle`<br>`CanSort`/`SortByColumn`/`ToggleSortDirection`/`GridLinesVisibility` docs; cell-peer `E_INVALIDARG`; group-header parts | 64475625, 63790855, 64143451, 63787021 | | `TableView/TableView_Columns.cpp` | +19 / −0 | Releases removed columns' header cells; old `UIElement` `Header` returned to the app on change | 64475625 | | `TableView/TableViewColumn.cpp` | +7 / −0 | `HeaderStyle` change queues a header rebuild | 64475625 | | `TableView/TableViewSource.h` | +6 / −0 | `IsOwnedByOther` helper for sort sync | 63790855 | | `CommonStyles/TabularSurfaces_themeresources.xaml` | +5 / −2 | New `TabularSurfaceOuterBorderBrush` (Dark/Light/HC); group count opacity documented as secondary-over-primary text (0.77 Dark) | 64475611, 64475629 | | `TableView/TableView.vcxitems` | +2 / −1 | Swap `TableViewHeaderCell.h` for `TableViewColumnHeader.*` | 64475625 | | Generated / metadata (`Generated/TableView.properties.*`, `Generated/TableViewColumn.properties.*`, `dll-tabular/XamlMetadataProviderGenerated.h`) | +123 / −0 | Codegen for the new properties and type | 64475625 | **Total: 17 rows, 29 product files, +2117 / −738.** Also: 19 test files (+3718 / −32), 3 docs (+161 / −35), 6 sample files (+2 / −9). </details> ### New preview API (needs API review) - **`TableViewColumnHeader`**: sealed, templated `ContentControl`, created only by `TableView`. - Read-only `Column` property. - Template parts (optional): `PART_ContentPresenter`, `PART_GridLine`, `PART_SortIndicatorHost`, `PART_ResizeGripperHost`. - Visual state groups: `CommonStates`, `HighContrastStates`, `SortStates`. - **`TableView.ColumnHeaderStyle`**: Style applied to every column header. - **`TableViewColumn.HeaderStyle`**: per-column Style; wins over `TableView.ColumnHeaderStyle`. There is no `HeaderGridLinesVisibility` property. `GridLinesVisibility` affects body lines only; header lines are always drawn. To hide them, override `TabularSurfaceHeaderSeparatorBrush` with `Transparent`. ### Current Behavior - Headers are built in C++ with no template, no visual states and forced local typography, so apps can't restyle them. Header separators don't show. - Header peers report `HeaderItem` without Transform, so UIA clients can't resize columns. - `SortByColumn` ignores `CanSort`; `CanSort=false` on a sorted column leaves the sort in place; sorting through a source another table owns reports success; some implicit clears raise `Sorted(None)` while the app's sort remains. - The cell peer constructor accepts invalid arguments; a throw in `BeginEdit` leaves edit state half-open. - Recycling a row rebuilt every cell, refreshed chrome up to twice and invalidated the table's measure. - The outer border has no dedicated brush; the default group header uses raw weights and off-grid spacing. ### New Behavior - Headers use the templated `TableViewColumnHeader` (see API above); typography, padding and brushes come from Style setters. - Header peers implement `ITransformProvider` through the existing resize path (Min/Max clamping, scale conversion; pinned columns report `CanResize=false`). - `CanSort=false` blocks header clicks, `SortByColumn` (except `None`) and `ToggleSortDirection`, and clears the column's own active sort; sorts through a source owned by another table return false; `Sorted` is raised only when the table's sort state changes. - Cell peer constructor throws `E_INVALIDARG` on bad input; `BeginEdit` rolls back on failure. - Recycled rows reuse their cells unless cell automation peers exist; chrome refreshes once. > **Visible changes** > - Outer border uses `CardStrokeColorDefault`. > - Header lines are always shown, even when `GridLinesVisibility` is `None` or `Horizontal`. > - Group header restyled: 8px spacing; the count follows the header `Foreground`. > - Disabled header text and sort glyph dim. > - TableView samples no longer opt out of High Contrast backplates app-wide. ## Customer Impact Restylable column headers, visible header separators, Fluent-aligned outer border and group headers, UIA column resize for assistive technology, smoother scrolling and predictable sort events. ## Regression Potential - [ ] Low risk — isolated change, limited scope - [x] Medium risk — touches shared components or public APIs - [ ] High risk — architectural or breaking API change Header construction (focus restore, frozen columns, UIA peer, tooltips, resize, sort glyph) moved to a templated control. Sort event semantics are tighter: `CanSort` is enforced on programmatic sorts and fewer `Sorted` events fire. Outer border color and always-on header lines change visibly. Covered by new and existing tests. ## How Has This Been Tested? - [x] I have performed a self-review of my own code - [x] I have added tests to cover my changes - [x] Existing tests pass locally Full TableView API and interaction suites on test VMs: 877/882 pass at 224d10d5c; 3 Localization tests are [Ignore] and 2 intermittent failures passed 3/3 on rerun. Product, test apps and TableView sample apps build (x64 chk). Also covered: `VerifyDisabledGroupHeaderCountFollowsContentTemplateChanges`. Key new tests: - `VerifyColumnHeaderTemplateParts`, `VerifyColumnHeaderSortStates`, `VerifyColumnHeaderTypographyHasNoLocalValues` - `VerifyColumnHeaderStyleApplies`, `VerifyBasedOnOnlyColumnHeaderStyleApplies`, `VerifyIncompatibleColumnHeaderStyleIsIgnored` - `VerifyHeaderGridLinesShowWhateverTheBodyGridLines`, `VerifyTransparentHeaderSeparatorBrushHidesHeaderLines`, `VerifyGridLineAndOuterBorderBrushesMatchFluentStrokeTokens` - `VerifyDisablingHighContrastHoveredHeaderKeepsDisabledForeground`, `VerifyDefaultGroupHeaderCountInheritsForegroundThatFollowsStates` - `VerifyTransformResizeAccountsForAncestorScale`, `VerifyTransformResizeSmallWidthChangeWritesOnce`, `VerifyHeaderDisabledByStyleIsNotEnabledOrInvokable` - `VerifySortByColumnRespectsCanSort`, `VerifyImplicitClearKeepsSourceSort`, `VerifyUngatingKeepsAppLitColumnWithoutEvents`, `VerifySortOptOutDuringBeginningEditKeepsTheEditOpen` - `VerifyRecycledRowWithoutCellPeersReusesItsCells`, `VerifyHeaderFocusStaysInHeaderBandWhenColumnsReplaced`, `VerifyBeginEditThatThrowsRollsBackAndAllowsALaterEdit`, `VerifyCellPeerConstructorValidatesArguments` --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 94961bb8-d187-483c-bece-19e203752c67 |
||
|
|
927f474d48 |
TableView TiP: blame by exception type and count contained app faults
Review round 3. Attribution matched the last app HRESULT against a control-wide counter, so a nested callback could overwrite it and an unrelated exception with the same HRESULT could match. App code's exceptions are now tagged where TableView calls it (CallerError, and CallerOutOfMemory for an allocation failure, which stays a std::bad_alloc), and the catch that decides an outcome reads the type. SelectionModel calls TableView back across the ABI, which drops the C++ type, so the call into it tags the exception again by its error object. Column code is TableView's unless app code ran it: the editing hooks are internal, so only a CellEditingTemplate's content (while the editor is prepared) is the app's there, and only an app-derived column class owns its display factory and sort member path. Expansion is always tracked and its catch reads the tag; only a request that changes one group or node runs a test. A filter over a large source no longer drops into the small bucket: the large flag counts the source before the filter, and the metrics and TableView_Instance carry both counts. Initialize closes as unloaded_before_render when the table is unloaded with rows and a body viewport before any row was laid out. An app filter predicate, sort or group key selector or custom comparer that throws is still contained, and now writes one recoverable TableView_Error per operation with FaultCount. Internal pipeline builds fail when Tabular falls back to the TiP stub. All versions start at 1, since none has shipped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 53d996ee-1cb6-468a-bfc9-e11af03b1f80 |
||
|
|
a7483e3aed |
Merge main into user/sahjaiswal/tableview-tip-followup
Brings in #12349 (TableView low-risk fixes) and 35 other commits from main. No conflicts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 53d996ee-1cb6-468a-bfc9-e11af03b1f80 |
||
|
|
83ebb939f7 |
TableView: low-risk fixes (keyboard group toggle, gripper cursor and visuals, Fluent grid lines, scroller lookup, samples) (#12349)
## Fixes | Internal issue | Type | Change | |---|---|---| | https://task.ms/64512114 | Bug | Toggling a focused group header with the keyboard no longer jumps the viewport to earlier groups | | https://task.ms/64475631 | Bug | A missing `TabularSurfaceGridLineBrush` is reported once per instance (debug diagnostic) | | https://task.ms/63787021 | Task (part) | Scroller ancestor lookup stops at the TableView | | https://task.ms/64475630 | Bug | Selection indicator seeded from accent fill tiers; unused `TabularSurfaceHeaderForegroundBrush` removed | | https://task.ms/63790855 | Task (part) | Sort and scroller-structure IDL docs match current behavior (comments only) | | https://task.ms/64475633 | Bug | Samples: remove the extra card frame around examples | | https://task.ms/64475637 | Bug | Samples: opt out of automatic high-contrast backplates | | https://task.ms/64491943 | Bug | Test only: row banding follows the element theme (app-side fix is theme-aware brushes) | | https://task.ms/64475632 | Bug | ResizeGripper template collapsed to one Border; overwritten row `MinHeight` setter removed | | https://task.ms/64475628 | Bug | ResizeGripper: no accent hover line; 1px neutral line only while dragging | | https://task.ms/64143434 | Bug | ResizeGripper shows the East-West resize cursor; mouse captured while dragging, released on Escape, capture loss, disable or unload | | https://task.ms/64475611 | Bug (part) | Grid lines use the `DividerStrokeColorDefault` values; the new outer-border key stays in #12307 | ## PR Type - [x] Bugfix - [ ] Feature - [ ] Code style update (formatting, renaming) - [ ] Refactoring (no functional changes, no API changes) - [ ] Build related changes - [x] Documentation content changes - [ ] Other (please describe): ## Description First of two PRs split out of #12307. This one carries only small, low-risk fixes with **no new API** (product: +158 / −72 lines; IDL changes are comment-only; samples +51 / −52). New API and the larger changes stay in #12307, which is stacked on this PR. | File | +/- | Change | |---|---|---| | `TableView/TableView_Keyboard.cpp` | +14 / −1 | `OnBodyBringIntoViewRequested` ignores the focus-rescue element (row or group header) only while a group-toggle focus restore is pending; with no body scroller, focus reveal goes through the app's ScrollViewer | | `TableView/TableView.cpp` (+ .h) | +11 / −4 | Missing grid-line brush logged once; `FindScrollViewerAncestor` stops at the TableView | | `ResizeGripper/ResizeGripper.cpp` (+ .h) | +97 / −21 | Resize cursor; mouse capture during drag, released on Escape, capture loss, disable and unload | | `ResizeGripper/ResizeGripper.xaml`, `ResizeGripper_themeresources.xaml` | +18 / −26 | Template collapsed to one Border; hover line removed; 1px neutral line only while dragging | | `CommonStyles/TabularSurfaces_themeresources.xaml` | +8 / −15 | Selection indicator uses `SystemAccentColorLight2`/`Dark1`; grid lines use `DividerStrokeColorDefault` values; unused header foreground key removed | | `TableView/TableView.xaml` | +0 / −2 | Overwritten row `MinHeight` setter removed | | `TableView/TableView.idl`, `TableViewSource.idl` | +10 / −3 | Doc comments only | ### Current Behavior - Toggling a focused group header near the bottom of a grouped list with the keyboard scrolls the viewport to unrelated earlier groups. - A missing grid-line brush is silently replaced; the scroller lookup can walk past the TableView into app content. - The selection indicator uses the raw accent seed color. - The column resize gripper shows no resize cursor and draws an accent line on hover. - Grid lines are darker than the Fluent divider stroke. - Samples wrap every example in an extra card and let the system draw high-contrast backplates over the control's own colors. ### New Behavior - The focused group header stays in view after a keyboard toggle. - In custom templates without a body ScrollViewer, keyboard focus reveals the focused row through the app's ScrollViewer. - A missing grid-line brush produces one debug diagnostic per instance; the scroller lookup is bounded by the TableView. - The selection indicator follows Fluent accent fill tiers (small visible color change). - The gripper shows the East-West resize cursor and holds it during a drag; no hover line; a 1px neutral line shows only while dragging. - Grid lines use the fainter Fluent divider values (visible change). - Samples show the control directly and keep its high-contrast colors. ## Customer Impact Keyboard group toggling keeps the user's place; column resizing shows the expected cursor and Fluent visuals; selection indicator and grid lines match Fluent tokens; cleaner samples. ## Regression Potential - [x] Low risk — isolated change, limited scope - [ ] Medium risk — touches shared components or public APIs - [ ] High risk — architectural or breaking API change The keyboard guard applies only while a group-toggle focus restore is pending. The removed theme key is a preview key that nothing reads. The gripper now captures the mouse during a drag; capture is released on Escape, capture loss, disable and unload. Grid lines use the Fluent divider values, a visible but intended change. ## How Has This Been Tested? - [x] I have performed a self-review of my own code - [x] I have added tests to cover my changes - [x] Existing tests pass locally Full TableView suites on test VMs: 639 API tests pass (3 Localization tests are `[Ignore]`) and 150/150 interaction tests pass. Product, test apps and TableView samples build (x64 chk). Four rounds of automated review (code, WinUI, regression, security) until no High+ findings. New tests: `VerifyFocusedBottomGroupToggleKeepsViewport`, `VerifyFocusedGroupToggleAmongCollapsedHeadersKeepsViewport`, `VerifyDroppedGroupToggleKeepsRowBringIntoView`, `VerifyScrollerLookupStopsAtTheTableView`, `VerifySelectionIndicatorBrushFollowsAccentFillTiers`, `VerifyAlternatingRowBackgroundFollowsElementThemeOppositeToApp`, `VerifyResizeGripperHoldsResizeCursor`, `VerifyDisablingGripperMidDragCancelsResize`, `VerifyEndDragOnUnloadedGripperRaisesDragCompleted`, `VerifyRemovingTableMidDragCancelsResize`, `VerifyResizeGripperSeparatorShowsOnlyWhileDragging`, `VerifyGridLineBrushesMatchFluentStrokeTokens`; interaction tests `GroupHeaderKeyboardToggleAtBottomKeepsViewport`, `PointerOverGripperShowsResizeCursor`, `PointerResizeEscapeReleasesCapture`, `KeyboardNavigationRevealsRowInAppScrollerWithoutBodyScroller`. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 94961bb8-d187-483c-bece-19e203752c67 |
||
|
|
f6c060aeb0 |
Merge main into user/sahjaiswal/tableview-tip-followup
Brings in #12306 (TableView context menus) and seven other commits from main. The only conflict was the TableView.vcxitems item lists; both sides' entries are kept. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 53d996ee-1cb6-468a-bfc9-e11af03b1f80 |
||
|
|
09cfc8ff3f |
TableView: context menu support (cell, header, row and table flyouts) (#12306)
## Fixes Brings the TableView context menu work (reviewed in #12299, merged into `user/dipesh/feature/tableview-experimental`) into main. ## PR Type - [ ] Bugfix - [x] Feature - [ ] Code style update (formatting, renaming) - [ ] Refactoring (no functional changes, no API changes) - [ ] Build related changes - [ ] Documentation content changes - [ ] Other (please describe): ## Description Adds context menu support to TableView for cells, column headers, rows, group headers and the table itself, from mouse, touch, keyboard (Shift+F10 / Menu key) and UI Automation. ### Current Behavior TableView has no per-column menus. A right-click only shows whatever `ContextFlyout` the framework finds on the hit element, keyboard and UIA requests have no defined target, and menus do not get the row's item as their DataContext. ### New Behavior - **New API (preview):** `TableViewColumn.CellContextFlyout` and `TableViewColumn.HeaderContextFlyout` (+ DP properties). - **Resolution, first match wins:** - Cell: a `ContextFlyout` inside the cell content → `CellContextFlyout` → the row's `ContextFlyout` → `TableView.ContextFlyout`. - Header: a `ContextFlyout` inside the `HeaderTemplate` → `HeaderContextFlyout` → `TableView.ContextFlyout`. - Rows and group headers get menus from implicit `TableViewRow` / `TableViewGroupHeader` styles; a group header's DataContext is its `TableViewGroupInfo`. - Menu items inherit the requested row's item as DataContext, so one shared menu works for every row. - Opening a menu focuses the target cell/row and never changes selection. Keyboard and UIA requests open at the focused cell. - No TableView menu opens while a cell is editing. Nested TableViews / items hosts and text controls' default flyouts are not picked up by the outer table. - Automation peers (row, cell, header, group header, table) implement `ShowContextMenu`; pooled/collapsed elements open nothing. - Editing: focus moving into a flyout the editor owns (attached flyout, button flyout, picker) keeps the edit open, including while focus rests on the flyout's `Popup`; focus in an unrelated popup commits. - Sample app: new **Context menus** page; README updated. - Tests: context menu API tests, interaction tests, editing focus tests, shared interaction test helpers. ## Customer Impact User-facing: apps can give TableView cells, headers and rows their own context menus with no code-behind wiring. Additive, preview API. ## Regression Potential - [ ] Low risk — isolated change, limited scope - [x] Medium risk — touches shared components or public APIs - [ ] High risk — architectural or breaking API change New public properties on `TableViewColumn`. Touches row/cell focus on context requests, editing focus-loss commit, and TableView automation peers. ## How Has This Been Tested? - [x] I have performed a self-review of my own code - [x] I have added tests to cover my changes - [x] Existing tests pass locally Builds (x64chk, 0 errors): - `.\initrun.ps1 .\build.cmd /q` — passed - `.\initrun.ps1 msb /q /m controls\test\MUXControlsTestApp\MUXControlsTestApp.csproj /p:Platform=x64 /p:Configuration=Debug` — passed - `.\initrun.ps1 msb /q /m controls\test\MUXControls.Test\MUXControls.Test.csproj /p:Platform=x64 /p:Configuration=Debug` — passed - `.\initrun.ps1 msb /q /restore Samples\TableViewSampleApp\TableViewSampleApp.csproj /p:Platform=x64 /p:WinUIVersion=3.0.0-dev` — passed TAEF on a test VM (115 tests: context menu API + interaction, editing API, hierarchy interaction), run in 9 batches: - `.\initrun.ps1 .\tools\run-tests-on-vm.ps1 -VMName winui-test2 -SkipPayload * -fromFile:batchN.txt` — 113/115 passed on the first run. The 2 failures (`VerifyFocusInEditorOwnedFlyoutKeepsEditOpen`, `PressAndHoldOnCellOpensCellMenu`) were fixed (product + test) and the rerun passed 4/4 including neighbouring tests. Runtime: sample app Context menus and Hierarchy pages navigated through UIA after the fixes; app stays up and tables render. ## Screenshots (if appropriate) N/A --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 75efebd4-6de1-4fe8-b075-fab226b722c6 Copilot-Session: 128a3e81-37b9-46df-b080-757f110ae417 |
||
|
|
1f82669f7a |
TableView TiP: one test per key operation
Sort, filter, edit begin, edit commit, and expanding or collapsing one group or tree node each report under their own measure and failure reason, so each has its own pass rate. Every test's metrics bucket is the flow direction and whether there are at least 1,000 data rows. A source swap after Initialize, selection, a cancelled edit and group configuration no longer run a TiP test; a failure in one still writes TableView_Error. An exception while expanding or collapsing one group or node is still swallowed, but now fails its test and writes TableView_Error. An expansion request that changes nothing runs no test, since each arrow key on a group header is requested twice. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 53d996ee-1cb6-468a-bfc9-e11af03b1f80 |
||
|
|
6687f886d6 |
TableView: Initialize TiP ends at the first laid-out row
Initialize (measure 64477764) now starts once the template is applied and a source is attached, whichever comes later, and ends at the layout pass of the first realized row or group header, or at once for an empty view. A source set before Initialize has its verdict is part of it, so it no longer writes its own ReplaceSource result. A table whose source stays null runs no Initialize. Failures carry template_setup_failed or source_attach_failed with the HRESULT; app faults still close Ignored. A custom template without PART_RowsRepeater, PART_HeaderRow or PART_HeaderHost closes Initialize Ignored (app_template_incomplete) and sets TemplateIncomplete on TableView_Instance. The metrics bucket is the flow direction and whether there are at least 1,000 data rows. TiP data version 7 (Initialize), InstanceVersion 2, SchemaVersion 8. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 53d996ee-1cb6-468a-bfc9-e11af03b1f80 |
||
|
|
1737209cd2 |
TableView: TableView_Instance replaces TableView_SessionSummary
One TableView_Instance per loaded session (written at Unloaded, the destructor as backstop, capped at 64 per process) carries the enabled and used feature masks, the data-row and peak-realized-row buckets, column and source kinds, flow direction and whether the instance has shown rows. Live shaping counts as used when it reshapes for an item change. TableView_SessionSummary is removed and SchemaVersion goes from 6 to 7. TiP and TableView_Error are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 53d996ee-1cb6-468a-bfc9-e11af03b1f80 |
||
|
|
324b1243e7 |
TableView TiP: blame the app only when its code decides the outcome
- Recovered app faults (BeginningEdit, CellEditEnding, validation, factories, forced-close write) are reported and keep TableView's outcome; app exceptions that leave an operation make it Ignored(application_fault); a TableView failure always wins and both are reported. - Collection reads, caller contracts and selection property observers are attributed through notes at their source instead of exception types; source swap and control sort keep the failing HRESULT. - A parented element from a column factory is the app's fault. - Clears are measured only when the engine has something to clear. - Session: TableView_Loaded once per process, the summary at Unloaded or destruction and only when non-empty, pre-load source swaps measured and counted. - TiP vocabulary moves to TableViewTipTypes.h; counters use explicit slots; data versions 6/5, schema and summary version 6. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 53d996ee-1cb6-468a-bfc9-e11af03b1f80 |
||
|
|
866ba81a1c |
TableView: merge feature/TableView into main (#12267, #12256, #12248, #12120) (#12285)
Merges the TableView feature branch `user/hik/feature/TableView` into `main`. Each change below was reviewed and merged into the feature branch as its own PR. This PR brings them to `main` together. ## Included PRs - **#12267 Hierarchical and live data operations (review fixes)** (@dipeshmsft) - Group/tree collapse keeps selection consistent. - Live-shaping subscriptions are hardened. - Code-review and benchmark-app fixes. - **#12256 [2/3] TableView: cell UIA names, stale peers, grow-only Auto width, regroup selection, HC backplates, rounded focus** - Cell accessible names don't repeat the column header. - Stale GridItem peers throw ElementNotAvailable. - Group StructureChanged is gated on ListenerExists. - Auto columns only grow within a data set. - A regroup keeps the moved selected item selected. - No high-contrast backplates on hovered/pressed rows. - Keyboard focus rects are rounded on cells, header cells and group headers. - **#12248 [3/3] TableView: ListView paging and far-row focus, header naming/focus/gripper, source ownership, safe sort, edit rollback** - **Paging and focus:** ListView-style PageUp/PageDown (group headers are valid targets); far-row focus on Home/End and Ctrl+Home/End. - **Headers:** naming, focus restore and resize-gripper fixes. - **Source ownership:** single-owner `TableViewSource` with release/reclaim on Unloaded/Loaded. - **Sorting:** fails safely over null or duplicate rows. - **Editing:** rollback of invalid values on forced edit teardown. - **Sample-app findings fixed:** a layout-cycle crash, fast-paging focus, ComboBox editor focus, Ctrl+Space selection. - **#12120 TableView: add usage telemetry and Test in Production** (@Saharsh979) - Reliability (TiP) and usage telemetry on the existing Tabular providers. No public API change. - Two TiP tests: `TableViewInitializationTest` runs on `OnApplyTemplate`; `TableViewOperationTest` covers source swap, sort, filter, grouping, selection and edit begin/commit/cancel. - Usage telemetry events: `TableView_SessionSummary` and `TableView_Error`. - Build: `TableView.TestInProduction.props` is imported by Tabular only, and `MuxcTraceLogging.h` gains `KEYWORD_TABLEVIEW`. `main` has 3 NavigationView commits that aren't on the feature branch. They don't touch TableView, and the two sides merge with no conflicts. ## Validation On a test VM, using the feature-branch bits before the final #12248 rebase (before #12120 landed): - **TableView API suite:** 581/582 passed. - **TableView interaction suite:** 122/122 passed. - **TableViewSampleApp:** sanity pass on every page (paging, grouping, editing, sort, navigating away and back). - **Build fix ( |
||
|
|
b6d103698d |
[1/3] TableView: focus re-entry/restore, close fail-fast, UIA/typography fixes, new tests, sample a11y (#12220)
## Summary Fixes a process fail-fast when a window hosting a TableView closes, makes Tab / Shift+Tab into the table land on the remembered cell or row (or a visible row) without scrolling, moves keyboard focus to a clicked group header and keeps it across the collapse, clips the TableView UIA bounds to the table, names the built-in text cell editor, rounds the header focus visual, and lets a `FontWeight` set on the TableView reach cell text. Makes a forced edit close cancel when the edited item was removed. Adds 77 new tests (59 API, 18 interaction) with two TestUI pages, and fixes the sample app's remaining accessibility test-pass findings. Product changes are in `controls/dev/TableView`, plus `ShapedItemsSource` (`RebuildGrouped` ordering) and `HierarchicalSourceAdapter`; the dev spec is updated to match, and code comments are trimmed to must-have only. Supersedes #12185 (closed). #12256 is stacked on this PR, and #12248 on #12256. ## Fixes Only **Fixed** rows are resolved by this PR. | Work item | Bug | Status | |---|---|---| | https://task.ms/64512990 | Closing a window that hosts a TableView fail-fasts the process (0xC0000409) | Fixed | | https://task.ms/64396676 | Watson: fail-fast in Tabular.dll (Task Manager) | Related: generic noexcept→terminate stack; check the cab before resolving | | https://task.ms/64474596 | Watson: fail-fast in Tabular.dll (C++ sample) | Related: same as above | | https://task.ms/64512113 | Shift+Tab back into the table lands on a different row and scrolls | Fixed | | https://task.ms/64459543 | Focus lost on Tab from the last left-nav element into the table | Related: retest on the current sample | | https://task.ms/64476282 | Focus lost on Tab from 'Run all' (Performance page) | Related: retest on the current sample | | https://task.ms/64476063 | Built-in text column editor has no accessible name | Fixed | | https://task.ms/64142128 | Header keyboard focus border has square corners | Fixed (filed against File Explorer; resolve after the package flows) | | https://task.ms/64475623 | Header and cell typography set as local values | Related: cell/editor part fixed here; header weight is #12256; FontSize pending design | | https://task.ms/64338492 | Sample app accessibility test pass (85 sample-side bugs) | 16 fixed here; the rest triaged (see below) | Also fixed, no tracking bug: clicking a group header doesn't focus it; the UIA bounding rectangle includes cache rows below the viewport; Multiply (`*`) on a click-focused tree row strands focus. ## What's fixed **1. Window close no longer fail-fasts (64512990).** - Cause: the deferred `Unloaded` runs after the window is destroyed. Removing the `ThemeSettings.Changed` handler then throws `E_FAIL` from `ThemeSettings::EnsureWindowFeatureDetached` inside the `noexcept` `remove_Changed` in Microsoft.UI.dll, which no TableView try/catch can contain. - Fix: release `ThemeSettings` on the host `AppWindow.Destroying`, while the window still exists. It is subscribed only when the window belongs to the current thread (where `Destroying` is raised), and by token, since `AppWindow` has no weak-reference support for `auto_revoke`. - Other hosts (no `AppWindow`, or a window on another thread) use the `Unloaded`/`~TableView` path: `ReleaseThemeSettings()` releases through `::IUnknown` in try/catch before revoking, and drops the `Destroying` subscription. **2. Tab / Shift+Tab re-entry returns to where the user was (64512113).** - Cause: XAML aims entry at the first/last realized tab stop, often a cache row below the viewport, so focus moved to a different row and the body scrolled. - (i) Entry from outside (`Next`/`Previous`) is redirected in `TableViewRow::OnRowGettingFocus` via `ResolveFocusEntryTarget`: the remembered record's cell or row, found by item (survives sort/recycle); otherwise the first (Tab) or last (Shift+Tab) enabled row in the viewport. - (ii) Disabled rows are skipped when resolving the remembered row and the viewport edge; if the remembered target can't take focus for any reason, entry falls back to the viewport edge row. - (iii) Guards: target must be visible, a tab stop, enabled, not under a disabled control, inside the table; one redirect per `CorrelationId`; cursor state committed only when `TrySetNewFocusedElement` succeeds; the drilled container is cleared on recycle. Arrow, pointer and programmatic moves are not redirected. **3. Tab from the column header band lands in the viewport.** - `TryRedirectBandEntryToViewport`, called from both the row and group header `GettingFocus`, sends Tab from the header band to the first fully visible enabled row or group header in the viewport (last for Shift+Tab), never an off-screen cache container. A row at cell level lands on its current-column cell. - Landing on a group header leaves cell level (the column cursor is kept), whether redirected or already visible. If no enabled element is visible, the framework's target is kept. Entry from outside the table aimed at a group header is not redirected. **4. Clicking a group header focuses it.** - `Focus(FocusState::Pointer)` on left-button release inside the band, before the toggle; the cell cursor returns to row level. Nothing on press, so an open editor's commit can't reshape the table mid-gesture. - The release acts only if the container still shows the pressed group, re-checked after `Focus()`; a press dragged off does nothing; pointer state resets when the container is prepared or cleared. **5. Focus restore after collapse/expand.** - Header restores use the gesture's focus state (Keyboard for Enter/Space/Left/Right, Pointer for a click); data rows restore only Keyboard/Programmatic focus, except Multiply, which is treated as Keyboard so a click-focused tree row keeps focus. - `CanRestoreContainerFocus` skips the restore if focus left the table or the XamlRoot host is hidden. A pointer-toggled header is not scrolled into view. **6. UIA bounds match the table.** - `TableViewAutomationPeer::GetBoundingRectangleCore` intersects the base bounds (a union of realized descendants, including cache rows) with the table's layout box; on a transform failure or no valid intersection it returns the base bounds, never an empty rect. **7. Text column editor is named (64476063).** - `TableViewTextColumn::GenerateEditingElementCore` sets the editor's UIA Name from the existing `TryGetColumnHeaderString` helper. An explicit `CellEditingTemplate` still returns early. **8. Rounded header focus visual (64142128).** - The system focus visual takes its corners from the focused `Grid`'s `CornerRadius`; `RebuildHeaders` now sets `ControlCornerRadius` (4 px fallback) on each header cell. The cell fill is transparent, so only the focus visual changes. **9. FontWeight inherits into cells (64475623, partial).** - Removed the no-op local `FontWeight(Normal)` on the cell `TextBlock` and editor `TextBox`. Header `SemiBold` is unchanged here. **10. Sample app accessibility (64338492).** - Of 85 sample-side bugs: 16 fixed, 33 already fixed, 18 UI removed, 6 stock WinUI behaviour, 9 not valid failures, 3 need a runtime check. 64479175 and 64480152 (Up/Down doesn't scroll the options rail) are not failures. - Actions announce their outcome (repeats re-announced); `RailSection`/`ShapingOptions` are UIA Groups; sliders state their unit via `ItemStatus`; text wraps at 200%; decorative glyphs hidden; Hierarchy headings marked; "Try it" is a callout, not an `InfoBar`. - `AGENTS.md` gains accessibility rules, including "Fix failures, not findings". - Each avatar gets its own `BitmapImage` (sharing one tripped a debug-build image assert). **11. A forced edit close no longer writes into a removed item.** - A forced close (row removed, filtered out, or `ItemsSource` replaced) now reports `Cancel` and doesn't write the pending value into the removed item, matching WPF and the Community Toolkit `DataGrid`. - When the item is still present (sort/regroup, or only under a collapsed group or node), a valid pending value still commits. - This holds for grouped and hierarchical tables too: removing or filtering out the edited row while grouped reports `Cancel`; an item only under a collapsed group or node, or kept by `ClearParentBy`, commits. ## Behaviour changes / regression risk (medium) - Tab/Shift+Tab entry changes for every TableView: forward Tab into a never-focused, scrolled table now lands on the first visible row, and Tab from the header band no longer lands on a cache row. - The `AppWindow.Destroying` release is the fix. The `Unloaded` path remains for other hosts (no `AppWindow`, or a window on another thread), where `remove_Changed` after window destruction can still fail-fast inside Microsoft.UI.dll (WinAppSDK defect, related 52527001). - Cells no longer force `FontWeight` Normal, so an app `FontWeight` on the TableView or an ancestor now shows in cells and the editor. - Clicking a group header now takes focus and restores it after the collapse. - `CellEditEnding` from a forced close now reports `Cancel` instead of `Commit` when the edited item was removed, filtered out, or `ItemsSource` was replaced, and the pending value is not written. ## Testing **Build:** the head, `06f63ef51`, builds Debug x64 with 0 errors and no new TableView warnings (`Microsoft.UI.Xaml.Controls.Tabular.vcxproj`, `MUXControlsTestApp.csproj`, `MUXControls.Test.csproj`). The sample app is not in that set. `tools\Update-Snippets.ps1 -Check` passes. **New tests (77):** - Fix tests: 20 API (`TableViewTests`, `TableView_Editing/Grouping/Rows/Theming_APITests`) and 9 interaction (Keyboard 5, Pointer 2, Accessibility 2); `MultiplyExpandsTreeRowSubtree` re-enabled. - New suites: `TableView_Localization_APITests` (10), `TableView_Recycling_APITests` (9), `TableView_Lifetime_APITests` (3), `TableView_Intersections_APITests` (12), `TableView_HierarchyLive_APITests` (5), `TableView_Axe_InteractionTests` (4), `TableView_Touch_InteractionTests` (3), `TableView_Virtualization_InteractionTests` (2). - TestUI: new `TableViewTouchPage` and `TableViewVirtualizationPage`; `TableViewPage` gains a `DisableFocusedRowButton` fixture and non-tab-stop nav buttons. <details><summary>Fix test names</summary> - Keyboard: `ShiftTabReentryResumesRememberedCell`, `ShiftTabReentryResumesRememberedRow`, `ShiftTabReentryAfterSortResumesCellOnMovedRecord`, `ShiftTabReentryAfterScrollLandsInViewportWithoutScrolling`, `ShiftTabReentrySkipsCellInDisabledRow` - Pointer: `GroupHeaderPointerClickMovesKeyboardFocus`, `GroupHeaderPointerPressDraggedOffDoesNothing` - Accessibility: `TableViewBoundingRectangleExcludesRowsBelowViewport`, `TableViewBoundingRectangleStaysOnLayoutBoxWhenScrolled` - Hierarchy: `MultiplyExpandsTreeRowSubtree` (re-enabled) - API: `VerifyClosingHostWindowDoesNotCrash`, `VerifyHeaderCellCornerRadiusMatchesControlCornerRadius`, `VerifyTextColumnEditorNameComesFromHeader`, `VerifyTableViewFontWeightReachesTextCells`, `VerifyTableViewFontWeightReachesTextEditor`, `VerifyGroupFocusRestoreSkippedWhenFocusLeftTable`, `VerifyForwardTabEntryLandsOnFirstViewportRow`, `VerifyTabFromHeaderBandLandsOnFirstViewportRow`, `VerifyTabFromHeaderBandAtCellLevelLandsOnFirstViewportRowCell`, `VerifyTabFromHeaderBandOntoGroupHeaderLeavesCellLevel`, `VerifyTabFromHeaderBandOntoVisibleGroupHeaderLeavesCellLevel`, `VerifyTabFromHeaderBandLandsInViewportInGroupedTable` - Forced edit close (API): `VerifyForcedCloseCancelsWhenEditedItemIsRemoved`, `VerifyForcedCloseCancelsWhenEditedItemIsFilteredOut`, `VerifyForcedCloseCancelsWhenItemsSourceIsReplaced`, `VerifyForcedCloseCommitsValidValueWhenItemStillPresent`, `VerifyForcedCloseCommitsWhenEditedItemIsOnlyCollapsed`, `VerifyForcedCloseCancelsWhenEditedItemIsRemovedWhileGrouped`, `VerifyForcedCloseCancelsWhenEditedItemIsFilteredOutWhileGrouped`, `VerifyForcedCloseCommitsWhenClearParentByKeepsEditedItem` - Virtualization: `OpenEditIsCancelledWhenItsItemIsRemoved` (replaces `OpenEditIsClosedWhenItsRowIsRecycledByRemovingItsItem`, which expected `Commit`) - `ShiftTabReentrySkipsCellInDisabledRow` also asserts the row it lands on was an enabled row in the viewport before re-entry. </details> **Results so far (VM, earlier heads):** window-close fix before rebase: `VerifyClosingHostWindowDoesNotCrash` 3/3 (fail-fast without the fix), TableView API 511/511. Recycling 8/8; intersections/hierarchy-live/virtualization/Axe 25/25; Touch 3/3, Virtualization 2/2. Text editor name checked manually in the sample (UIA Name = column header). **Forced edit close on `06f63ef51`** (VM `winui-tableview-vm`): all 9 edit-cancel tests pass. Without the fix, `VerifyForcedCloseCommitsWhenEditedItemIsOnlyCollapsed` and the two `*WhileGrouped` tests fail. **Full TableView suite on `c3afccc49`** (fresh VM, one class group per run): API 513/513 (including `VerifyClosingHostWindowDoesNotCrash` 3/3, localization, lifetime); interaction 116/117 (Keyboard 45/45, Hierarchy 13/13 with the Multiply re-enable, Touch, Virtualization, Axe, Pointer, Editing, Layout, ToolTips, Accessibility). The one failure, `TableViewTests.VerifyAxeScanPasses`, flagged test-app chrome (a duplicate app window, `RasterizationScaleTextBox`) on the first interaction run of the fresh VM and passed 2/2 on rerun. **Not yet run:** - The full suite on `06f63ef51`. - Not run since they landed: the header-band redirect and its five API tests, the `Destroying` thread check, the viewport-edge fallback and `ShiftTabReentrySkipsCellInDisabledRow` on the scrolling table. - The sample with Narrator, at 200% text, or in portrait. - No tests for the editor-commit/wrong-group race, a pointer toggle not scrolling, a hidden host skipping the restore, or the popup/island bounds fallback. ## Open questions - With nothing remembered, entry lands on the first/last visible row; ListView uses the focused index and scrolls. Needs UX sign-off. - The UIA bounds over-report may be generic to ItemsRepeater in a ScrollViewer; consider a core fix. - `ThemeSettings::remove_Changed` throwing from a `noexcept` method after window destruction is a WinAppSDK defect (related 52527001); this PR avoids it on the TableView side. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Hitesh Kumar <hiteshkr@microsoft.com> Copilot-Session: 4e307622-98e4-4961-aacf-3b7175bfbb34 Copilot-Session: 26fec974-5991-4f86-93a7-0345db11cdc0 Copilot-Session: 1b4b389b-9df6-4996-bd1c-b4a30a5da768 Copilot-Session: fc704b40-91b4-46d0-85a5-6266d5bfd0bb Copilot-Session: 6a119c59-5ea3-491a-b1fc-080b9c644511 |
||
|
|
166972a99d |
TableView: fix the product bugs behind the ignored API tests and re-enable them (#12190)
## What this does Several TableView tests were checked in disabled because each one exposed a real product bug. This PR fixes those bugs and enables the tests — both the original API tests and the interaction tests that landed more recently. **No public API change.** The only `.idl` edit enables the code-generated property-changed callback for the already-public `GroupHeaderTemplate`. ## What's fixed **Keyboard and focus** 1. After a sort, filter or group, focus returns to the same row instead of being lost. 2. If you press a key or click while the table is rebuilding, your choice wins; the restore doesn't pull focus back. 3. Up/Down/Tab move correctly between the header row and the body, and keep your column. 4. Tab moves cell by cell even when a cell hosts buttons or text boxes; those controls still work and keep their bindings. **Grouping** 5. A single group expanded or collapsed right after Expand all / Collapse all is no longer dropped. 6. The same holds when Expand all / Collapse all is deferred because a cell is being edited. **Sorting and live data** 7. In a sorted view, a row moves to its correct position when its sort value changes. 8. This works with live shaping on or off, without double work, duplicates or crashes. 9. Adding, removing or replacing items while sorted stays in order and can't crash, even if the app changes the data mid-update or from a background thread. **Columns and resizing** 10. Dragging a column gripper follows the mouse in both left-to-right and right-to-left layouts. 11. Esc during a drag cancels it and restores the original width. 12. A bad Width, MinWidth or MaxWidth (NaN, infinite, negative) no longer breaks layout. 13. Swapping the header template at runtime redraws the headers already on screen. **Themes** 14. Light, Dark and High Contrast pick the right colours; the gripper uses the accent colour. 15. A brush or style set directly on the TableView now wins over shared resource files. **Screen reader (Narrator)** 16. A row inside a group reads as "X of Y" within that group, not the whole list. 17. Adding, removing or reordering columns notifies Narrator. 18. Narrator can move focus to a group header. ## What changes for apps Everything below is a bug fix unless marked ⚠️. Risk is what could still go wrong in real apps. | Area | What you'll notice | Risk | |---|---|---| | **Focus after sort / filter** | Focus stays on the same row position instead of being lost. | Restore waits on layout and verifies itself; could land a frame late on very large tables. | | **Focus is no longer stolen** | A search box or a column header keeps focus while the table re-shapes — typing in a filter box is no longer interrupted on every keystroke. | Low. Replaces a "who started the sort?" heuristic with a positional rule. | | **Tab in / out of a row** | One Tab leaves the table even when a cell template hosts a `Button`; the button is still reachable by pointer and by Enter. | Medium — suppress/restore temporarily touches app-owned `IsTabStop`. Restored exactly (value, binding or unset); an app-set value in the meantime is left alone. | | **Tab between bands** | Tabbing header ↔ body keeps your column instead of resetting to the first. | Low. | | **Up / Down across the band boundary** | Down from a header enters the first row in the same column; Up from row 0 returns to that header. | Low. | | **Escape during a pointer resize** | Escape cancels the drag and restores the authored width. It previously never reached the (already correct) cancel path, because a pointer resize takes no focus. | Low — the listener lives only for the drag. | | **Open cell edit** | An edit is closed before the rows view resets, instead of leaving a floating editor behind. | Low. An edit no longer survives a sort — that is the intended behaviour. | | **Right-to-left** | Gripper, header grid line, sort indicator and body grid lines all sit at the logical trailing edge; pointer resize now agrees with keyboard resize. | Medium — several visual pieces mirror together; RTL + custom templates is a wide space. | | **Resize separator colour** | Follows the user's accent colour per theme and meets non-text contrast. The old colours were hard-coded, swapped between themes, and under 3:1. | Low–Medium — visibly different if an app expected the old line. | | **`ResizeGripper` theme resources** | Nothing visible via TableView; the primitive now works standalone. | Medium — a theme page that isn't wired into the build is *silently* absent at runtime. Verified present in the generated dictionary. | | **Resource lookup order** | An `Application.Resources` override of a TableView brush now actually wins, and the nearest scope wins; element-scoped `RequestedTheme` resolves. | Medium — central and subtle. An app relying on the old (wrong) order sees a change. Also no longer force-creates every deferred resource. | | **Grouped row position** | Narrator says "3 of 8" within the group instead of counting the whole flattened list including header bands. | Low — and faster, via a cached group index. | | **Single-level group header** | No longer announces a meaningless `Level` 1; reports `0`. An app-set `AutomationProperties.Level` still wins. | Low. | | **Template cell / header names** | `AutomationProperties.Name` on templated content is announced instead of ignored. | Low–Medium — the search is intentionally depth/width bounded, so a deeply buried label may still not be found. | | **Group header + screen reader** | Collapsing a group through UIA keeps focus on the header, matching the Tab route. | Low. | | **Column add / remove / replace / hide** | Raises a structure-changed notification, so assistive tech stops working from a stale picture. | Low–Medium — coalesced to the next tick and gated on a listener. Header-content-only changes deliberately don't raise. | | **Non-finite column widths** | `MinWidth`/`MaxWidth`/star factors/auto width are normalised, so `MinWidth = +∞` no longer produces a non-finite `ActualWidth`. The authored `Width` value isn't coerced — only its consumption. | Low. One shared clamp policy replaces four near-copies. | | **⚠️ Negative `MaxWidth`** | **Behaviour change.** A negative finite `MaxWidth` (and `-∞`) now clamps to 0, then up to `MinWidth`. It previously meant "unbounded" — the opposite of the author's intent. `NaN` and `+∞` still mean unbounded. | **Medium — the one item worth a release note.** A column that used to grow freely is now pinned to its minimum. | | **Sort key changes at runtime** | A row whose sort-key property changes via `INotifyPropertyChanged` now moves to its correct position by itself. | Medium — deepest change here. Only sort-key properties are tracked, so unrelated changes cost nothing. | | **With `IsLiveShaping` on** | No double work: live shaping (from #12219) owns staleness and the sort-key subscriptions stand down, handing back when it is turned off. Focus also holds its row position across a live re-shape. | Low — ownership is handed over in both directions, and the in-place splice stands aside while a live restore is pending. | | **Filter + sort together** | Correct ordering. The fast in-place re-sort is contracted sort-only; with a filter active it could splice at an offset derived from live predicates against stale membership. That combination now takes the full-refresh path. | Low — deliberately trades speed for correctness. | | **Large / busy collections** | Sorted bulk append is no longer O(n²); Remove/Move use the change's own index. No visible behaviour difference. | Medium — index arithmetic. Indices are revalidated before state is reused, degrading to a rebuild rather than undefined behaviour. | | **Collapse All** | No longer leaves a hand-expanded group looking expanded. Queued per-group toggles can't write an exception back after the bulk call. | Low. | | **`GroupHeaderTemplate` at runtime** | Swapping the template updates headers already on screen; pooled containers are skipped so an old template can't resurface. | Low. | ## How the fixes work ### Focus across a re-shape (sort / filter) - A body row's focus position is **tracked as focus moves**, rather than sampled when the re-shape happens. Every re-shape notification is raised *after* `ItemsRepeater` has already recycled the focused row, so sampling at that point always observed focus outside the body. - Body focus is restored **only when focus was in the body, or was dropped to nothing by the re-shape itself**. Focus anywhere else is left alone: a column header keeps focus however it was activated (Enter/Space, pointer, or a screen reader's UIA Invoke), and a control outside the table — e.g. a search box focused by Ctrl+F — keeps focus while it filters the table. - The restore checks where focus is before every attempt, retries against layout rather than a fixed number of turns, verifies the result instead of trusting `Focus()`, and restores with the focus state the user had. - An open edit is closed before the rows view resets. ### Keyboard navigation - Tab / Shift+Tab out of a row no longer moves focus itself. It suppresses the row's cell-content tab stops for the duration of the walk and lets XAML perform the navigation, which keeps `ItemsRepeater`'s `KeyboardNavigationMode.Once` scope intact. The original `IsTabStop` local value, binding or unset state is restored exactly on the next dispatcher turn, and a value the app changed in the meantime is left alone. ### Grouped automation peers - Rows in a group report `PositionInSet` / `SizeOfSet` **relative to their own group**, excluding header bands, instead of against the whole flattened projection. - A single-level group header no longer synthesizes `Level` 1; it reports `0` ("not specified"), while an app-set `AutomationProperties.Level` still wins. ### Theming and right-to-left - The resize separator follows the user's accent colour per theme (`SystemAccentColorDark1` in Light, `SystemAccentColorLight2` in Dark), meeting non-text contrast in both. - `ResizeGripper` owns its theme resources, so the primitive works outside TableView. - Right-to-left uses one model: `FlowDirection` mirrors the TableView subtree, so the gripper, header grid line, sort indicator and body grid lines all sit at the logical trailing edge. - Element resource lookup no longer enumerates dictionaries (which forced every deferred resource to be created), follows the framework's local → merged → theme order, and resolves element-scoped `"Dark"` theme dictionaries. ### Performance - Grouped row `PositionInSet`/`SizeOfSet` use a cached group index (O(log G) per query instead of a walk per query). - Sorted incremental Remove/Move use the change's own index instead of scanning the source; per-row property-change tracking only reacts to sort-key properties; all preconditions are checked before the projection is mutated. - A stale-sort repair raises `ShapingChanged`, so the control treats it as a re-shape. ### Other - Column sizing is normalised through one shared clamp policy; a negative `MaxWidth` now clamps to 0 rather than meaning unbounded. - A row whose sort-key property changes is re-sorted in place; the sort-only fast path is gated off whenever a filter is active. - `CollapseAll` reconciles realized group headers, and a `GroupHeaderTemplate` change re-prepares them. - Column add/remove/replace/visibility raise `ChildrenInvalidated`, coalesced and gated on a listener. - Column-resize cancel and RTL gripper behaviour fixes carried over from the earlier rounds of this PR. ## Known unrelated build issue (not fixed here) While validating this PR we hit a pre-existing, repo-wide incremental-build bug in `packaging/Microsoft.WinUI.csproj`, unrelated to TableView. `package.appxfragment` is emitted by a separate target that reuses a property computed inside `FillExtensions`; when MSBuild skips `FillExtensions` because the manifest is up to date, the fragment is written from an undefined property — empty — and then becomes *newer* than the manifest, so both targets look up to date indefinitely. The component package then carries **0 of 791** `ActivatableClass` registrations and the test app cannot activate most WinRT types. A clean build orders the targets correctly, so it only bites incremental builds. **Recovery without a code change: delete `$(OutDir)AppxManifest.xml` as well as `package.appxfragment`** — deleting the fragment alone reproduces the empty result, because `FillExtensions` stays skipped. Deliberately left out of this PR: it is a packaging change with different owners and no TableView content. ## Test infrastructure - The frozen-column pinning test now asserts on geometry **published by the test page**. The pin is a composition-only `UIElement.Translation`, which no UIA `BoundingRectangle` reflects, so the previous assertion could not observe the behaviour it claimed to test. - Page buttons driven by **physical pointer input** are kept inside the lab frame's 1024px clip. A clipped control still answers UIA, so it looks present, but receives no clicks — which previously read as "the API is a no-op". - Axe failures now report the violating element and its ancestry instead of only a rule ID. - Sort/filter focus tests trigger the re-shape without moving focus, and new tests cover a header sorted through UIA keeping focus and an outside control keeping focus across a filter. ## Validation Final head, built `amd64chk` and run on a Hyper-V test VM (not the dev box), one class per `TE.exe` process: | Suite | Classes | Result | |---|---|---| | TableView API tests | 36 | **465 / 465** | | TableView interaction tests | 8 | **124 / 124** | Interaction breakdown: Keyboard 40/40, TableViewTests 37/37, Pointer 13/13, Hierarchy 12/12, Editing 10/10, Layout 8/8, ToolTips 3/3, Accessibility 1/1. This includes the tree-row classes that arrived with #12219 (API `TableViewHierarchyTests` 44/44, `TableViewLiveShapingTests` 37/37). ### Rebase onto #12219 #12219 (hierarchical rows and live data operations) refactored the same `TableView` / `ShapedItemsSource` code. Four files needed hand-merging. Notable semantic merges: - `ApplyIncrementalChange` drops the retained layer-1 membership **below** the sorted path, not at the top. The stale-sort repair re-sorts that retained state in place and the sorted splice keeps it in lockstep; every other path there mutates `m_rows` outside `Refresh` and still invalidates. - `TableViewRow::OnKeyDown` is one override: the Tab tab-stop suppression and `Multiply`-expands-subtree both run before a single `__super` call. - `ApplyGroupExpansionByIdentity` carries the bulk generation, its axis, and the `subtree` flag. An A/B against `main` on the same VM was used to confirm the hierarchy chevron tests (`VerifyChevronClippedToLeadCell`, `VerifyChevronClipTracksColumns`, the chevron-click interaction tests); all pass on the final head. ### Review findings The last commit addresses the review findings that held up when checked against the code: re-entrant rebuild during a sorted incremental change (stale indices used on raw `std::vector` splices), off-thread `PropertyChanged` able to destroy the source off the UI thread, bulk-expansion generation stamped at execution instead of request, the re-shape focus restore pulling focus back from a row the user moved to, own-entry detection in element resource lookup, and High Contrast theme-dictionary variant keys. See that commit's message for detail; two regression tests are added. One reviewer suggestion was deliberately **not** taken: making own-entry detection mirror `GetKeyNoRefImpl` exactly when the last merged dictionary answers with a global value. Tabular keys are global (the dll's theme resources), so that change sends nearly every lookup down through the app's merged dictionaries, including `XamlControlsResources`; on the VM it hung the test host. The approximation is documented at `IsOwnEntry`. Note: running the whole suite in a single `TE.exe` process destabilises the guest, so results are from per-class runs. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
4c45fb8ea9 |
Improve TableView resource lookup diagnostics (#12197)
Related to #12115. Missing TableView theme resources can compile and construct successfully, then fail during style/template realization with a generic resource error. This adds setup guidance without changing resource registration, exception text, HRESULTs or failure policy. Control-specific advice stays in the Tabular DLL: informational output to an attached debugger, at most once for the lifetime of each XAML UI thread. Completed dictionary construction is a heuristic, not proof that resources are merged. Equivalent resources, custom templates and late registration remain supported. The shared parser change is generic: use the saved XAML source for resource-lookup tracing when `BaseUri` is absent. No control-specific URI/type routing or new error IDs remain in shared runtime. **Validation:** Release x64 product/native/API builds; 18 fresh native resource/configuration cases; independent compiled StaticResource/ThemeResource red/green with identical XBF; two-UI-thread and late-debugger comparisons; exact API negative-path/cleanup parity. The two compiled deferred tests, format-token guard and TableView negative-path test also passed in OneBranch on Win10 RS5, Win11 23H2 and Win11 25H2. **Visual Studio — same missing HeaderBrush case, tracing off** Before (`242bbf09` runtime): the generic missing-resource error.  After (`a60cb1a5` source-matched runtime): separate setup guidance with the copyable dictionary entry and merge ordering.  The original generic error remains unchanged in that same after session.  |
||
|
|
a60cb1a59b |
Harden TableView setup hints and cover deferred resource traces
Keep setup guidance debugger-only and scoped to the XAML UI thread. Preserve generic exceptions and valid custom/equivalent resource arrangements. Add compiled deferred StaticResource/ThemeResource trace regressions and clarify the format-token compatibility guard and consumer documentation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c7abe393-0d7b-4afe-aafd-d8b475586815 |
||
|
|
acfcee3641 |
fix: keep TableView setup guidance in the Tabular component
Use the saved XAML source only for generic resource tracing. Remove control-specific shared-runtime error routes, log conditional setup guidance once in Tabular, and make construction tracking atomic after successful initialization. Co-authored-by: Hitesh Kumar <hik@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c7abe393-0d7b-4afe-aafd-d8b475586815 |
||
|
|
9da8199d74 |
Trim verbose comments in hierarchy and live shaping code
Comment-only change addressing review feedback; detailed contracts moved to the design doc. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
7e5b704bee |
Re-rank custom comparer rows under live shaping
A CustomSortComparer column sorted by ranks frozen at the header sort, so a live edit to the compared property never moved the row. Each live-tracked change now re-places the item with the app comparer and reshapes when any row's rank moves; coalesced bursts evict and bulk re-rank past log2(n) pending items. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
5b04960b26 |
Harden TableView live shaping with hierarchy
- Reparent/regroup live for object-valued parent and group keys - Copy selectors before hierarchy structure reuse (use-after-free) - Track non-weak items via strong ref + token revoke - Marshal off-thread PropertyChanged to owner dispatcher queue; drop off-owner-thread changes when no queue exists - Revoke subscriptions only after extracting them from the map - Snapshot group keys by group identity, not stringified key - Reshape once when live shaping is switched on - Raise ShapingChanged after a live restore for UIA - Hold live reshapes while a cell is being edited; resume on edit close Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
2bddd5f3ff | feat(TableView): enhance hierarchical data handling with group expander support and improved index management | ||
|
|
1371af7f50 |
fix(TableView): harden hierarchical rows after second review
Engine (ShapedItemsSource / HierarchicalSourceAdapter): - Fix use-after-free when ClearParent is called re-entrantly from a callback. - Fix array overrun when re-slicing groups after a sort. - Defer hierarchy teardown requested during Refresh/Reslice and complete it on unwind (success or failure); rows fall back to inert flat rows on failure. - Track actual publication (m_hierarchyPublished) via a PublishProjection wrapper. - Guard teardown completion against re-entry; nested ClearParent/Refresh requests replay at most once after the outer attempt. - Post refreshes deferred behind a failed pass or failed incremental source change to the dispatcher instead of stranding them. Control (TableView): - Gutter collapse collapses the parent; chevron double-press no longer starts an edit; deep chevrons are clipped instead of overlapping column 2. - Keep selection by node key across rebuilds; bulk expand/collapse no longer ends edits on non-hierarchical sources; reapply theme to hierarchy rows. Tests: self-check grows to 63 cases, including re-entrancy, failure-replay and InputInjector pointer cases for the chevron and gutter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ed471fcb-4192-4dbe-bd09-be236d717a67 |
||
|
|
0bc68ccd5c |
feat(TableView): hierarchical data rows from a flat source
Add tree-shaped rows to TableView driven by a flat items source and a parent-key relation: - TableViewSource.WithParent(keySelector, parentKeySelector) declares the relation; a second call replaces the first. ClearParent() removes it. - Items whose parent key is not found become roots. Duplicate keys and cycles throw E_INVALIDARG naming the offending key. Value keys (numbers, strings, GUIDs, WinRT enums, Point/Size/Rect) compare by value; other objects compare by reference identity. - Filtering keeps matches plus their ancestors; ancestor context rows are auto-expanded through an overlay that never changes the user's expansion state, so clearing the filter restores it. - Works with GroupBy (roots bucketed under group headers) and sorting (siblings ordered by the active sort). - Expansion state is keyed by node key and survives source changes. TableView.ExpandAllRows/CollapseAllRows; '*' expands a subtree. - TableViewRow: indentation, chevron gutter pinned with frozen columns, Level/IsExpandable/IsExpanded state, tree keyboard (Right/Left/*), RTL-aware. - UIA: ExpandCollapse pattern and Level/PositionInSet/SizeOfSet on tree rows; non-tree rows keep base behaviour. - New ParentKeyIndex (TabularShaping) and HierarchicalSourceAdapter projector with splice updates and Reset fallback for large toggles. - Design note: Hierarchical-Data-Rows-Parent-Key-Design.md. - Sample: hierarchy demo page (two relations over one collection, perf probe) and a self-check page. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ed471fcb-4192-4dbe-bd09-be236d717a67 |
||
|
|
ee23471e06 | Adding basic TableView tests | ||
|
|
2def76947e |
TableView: close the remaining UIA accessibility gaps (#11820)
## Fixes Fixes # Internal Issue: https://task.ms/63757754 Also addressed here: https://task.ms/63996585 · https://task.ms/63996586 · https://task.ms/63996583 · https://task.ms/64055746 · https://task.ms/64055747 Resource lookup dependency, fixed upstream by #12071: https://task.ms/64037296 Validation gate: https://task.ms/64037297 ## PR Type - [x] Bugfix ## Description An accessibility review of `controls/dev/TableView` against the UIA provider contracts. The peers already cover the main shape — `IGridProvider`, `ITableProvider`, `ISelectionProvider`, `IItemContainerProvider`, `IGridItemProvider`, `ITableItemProvider`, `IValueProvider`, `IExpandCollapseProvider`, `IInvokeProvider` for sort, plus sort/resize/selection announcements and RTL handling — but they left gaps in the properties, provider identity, and keyboard paths a Narrator user depends on. ### Current Behavior | Gap | Symptom today | |---|---| | `TableViewRowAutomationPeer` overrides no `GetPositionInSetCore` / `GetSizeOfSetCore` | Rows are virtualized, so UIA only sees the realized window. Narrator cannot say "row 5 of 200". | | `TableViewRow` has no stable name | The row is a `Control` with no content of its own, so the base peer computes little. | | `TableViewGroupHeaderAutomationPeer` has no `GetLevelCore` | Grouping is hierarchical (`TableViewGroupInfo::Level`), but nested groups are announced as siblings. | | Cells have no localized control type | Every cell is announced as a generic data item. | | Header cell `IsTabStop` is gated on resizability alone | In the common `CanUserSortColumns=true` / `CanUserResizeColumns=false` configuration the header is clickable-to-sort but has no tab stop and no focus visual, so a keyboard-only user can never sort. | | Cell and header peers are not identity-stable | Cells were minted fresh per query. UIA compares providers by identity, so grid addressing and tree navigation disagreed about the same cell. | | `ITableProvider::GetColumnHeaders` is broken in dxaml | `CUIATableProviderWrapper::GetColumnHeaders` used `safeArray.release()` in the OOM check. For any non-empty header set it leaked the SAFEARRAY and returned a failure HRESULT, making column-header enumeration effectively non-functional for **every** XAML `ITableProvider` implementer. | ### New Behavior | Area | New behavior | |---|---| | Row peer | `GetNameCore` composes visible cell text and is **focus-independent**; an explicit `AutomationProperties.Name` still wins. `GetPositionInSetCore` / `GetSizeOfSetCore` are **flat**, using the same basis as `IGridProvider::RowCount` and `GetItem` — including grouped tables, where group headers and rows are flat siblings in this provider tree. | | UIA property conventions | Unknown `PositionInSet`, `SizeOfSet` and `Level` return `0`, UIA's "not specified" value for these 1-based properties. This is a spec-conformance improvement that **deliberately differs** from dxaml peers such as `AutomationPeer::GetPositionInSetCoreImpl`, which return `-1`. | | Cell peer | Adds localized control type `"cell"` while keeping `AutomationControlType` as `DataItem`. The Tabular resource-subtree fix is **not** in this PR — upstream #12071 landed the byte-identical change first. With it present, a COM UIA client observes `LocalizedControlType=cell`. | | Header peer identity | Restores and redesigns the column-header peer cache: keyed by logical column, peers held in `tracker_ref`, returning the realized header-cell-owned peer when one exists and synthesizing a TableView-owned peer only for unrealized columns. `GetColumnHeaders` and cell `GetColumnHeaderItems` share one source of identity, and enumeration includes scrolled-out columns. | | Header peer behavior | The public constructor no longer throws for an unrealized column. Synthetic `AutomationId` is restored as `TableViewColumnHeader_<n>` unless the app supplies one; it is creation-order dependent, not run-stable. Synthetic peers are gated by `IsTableViewOwned()` so they do not report the whole table's bounds, a clickable point inside the data body, or children they do not own. | | dxaml wrapper | Fixes `CUIATableProviderWrapper::GetColumnHeaders` by checking `safeArray.get()` instead of `safeArray.release()`, and releases each COM interface through its own pointer. This was the lone outlier among the 15 `IFCOOM(safeArray...)` sites in that file — 14 already used `.get()`. It is a hard **prerequisite** for the controls-layer fix, not a drive-by. | | Keyboard | A header cell is a tab stop when it is resizable **or** sortable, and unmodified Enter/Space on a focused header toggles sort. Enter fires on key down; Space arms on key down and fires on key up; auto-repeat cannot re-sort. | | Implementation plumbing | Adds implementation-only `TableViewHeaderCell` and `TableViewCell` wrappers; changes the cell wrapper from `Border` to a composed `Grid`; adds `IVirtualizedItemProvider` to row and cell peers; adds the cell-peer edit-name pinning state machine; adds `IsTabStop=False` to the `TableView` default style. | | Localization | Adds five localized resources (cell control type, list separator, cell name format, boolean words) and culture-aware number formatting, so an integer no longer announces as `42.00` and a decimal no longer as `1.50`. | ### Subsequent fixes in this PR Validation against a live sample surfaced further defects - several introduced by earlier revisions of this PR. Each is fixed here. | Fix | Defect | |---|---| | Reach every header, and every control inside a cell | Headers that were not focusable could not be reached, and controls hosted in a cell were unreachable. Adds `Enter` to enter cell content, `Escape` to leave it, and `Shift+Space` for row selection. | | Keep arrow keys inside the cell | Arrow keys escaped the focused cell's content - from column 7 to column 9 in one case. Drill-in now enters the first cell rather than an arbitrary one. | | Make every row reachable, stop announcing cells twice | Grouped rows reported "no content" / "no items in view", and every cell was announced **twice**. The duplication was proven structurally from a UIA tree dump rather than inferred from property spot-checks. Also fixes a `weak_ref` crash and a duplicated occurrence. | | Keep rows exposed after a group collapse and expand | Collapsing then expanding a group left its rows absent from the UIA tree, because cached peers outlived the containers they described. | | Stop a UIA tree walk hanging the control | An identical `FindFirst(Descendants, PropertyCondition)` took 136 ms on 8 columns and **timed out, pinning one core**, on 17. CPU attribution placed the cost in the control (+9.9 s per 10 s), not the client: `GetChildrenCore` ran a focus search plus repeated `AccessibilityView` writes on **every** query. The walk is now bounded. Also scopes cell interaction mode and the Alt/Ctrl chords. | | Cell classification cache correctness | The classification verdict was keyed on content-object identity alone. Because `ItemsRepeater` rebinds the *same* container element to a different item, a verdict could leak between records with no state transition at all. Now keyed on content **and** the tracked item identity, with property-changed callbacks on `Visibility`, `IsEnabled` and `IsTabStop` held by weak-object revokers. | | Invalidate that cache on subtree swaps | Those properties describe elements that already exist; none covers the subtree *gaining* one. A `TableViewTemplateColumn` can swap content beneath a stable presenter with no observed property changing, leaving a stale "not interactive" verdict that would then actively hide a newly-arrived control from the Content view. Now also observes `ContentPresenter`/`ContentControl` `Content` and `ContentTemplate`, and `ItemsControl.ItemsSource`. | **Root cause common to most of these:** state keyed on objects that `ItemsRepeater` recycles. An existing container is rebound to a new item, so any peer state keyed on container identity silently describes the wrong record. These fixes state the peer-container-item relationship explicitly instead of trusting object identity. **Measured after the final fix:** 17 columns by 8 rows, the previously fatal `FindFirst` shape at **2-4 ms**, control responsive, zero idle CPU. **Known residual, stated rather than papered over.** The classification cache is derived from the visual tree, and XAML exposes no subtree-changed event. Direct `Panel.Children` mutation, `x:Load` realization, and mutation of an already-assigned observable items source are therefore not observable without re-walking on every query - which measured 1680 ms on 8 columns and a timeout at 17, i.e. it reintroduces the hang this change exists to fix. `LayoutUpdated` would catch every case but fires for any layout anywhere in the tree, so the cache would be permanently stale during scrolling. Note that the timings quoted here are from a debug build; if a retail measurement shows the uncached walk is acceptable, the better resolution is to remove the cache entirely and close this class of staleness outright. ### Terminal gridlines merged in (was PR #12139) The terminal-gridline work is merged into this branch so the two land together rather than conflicting on whichever goes second. Four files conflicted; each was resolved as a union of both behaviours: - `TableView.h` - keep the grouped-row refresh layout token, take the renamed and broadened items-source collection revoker. - `TableViewRow.cpp` `OnApplyTemplate` - keep the cells-host local that later code uses and the cell-name reset, add the grid-line border template part. - `TableViewRow.cpp` - keep the cell-navigation helpers and add `GetLastVisibleCellInternal`; both shared a trailing `return nullptr`, so each needed its own closing. - `TableView.cpp` - element-prepared keeps the owner-ensure call and takes terminal suppression in place of the superseded per-row refresh; index-changed and both teardown sites run both behaviours. **One defect the textual merge produced, which would have shipped silently.** `RefreshGridLines` located the last visible cell with `try_as<Border>`. That was correct when the gridline work was written, but this branch changes the cell wrapper to a composed `Grid`. `Grid` does not derive from `Border`, so the cast could never match, the last visible cell would never resolve, and terminal-gridline suppression would never apply - while the very next loop in the same function already used `Grid`. A textual merge cannot see this; it only surfaces when the merged result is compared against a known-good integration of the same two changes. Fixed to `Grid`. The IDL delta from that work is comment-only (documenting `PART_GridLineBorder`), so the no-API-change statement below still holds. **Verified after the merge** on a fresh build, deployed binary hash-matched to the product output: 17 columns x 8 rows, all 136 cells addressable and named, **0 duplicate cell identities**, 8/8 row peers named with `SelectionItemPattern` and populated `PositionInSet`/`SizeOfSet`, 17/17 headers focusable, `Grid`/`Table`/`Selection`/`Scroll` patterns intact, `GetColumnHeaders` returning 17, and the previously fatal `FindFirst` shape at **1-4 ms** with the control responsive. Not verified by me: the gridline rendering itself. That is a visual property and this environment has no reachable interactive desktop (screen capture and foreground activation both fail), so it needs a human pass. **No IDL/API surface change** — nothing in `controls/dev/Generated` changes. There are, however, observable UIA behaviour changes: cell `AutomationControlType` is `DataItem`; header `PositionInSet`/`SizeOfSet` move from `-1` to `0` when unknown; and the synthetic header `AutomationId` fallback is restored. ## Validation status **Built clean** (product, `amd64chk`) and the accessibility surface was verified end to end against a live sample: 150x8 grid, **1200/1200 cells addressable and named, 0 duplicate provider identities**, 8/8 headers focusable, `Grid`/`Table`/`Selection`/`Scroll` patterns all present, `GetColumnHeaders` correct, and the previously fatal `FindFirst(Descendants, PropertyCondition)` shape at **1-3 ms** (it previously timed out while pinning a core). Keyboard behaviour verified by `SendInput` on an interactive desktop, with a foreground guard and a positive control so a dead injection channel could not be misreported as a product pass: `Enter` into interactive cell content and `Escape` back out (3/3 trials), `Shift+Space` row selection, and sort via `Enter` and `Space` on a sortable header. A non-sortable header correctly exposes no `InvokePattern`. ## Customer Impact User-facing accessibility. Narrator and other UIA clients get stable row names, flat row position that agrees with `IGridProvider::RowCount`, localized cells, audible group nesting, keyboard access to sortable headers, header enumeration that includes scrolled-out columns, and provider identity that stays consistent across table, cell and tree navigation. ## Regression Potential - [x] Medium risk — touches shared components or public APIs The controls-layer changes affect TableView peer lifetime, focusability, names, localized strings and keyboard sorting. The dxaml change is in shared UIA wrapper code and fixes `ITableProvider::GetColumnHeaders` for every XAML implementer, so owners of likely consumers such as `CalendarViewAutomationPeer` should regression-check column-header enumeration. The header tab-stop change adds stops only where a header is actionable. ## How Has This Been Tested? - [x] I have performed a self-review of my own code - [ ] I have added tests to cover my changes - [x] Existing tests pass locally — builds clean. Test coverage is going in a **separate PR**. **Validated by UIA against a built TableView sample** with a COM `IUIAutomation` client: **13 PASS / 4 BLOCKED / 0 FAIL**. | Behaviour | Result | |---|---| | Flat grouped row position | ✅ `PositionInSet=2`, `SizeOfSet=39`, `RowCount=39`; index continues across a group boundary (`11,12,13 \| boundary \| 15,16,17`) | | `GetColumnHeaders` incl. scrolled-out / hidden columns | ✅ 16/16; 16 after scroll; 15/15 after hide | | Header identity; cell ↔ table header provider identity | ✅ stable runtime id; cell header item matches the table header provider | | Row name focus-independent | ✅ identical before and after a focus move | | Localization | ✅ `cell`; `Platform (12)`; no raw True/False; `42` not `42.00`; `1.5` not `1.50` | | Cell control type | ✅ `DataItem(50029)` + localized control type `cell` | | `GetItem` identity and live `Column()` remap | ✅ stable id; coordinates remap `3 → 2 → 3` across hide/show | | Header `PositionInSet` / `SizeOfSet` | ✅ `1..16/16`; a hidden column's header reports `0` | | Empty row names | ✅ 0 on every page, including template-only rows | | Group `Level` | ✅ 1 | | `FindItemByProperty` under grouping | ✅ terminates and advances through groups | | Header focusability | ✅ neither=false, resize-only=true, sortable-only=true, both=true | | Realized `HeaderTemplate` children | ✅ Button reachable in Raw **and** Control views | | Header geometry stack-overflow regression | ✅ no crash reading header `BoundingRectangle` / `GetClickablePoint` | **Enter/Space keyboard sort — now verified.** The earlier blocker was environmental, not a product gap: the validation machine had no interactive desktop, so `GetForegroundWindow()` returned 0 and `SendInput` injected 0 events. Re-run on an interactive desktop with real `SendInput`, with two guards added after an unguarded run produced meaningless output — abort unless the target window actually holds the foreground, and a positive control (`Down` must move focus cell-to-cell) so a dead injection channel cannot be misreported as a product failure. Both guards fired during validation and voided runs that would otherwise have been recorded as defects. | Keyboard behaviour | Result | |---|---| | Sort via `Enter` on a sortable header | PASS - column reorders | | Sort via `Space` on a sortable header | PASS - reorders again on next activation | | Sortable header exposes `InvokePattern` | PASS - `Invoke` + `ScrollItem` | | Non-sortable header omits `InvokePattern` | PASS - correct, `ScrollItem` only | | `Enter` into interactive cell content | PASS - focus moves into the hosted CheckBox, 3/3 trials | | `Escape` back to the cell | PASS - returns to the owning cell, 3/3 trials | | `Enter` on non-interactive cells | PASS - correctly a no-op, focus stays on the cell | | `Shift+Space` row selection | PASS - `SelectionItemPattern.IsSelected = true` on the row | One correction worth recording: an initial pass reported sort as broken. That was a bad test - it targeted a column deliberately marked `CanSort=false`. What exposed it was that **mouse click did not sort that column either**; a genuine keyboard-accessibility gap would show mouse working and keyboard not. [64037297](https://task.ms/64037297) is satisfied. ## Out of scope — follow-ups 1. ~~Confirm Enter/Space on an interactive machine before this PR leaves draft.~~ **Done** - verified by `SendInput` on an interactive desktop; see the keyboard table above. ([64037297](https://task.ms/64037297)) 2. **Automated tests in a separate PR** for the UIA provider shape and the keyboard behaviours validated here. 3. **Track the dxaml platform defect separately** and add an OS-side owner of `dxaml/xcp/win/shared/UIAPatternProviderWrapper.cpp` as a reviewer — its blast radius extends well beyond TableView. 4. **Clean up the sibling VT_UNKNOWN wrappers separately.** `CUIASelectionProviderWrapper::GetSelection`, `CUIATableItemProviderWrapper::GetColumnHeaderItems`, `CUIATableItemProviderWrapper::GetRowHeaderItems` and `CUIATableProviderWrapper::GetRowHeaders` still rely on COM identity aliasing to stay balanced. 5. **No `ValueProperty` changed event on edit commit.** ([63787021](https://task.ms/63787021)) 6. **Align the header-band tree peer with the rich header provider** used by `ITableProvider`, so Narrator scan mode can sort. ([63996581](https://task.ms/63996581)) 7. **No `ITransformProvider` on column headers.** ([63996584](https://task.ms/63996584)) 8. **Possible high-contrast brush shadowing** in `TableView.xaml`. ([63996587](https://task.ms/63996587)) --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7ff330f3-1185-4748-a52a-76eb70775fc1 Copilot-Session: 5984405c-a40f-44d2-95d5-98354df4ff74 |
||
|
|
04f85a0210 | Resolving merge conflicts | ||
|
|
23492d1a46 | Fixing ResizeGripper and column headers edge positioning in RTL mode | ||
|
|
d9a12a259a | Enabling sorting columns via keyboard | ||
|
|
3697faa62a | Removing redundant background setting of header cells | ||
|
|
64cb5cfd83 |
TableView: honor CanResize and keep a Star resize inside the viewport (#12083)
## Problem A column resize was clamped only to the dragged column's own `Min`/`MaxWidth`, ignoring what the other columns could absorb. Two consequences: 1. **`CanResize=false` had no effect on redistribution.** It stopped a gripper appearing on that column, but a drag on the *neighbouring* column still squeezed it down to its `MinWidth`. A column the app marked non-resizable was resized indirectly. 2. **The dragged column grew without bound.** Star columns divide the viewport, so the surplus became horizontal overflow with the trailing column clipped to a sliver behind a scrollbar. Reported against the Windows File Explorer property sheets, which use two `*` columns with `MinWidth="50"` and the second column non-resizable. ## Change New `TableView::ResizeBoundsForColumn` bounds the gesture by what the other columns can actually give: - a resizable **Star** column may yield space, down to the floor the Star pass clamps it to — only Star columns are re-divided when the dragged column's width changes, so only they can donate; - everything else — `Pixel`, `Auto`, or a column the user may not resize — holds its current width; - when no neighbour can participate the gesture is **pinned**, not merely capped. Bounding only growth would let a drag hand width to a column that is then not allowed to give it back, so the column could shrink but never return; - with no Star column anywhere, or no resolved viewport, the gesture stays unbounded and the extent scrolls exactly as before. `TableViewColumn` gains an internal **authored width**: the width the app assigned, which a user resize does not overwrite. This is needed because a resize rewrites `Width` as pixels and the keyboard path runs a *complete* drag per keypress — without it, from the second arrow press the column no longer reports that it was authored `Star` and the bound disappears. Both input paths are covered: the keyboard step drives the same gripper drag. 5 files, +259 / −60. ## Behaviour model The rules below decide every case. `Predict-Model.ps1` implements them and reproduces all 25 configurations exactly, so a drift between stated behaviour and actual behaviour fails. **Donor** - a column that can give width away: currently `Star`, user-resizable, **and positioned after the dragged column**. Only Star qualifies, because the layout pass only re-divides Star columns; a `Pixel` or `Auto` column never shrinks in response to a neighbour's drag. Star-ness is read from the column's *current* `Width`, because that is what the layout pass re-divides - a column an earlier drag rewrote as pixels donates nothing and reserves its actual width instead. **Direction.** A resize takes space only from the columns *after* the dragged divider, matching WPF DataGrid. Star columns before it are frozen at the width they already render, which keeps them out of both the bound and the redistribution pass without changing what the user sees. Measured on `S,S,S` at 159/160/159, dragging divider 1 by +60: the first column previously moved to 127 and now stays at 159 (WPF: 159). **Divides the viewport** - any visible column is authored `Star`, including the dragged one. Evaluated in order: | Condition | Outcome | |---|---| | No column authored `Star` | **Unbounded** - the column grows, the extent exceeds the viewport, a scrollbar appears | | `Star` present, no donor among the others | **Pinned** - the divider does not move | | `Star` present with at least one donor | **Capped** at `viewport - reserved` | What each other column reserves: | Column | Reserves | |---|---| | Donor (free Star) | its `MinWidth` | | `Pixel` | its pixel width | | `Auto` | its content width | | Locked `Star` | its authored share: `(viewport - authored non-Star widths) x factor / total authored factors` | Worked examples: `S,S!` has no donor, so the divider pins at 239/239. `S,S!,S` reserves the locked column's authored share of 159 plus the donor's 50, capping the drag at 269 for 269/159/50. `P,P` has no Star at all, so it grows and scrolls. **Where WPF differs.** WPF's donor rule is simply "resizable", with no Star requirement, because its resize *assigns* the neighbour a new width instead of relying on a redistribution pass. So WPF shrinks a `Pixel` neighbour and gives 428/50 for `S,P` where this pins at 239/239. Promising space that a `Pixel` neighbour never actually surrenders is what produced the original overflow. ## Testing Two paired samples were written for this — a WinUI `TableView` repro and a WPF `DataGrid` reference — both driving the **real** resize gripper (WPF via `PART_RightHeaderGripper` thumb events, WinUI via the header keyboard step), so the actual redistribution policy runs rather than a direct `Width` assignment. **25 configurations × 4 drags × 3 runtimes = 300 measurements.** Config syntax: one spec per column, `P` = Pixel 239, `S` = Star `1*`, `A` = Auto, trailing `!` = `CanResize=false`. Viewport 478px, `MinWidth` 50 on every column, 4 rows. Exhaustive two-column matrix (3 modes × 3 modes × locked/unlocked = 18): ``` P,P P,P! P,S P,S! P,A P,A! S,P S,P! S,S S,S! S,A S,A! A,P A,P! A,S A,S! A,A A,A! ``` Three-column set (7): `S,S,S` `S,S!,S` `S,S,S!` `P,S!,A` `A,S!,S` `S,P!,S` `S,A!,S` Drags per configuration: `+60`, `+220`, `+600`, `-220`, each from a fresh reset. ### Invariants Pixel-for-pixel equality with WPF is the wrong bar — WinUI's `Auto` columns measure wider than WPF's (115 vs 92 here; different cell padding) and the 8px keyboard step quantises a requested `+60` to 64. These two invariants are immune to both: 1. A locked column must not be resized by a neighbour's drag. 2. A layout containing Star columns must not be driven into horizontal overflow. 3. Nor may it overflow **at rest**, before any input — a rounding overshoot shows up with no drag at all. Checked with zero pixel tolerance. | Runtime | locked rows | locked MOVED | star rows | OVERFLOWED | AT-REST OVERFLOW | |---|---|---|---|---|---| | WPF DataGrid (reference) | 60 | **0** | 68 | **0** | **0** | | TableView before | 60 | **28** | 68 | **38** | **8** | | TableView after | 60 | **0** | 68 | **0** | **0** | **All three invariants now hold on every row, matching the WPF DataGrid reference exactly.** ### Headline case — two `*` columns, second non-resizable | Drag | Before | After | WPF | |---|---|---|---| | +220 | 527 / 50, 99px unreachable | **239 / 239** | 239 / 239 | | +600 | 1127 / 50, 699px unreachable | **239 / 239** | 239 / 239 | ### No regression to horizontal scrolling A Pixel-column table has no Star column, so it stays unbounded and still scrolls: 208 → 688 with 478px scrollable. ## Three-column redistribution A locked Star column was still re-divided by `ResolveColumnWidths` after the gesture bound had done its job. A locked Star column is now resolved from the **authored** layout - its share of the viewport under the authored star factors - and held fixed while the free Star columns divide what is left. An authored `Auto` column contributes its content width rather than whatever a resize gave it, so dragging an `Auto` column cannot change what the authored layout leaves either. This lands on WPF's numbers exactly: | Config | Drag | Start | WPF | This PR | |---|---|---|---|---| | `S,S!,S` | +220 | 159 / 159 / 159 | 269 / 159 / 50 | **269 / 159 / 50** | | `S,S,S!` | +220 | 159 / 159 / 159 | 269 / 50 / 159 | **269 / 50 / 159** | ### Layout rounding Star widths are snapped on the **running total** rather than per column. Rounding each column independently lets the snapped widths sum to more than the space being divided — with three `1*` columns in a 478px viewport that produced 160/159/160 = 479, a permanent one-pixel horizontal scrollbar with no input at all. Rounding the cumulative edge and taking differences keeps the sum exact, which is what invariant 3 above checks. ## File Explorer's shipping configurations Read from `FileExplorerManaged` source and swept on both runtimes. These use non-uniform star factors, the default `MinWidth` of 20, and up to four columns - none of which the generic matrix covers. | Surface | Config | MinWidth | |---|---|---| | Recycle Bin (`RecycleBinPropertiesView.xaml:125-149`) | `S,S!` | 50 | | Digital Signatures - Embedded (`DigitalSignaturePropertyPage.xaml:52-77`) | `S2,S,S2` | 20 | | Digital Signatures - Catalog (`:97-128`) | `S2,S2,S,S2` | 20 | | Previous Versions (`PreviousVersionPropertyPage.xaml:105-127`) | `S2,S3,S2` | 20 | | Surface | Drag | Start | WPF | This PR | |---|---|---|---|---| | Recycle Bin | +600 | 239/239 | 239/239 | **239/239** | | DigSig Embedded | +600 | 191/96/191 | 438/20/20 | **438/20/20** | | DigSig Embedded | -220 | 191/96/191 | 20/153/305 | **20/153/305** | | DigSig Catalog | +600 | 137/137/68/137 | 418/20/20/20 | **418/20/20/20** | | DigSig Catalog | -220 | 137/137/68/137 | 20/183/92/183 | **20/183/92/183** | | Previous Versions | +600 | 137/205/137 | 438/20/20 | **438/20/20** | | Previous Versions | -220 | 137/205/137 | 20/275/183 | **20/275/183** | Identical to WPF at saturation and on shrink; no overflow anywhere. The `+220` rows differ by ~4px only because the 8px keyboard step delivers 224 rather than 220. Only Recycle Bin has a locked column, so it is the only surface the `CanResize` half of this change affects. The other three are all free Star and simply gain the viewport cap. **Consumer note, not a control defect:** with the default `MinWidth` of 20, a full drag legitimately squeezes the remaining columns to 20px each. Both invariants hold, but 20px columns are unreadable - those three surfaces should consider setting `MinWidth`. ## Could the app just configure around this? No. Raising `MinWidth` limits how narrow the trailing column gets; it does nothing about how wide the dragged column grows. On the unfixed control the drag had no upper bound at all, so a larger minimum makes the overflow *worse* - the trailing column refuses to shrink as far, and more of the total spills past the viewport. Measured, two star columns, 478px viewport, full drag: | Control | `MinWidth` 50 | `MinWidth` 120 | |---|---|---| | Before | 839/50, **411px overflow** | 839/120, **481px overflow** | | After | 428/50, fits | 358/120, fits | So the overflow is a control defect that no consumer setting avoids, and it affects any app using Star columns. Honouring `CanResize` is likewise a contract the app cannot work around. ## Behaviour change worth noting With two `*` columns where the second is non-resizable, the divider now does not move at all, because there is no eligible column to take space from. This matches WPF DataGrid. Apps that want a draggable divider in that configuration should make the second column resizable or raise its `MinWidth`. Apps relying on the previous behaviour will see a change — but they were getting a column resized that they had asked not to be. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
50e8f50131 |
TableView: trim column header text instead of clipping it (#12084)
## Problem A column header wider than its column is hard-clipped mid-glyph, while the cells beneath it ellipsize correctly — `Department` renders as `Departmen`, directly above cells reading `harper.zhang.00…`. Most visible at high text scale, where a clipped header leaves the column unidentifiable. Two causes: 1. A string header is assigned straight to a `ContentPresenter`, which renders it through an implicit `TextBlock` carrying no `TextTrimming`. Cells set `CharacterEllipsis` explicitly; headers never did. 2. The sort chevron is drawn by a sibling overlaying the same `Grid` cell, and the header presenter reserves no width for it, so header text runs underneath the glyph. ## Change - **Trim header text.** For a string header with no `HeaderTemplate`/`HeaderTemplateSelector`, supply the `TextBlock` explicitly with `CharacterEllipsis`. Non-string headers keep the `ContentPresenter`'s own content model, so a `UIElement` header or an implicit `DataTemplate` is untouched. - **Give the chevron its own column.** The header cell gets two columns — star for content, `Auto` for the indicator — so the chevron reserves its realized width instead of overlaying the text. The grid line and resize gripper span both. - **Reserve that width only while sorted.** `SortIndicator` has a fixed themed `Width` and merely fades via `Opacity`, so an always-visible host would cost 16px on every sortable column. The host is collapsed until the column is sorted, and `RefreshSortIndicators` keeps it in step with a programmatic sort. ## Notes - **RTL:** column order is set explicitly, matching how the grid line and gripper already pick their trailing edge. - **Auto sizing:** only a sorted `Auto` column measures wider, by the indicator width. - **Accessibility:** the header cell's automation name is unchanged; the new `TextBlock` is `Raw` so the text is not announced twice. - **Tooltips are unchanged.** No platform grid (WPF DataGrid, WinUI, HTML) shows a tooltip on truncation by default, and the grids that do expose it as an opt-in. `HeaderToolTip` and `CellToolTipBinding` remain the opt-ins here. A trimming-triggered tooltip covering headers *and* cells uniformly is worth doing separately — it is new API surface with a per-cell cost in a virtualized grid. ## Validation Built with `Build.cmd product`, then `pack.component.cmd` + `Samples/TableViewSampleApp`. Note `Build.cmd mux` does **not** compile `TableView.cpp` — it builds only `dxaml\...\Microsoft.ui.xaml.vcxproj`. Run against `TableViewSampleApp`: **A header too wide for its column now ellipsizes instead of hard-clipping mid-glyph**, degrading the same way as the cells beneath it — `NoSortTip` and `NoSortBare` on the Cell tooltips page:  **Unsorted sortable columns keep their full text width**, matching a baseline build:  **Sorting a column places the chevron beside the header text**, with no overlap:  Templated and non-string headers are unaffected. ## Not yet covered - No API tests. `controls/dev/TableView/` has no test project today. Worth pinning: ellipsis on an over-wide header, sorted vs unsorted `Auto` width, RTL reserving the mirrored edge, and a `HeaderTemplate` column keeping its own content. - RTL, density and high contrast are reasoned about but not yet verified on screen. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
8c2662b3d1 | Fixing review agent findings | ||
|
|
1fa9b92349 |
feat(tableview): converge sort front-ends, add path sort
TableView and TableViewSource each declared their own sort axis with no awareness of the other, so a fluent Sort and a header click stacked two axes and precedence silently went to whichever was declared first while the chevron advertised the other. Sorting is now last-writer-wins: a control-initiated sort clears every foreign axis, and an app-declared sort clears the control's and reconciles the chevrons. Reconciliation could only clear chevrons, because TableViewSource.Sort takes an opaque key selector and nothing names the property it sorts on. Adds Sort(String sortMemberPath, SortDirection): the path is evaluated by the same resolver a column uses for SortMemberPath, so the control can attribute the axis to a column and light its indicator. The key selector overload stays as the escape hatch for computed, multi-field and custom-comparer keys, and stays deliberately chevron-less - guessing which column an anonymous key meant is worse than showing nothing. The axis is tokened by path so re-sorting one column replaces its axis rather than stacking a second; distinct paths still compose, which is how a multi-key sort is declared. SortMemberPathResolver moves out of TableView_Sort.cpp into its own header so the column front-end and the fluent verb cannot disagree about what a path means. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
b2aedb5fe1 |
Port TableView group-header focus + keyboard-nav fixes from internal lift
Restores keyboard focus to a group header after expand/collapse reshape recycles its container, and teaches arrow navigation/selection to treat TableViewGroupHeader as a valid focus anchor in the flat grouped projection (headers are recognized but never selected). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 22e963e3-084f-44c9-be76-8bf90278b146 |
||
|
|
714f0253dd |
Port TableView grouping fixes + GroupedSourceAdapter rename from internal lift
- A1: keyboard expand/collapse for group headers (Enter/Space toggle, Left/Right
collapse/expand, RTL-aware) with the header now a tab stop + focus visuals.
- C1: TableViewGroupInfo.Key exposes the actual group key instead of the internal
ShapedGroup wrapper.
- O1: neutralized internal review comments in the grouped source adapter.
- Programmatic sort now refreshes the column sort chevron: RefreshSortIndicators
walks the header cell for the SortIndicator (typed child-walk) instead of
FindName, which failed for the code-created, non-namescoped visual.
- Header-click sort: sortable header cells get a transparent Background so the
whole cell is a reliable tap target, not just the header text glyphs.
- Rename FlatGroupedSourceAdapter -> GroupedSourceAdapter across the class, files
(GroupedSourceAdapter.{h,cpp}), the FlatGroupedSourceAdapterPtr alias, includes,
and the shared vcxitems.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 22e963e3-084f-44c9-be76-8bf90278b146
|
||
|
|
7c147b5c6f |
Port TableView data-shaping grouping from internal lift
Ports the grouping feature (layer 3 of the Tabular shaping stack) from the internal microsoft-ui-xaml-lift branch onto public main, on top of the already -ported sort + filter core (#11644). Adds the hierarchical/grouped source stack and the row-metadata projection that the TableView consumes to render group headers: * HierarchicalItemsSource: GroupContract + GroupExpansionModel * GroupedSourceAdapter: FlatGroupedSourceAdapter + GroupedEntry * ShapedItemsSource: ShapedGroup + grouped RowIdentity handling * TableView: RowMetadataProvider, TableView_Grouping, flat row adapter, group-header automation peer, keyboard/layout integration; removes the superseded TableViewProjectionView. Control/product code only; the lift sample-app rewrite is intentionally not ported (matching the sort+filter port, whose public sample is a different app). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 22e963e3-084f-44c9-be76-8bf90278b146 |
||
|
|
8d73126ac5 |
Tabular: add opt-in column-header tooltips to TableView (#11764)
* Tabular: add opt-in column-header tooltips to TableView Adds `TableViewColumn.HeaderToolTip`, the header-side counterpart to the per-cell `CellToolTipBinding` that shipped in #11669. Every mature grid pairs the two; a header that ellipsizes or abbreviates has nowhere to put the full label today. A dependency property typed `Object`, not a `Binding`: a header is not bound against a row, so there is nothing to defer. The value is the tooltip's content, not a `ToolTip`, and the control owns the `ToolTip` and its placement (`PlacementMode.Mouse`) - the same contract as the cell path, including leaving a tooltip the app set alone. The tooltip is attached to the header cell root, so it covers the header's content, its padding and its sort affordance. Header cells are rebuilt wholesale rather than recycled, so there is no binding, no attached value and no refresh path: the content is read from the column when the header is built, and re-applied in place when the property changes. Re-applying rather than rebuilding keeps header focus and the sort affordance intact, and deliberately avoids `OnColumnHeaderChanged`, which recomputes the column's Auto width. Owned tooltips are closed before `RebuildHeaders` discards the band - clearing an open tooltip's element is the reentrant `CPopup::RemoveChild` shape. Accessibility: string content is reported through `TableViewColumnHeaderAutomationPeer::GetHelpTextCore`, composed with the column's sort state so neither is dropped. The header peer is virtual - owned by the TableView, not by the header element - so `AutomationProperties.HelpText` on the element would never reach a client; the peer publishes the text itself. As with cells, non-string content is mouse-only. Also corrects the functional spec, which claimed header tooltips could never open because the header band is `IsHitTestVisible="False"`. That attribute is on the row's selection indicator; the header band is hit-testable, which is what makes click-to-sort and resize work. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Tabular: keep header help text from throwing, and cover it in the sample Found by running the sample against a clean build. TableViewColumnHeaderAutomationPeer::GetHelpTextCore threw RESOURCE_NOT_FOUND (0x80073B17) out into UIA for every sortable column, so a client asking a header for its help text got an exception instead of the sort state. That predates this branch - the sort-state lookups are unchanged - but this change adds a second lookup on the same unprotected path, so the lookups now degrade to empty instead of throwing. Sample: adds the header-tooltip cases and two probes that read what a client sees - the ToolTip attached to each header cell, and each header peer's help text via ITableProvider.GetColumnHeaders. Adds two unsortable columns so the tooltip-only and empty paths are observable without the sort state, and buttons that mutate a HeaderToolTip in place and force a header rebuild. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Tabular: address header-tooltip review findings From two orchestrated reviews of the branch. Each finding was checked against the running sample before and after. - The header cell root had no Background, so a Panel with no child at that point took no pointer input: hit-testing the trailing edge of every header missed the cell entirely, and the tooltip could only open over the header glyphs. The row cell wrapper already sets a transparent fill for the same reason. Verified by probing each header near its trailing edge - all MISS before, all HIT after. This also restores the click-to-sort target over the whole cell. - ApplyHeaderToolTip now contains exceptions, matching the cell path. Both call sites run where an escaping exception is a fail-fast: the DP-changed callback, and the header rebuild. Reachable today by assigning one UIElement to two columns' HeaderToolTip - the second Content assignment throws because the element is already parented. - ReleaseHeaderToolTips snapshots the children before closing anything. Closing a tooltip raises Closed into app code, which can mutate Columns and re-enter RebuildHeaders, clearing the collection the loop was indexing with a cached count. - OnApplyTemplate releases the old band's tooltips. It already unhooks the header host there, and replacing m_headerHost otherwise abandons live popups still hosting content parented into the old cells. - The header peer reads HeaderToolTip from the column instead of the realized header, so help text no longer depends on whether the band is currently realized, and no longer costs two band scans and a peer allocation per UIA query. TryGetOwnedToolTipText goes with it. - Non-string header content gets a debug diagnostic: it works on hover but is not reported to assistive technology, and nothing else told the author. - Spec: state that a ToolTip is not a valid value, that a UIElement needs one instance per column, and that rich content should be paired with Header text. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Tabular: tighten comments on the header-tooltip change Trim to the load-bearing why, and make ReleaseHeaderToolTips private - it has no callers outside TableView. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Tabular: cache the header cell fill per rebuild Drops a static helper that duplicated the row cell wrapper's file-local TransparentBrush. RebuildHeaders already caches per-pass values, so the fill joins them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Tabular: share the non-string tooltip diagnostic, and trim comments The "content is not a string" diagnostic moves into SetOwnedToolTip, so the cell path emits it too - both paths have always had the limitation, only the header path reported it. Comments cut back to the load-bearing why. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
fa7cda27ef |
Tabular: add opt-in per-cell tooltips to TableView (#11669)
* Tabular: add opt-in per-cell tooltips to TableView TableView text cells render with CharacterEllipsis and no wrapping, so a value wider than its column is unreadable and there is no way for an app to surface the full text. Adds a CellToolTipRequested event: the handler receives the row's Item and the cell's Column and sets Content, which the control shows as that cell's tooltip. It is opt-in - with no handler attached the control does no per-cell work and allocates nothing - and it is resolved per cell as rows realize and recycle, so a virtualized table only pays for what is on screen. The control owns the ToolTip object. Content is the tooltip's content, not a ToolTip to attach: ToolTipService rebinds a ToolTip's owner and container on every registration, so a single instance handed back for several cells would leave them contending for one owner slot. A UIElement returned as Content is parented by that cell's ToolTip, so a handler returns a fresh element per raise. Ownership is recorded rather than inferred. A private attached record holds the ToolTip the control attached and the exact HelpText it published, tested against the raw value on the element, so a tooltip the app set - including a bare string - is detected and left alone. Accessibility: the tooltip text is published as the cell's AutomationProperties.HelpText, and the cell automation peer suppresses it when it merely repeats the cell's own text so the value is not announced twice. The suppression is resolved in the peer rather than when the tooltip is attached: at attach time the cell content has just been generated and its binding has not produced a value, so the two never compared equal. Resolving it in the peer also keeps automation peer creation off the realization path. When Content is not a string the handler sets AutomationHelpText, since the cell wrapper is internal and an app cannot reach it. Re-entrancy: the event reaches app code from the cell realization path, so the pass snapshots its targets rather than walking the live children collection and holds a strong reference across the raise. The editing cell is re-read after each raise, because a handler can start an edit on the cell being resolved; a refresh requested from inside a pass, such as an edit closing under a handler, is recorded and replayed. Rebuild and refresh share one bounded drain, so app code is never raised more than a fixed number of times per row. A handler that throws loses only its own cell's tooltip, and InvalidateCellToolTips is coalesced onto the dispatcher and bounded, so calling it from a handler cannot queue a callback every tick. Recycling: a recycled row never shows or announces a previous item's tooltip, including after the last handler is removed. Control-created tooltips are neutralized in place rather than detached, so scrolling reuses the object instead of allocating a Control per cell per scroll, matching TabViewItem. Column-header and group-header tooltips are deferred. The header band is IsHitTestVisible= "False" and is scrolled programmatically from the body, so a tooltip there could never open; both surfaces land with the work that makes them interactive. Adds a sample page covering the contract: string content, non-string content with AutomationHelpText, a column that returns nothing, a column whose own template owns its tooltip, and toggles for late attach, re-entrant invalidation and a throwing handler. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Tabular: make cell tooltips declarative via TableViewColumn.CellToolTipBinding Replaces CellToolTipRequested with a binding on the column. The binding is set once on the cell wrapper, so the row's DataContext drives it: a recycled row re-resolves through ordinary inheritance and a source PropertyChanged updates a live tooltip in place. Because the control no longer calls app code while realizing a cell, the re-entrancy surface disappears with it - the per-row drain loops, the pending flags, the dispatcher pass counter and InvalidateCellToolTips() are all gone. Computed content is authored with an IValueConverter. HelpText suppression in TableViewCellAutomationPeer::GetHelpTextCore is now gated on the ownership record, so it never drops text the app set. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 003c9ad0-6c65-4016-ac84-a6a0d75fb31b * Tabular: fix two review findings - Restore the UTF-8 BOM on TableView.cpp, stripped while excising the tooltip event machinery. - Restore the two [TestProperty("Ignore", "True")] attributes on InkToolbarTests.cs. They were dropped accidentally, reverting "Temporarily skip failing InkToolbar cleanup tests (#11733)" and re-enabling two tests the team had deliberately skipped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 003c9ad0-6c65-4016-ac84-a6a0d75fb31b * Tabular: trim the cell-tooltip change to what the feature needs - Restore the OnDataContextChanged re-entry guard and the original RebuildCells comment. They were removed so the tooltip drain could record pending rebuilds; that machinery is gone, and the guard in RebuildCells was always the real one. - Revert the GetCellValueTextFromWrapper extraction. It was pulled into TableViewAutomationHelpers.h to share with the tooltip pass; the peer is the only caller again, so the body moves back inline. - Keep the AbandonCellEdit local named cellWrapper; only the hoist out of the if is needed. - Tighten comments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 003c9ad0-6c65-4016-ac84-a6a0d75fb31b * Tabular: address cell-tooltip review findings Findings from an orchestrated multi-lens review of the tooltip change. Only the ones that verified against current code are here; a high-severity "recycled cells lose their tooltip" finding was disproved by a liveness probe (all realized rows keep live tooltips and HelpText; the neutralized ones are pooled rows, which is correct) and is not fixed. - Drop the unreachable null-binding branch in ApplyCellToolTipBinding. Its one caller is guarded by `if (auto const toolTipBinding = column.CellToolTipBinding())`, so the binding is never null. - EnsureCellToolTipRecordProperty returns void; its only caller discarded the DependencyProperty and called it purely to force lazy registration. - Diagnose a ToolTip handed back as tooltip content instead of swallowing it. ToolTipService.ToolTip does accept a ToolTip, so this is an easy authoring mistake with no other feedback. - Spec: state plainly that non-string content is mouse-only with no accessible representation, and that TabViewItem/NavigationViewItem refuse such content. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 003c9ad0-6c65-4016-ac84-a6a0d75fb31b |
||
|
|
e8e13dd87f |
ResizeGripper: framework-internal, dual-axis resize gripper primitive (#11640)
* ResizeGripper: framework-internal, dual-axis resize gripper primitive Adds ResizeGripper, a framework-internal primitive that owns the mechanics of dragging a divider to a new size, and consumes it from TableView for column resize. The primitive is a delta source: it owns no value, no bounds and no layout. TableViewColumn already owns Width/MinWidth/MaxWidth/ActualWidth, so the gripper reports how far the user has dragged and the host decides what that means and clamps it. BeginDrag -> DragDelta* -> EndDrag(canceled); DragDelta carries both the change since the last raise and the total from the gesture start, so a host applies the total against its captured anchor rather than accumulating error. DragCompleted.Canceled separates a torn-down gesture from a release, and the host restores the authored GridLength on cancel so an Auto or star column is never silently rewritten as fixed pixels. Pointer input arrives as manipulation events, so capture, touch and pen contacts and multi-pointer arbitration come from the gesture recognizer rather than from each host. ManipulationContainer lets the host name the frame drags are measured in: the gripper travels with the edge it drags, so the default frame feeds its own motion back into the gesture and stalls it. DragOrientation names the axis the drag is measured along, which for a divider is perpendicular to how it looks. It is deliberately not called Orientation -- the primitive owns no shape, and that name carries the opposite meaning in other sizer prior art. Deltas are logical, so positive always grows in reading order; only the horizontal axis mirrors under RTL, against the frame the delta was measured in. The header cell, not the gripper, is the keyboard target: that is where column commands belong, and one tab stop per gripper would sit between the user and the data. The gripper is not a tab stop by default, and TryKeyboardStep owns direction, the RTL mirror, KeyboardIncrement and the Shift multiplier so both keyboard paths cannot drift apart. The automation peer reports the Thumb control type -- there is no value to expose -- composes a per-instance name from OwnerName so several grippers in one header band are distinguishable, and returns an empty AutomationId because a constant id on sibling elements breaks UIA sibling-uniqueness. The primitive ships only in Microsoft.UI.Xaml.Controls.Tabular.dll and lives in a private namespace, so it appears in no shipped WinMD. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b77872a9-8623-47b9-a9f1-2a45e3ca3ead * ResizeGripper: review fixes - stale-drag revert, resize cursor, vertical affordance Addresses the nine findings from the framework review of PR #11640, plus one the review missed and running the sample found (F-13). F-1 (High) A drag started while another was live reverted the column to a width from a PREVIOUS gesture. TableView's DragStarted reset didWrite/didDelta only after its "one resize at a time" guard, and that guard ends the new gesture with EndDrag(canceled) - so DragCompleted saw the previous drag's didWrite and restored the previous drag's startWidth, silently undoing a committed resize. Reachable with a pointer drag held on one gripper and an arrow key on another header, or with a second touch contact. The reset now happens first. F-2 DragOrientation="Vertical" is advertised in the IDL but the separator was hard-wired to 0,0,2,0, so a vertical gripper drew its line along the axis it travels. The two concerns are now on two visual-state groups - CommonStates owns whether the separator shows (Opacity), the new OrientationStates owns which edge it is drawn on (BorderThickness) - because one group would have both states writing the same target. Adds ResizeGripperSeparatorThicknessVertical and UpdateOrientationVisualState, driven from OnApplyTemplate and the DragOrientation property change. The separator also moved into its own non-hit-testable child so showing and hiding it cannot interact with pointer input. F-4 Every gripper published a named but pattern-less Thumb in the UIA Content view, doubling the header band's node count with nodes that are not tab stops and expose nothing to invoke. AccessibilityView="Raw" in the default style, matching ScrollBar's HorizontalThumb/VerticalThumb and SortIndicator's glyph. OwnerName and the composed peer name are kept - they still identify the element in the raw view and for a host that opts it back into Content. F-5 TableViewResizeGripperWidth existed only in the theme-resources page, which is not emitted for the Tabular binary, so the key never resolved and the C++ constant was the real default. Mirrored into TableView.xaml's root dictionary alongside the two metrics already mirrored there for the same reason. F-7 A host handler that throws out of DragStarted/DragDelta left IsDragging stuck true on the keyboard path: unlike a pointer drag there is no ManipulationCompleted to follow and clean up, so every later BeginDrag early-returned and resize died for the life of the element. Not a wil::scope_exit - EndDrag raises DragCompleted, and running that from a noexcept guard during unwind would terminate on a handler that throws in turn. F-8 Added the template contract block to the IDL - required parts, the named state target, both visual-state groups and the lightweight-styling keys - following the convention TableView.idl already sets. F-9 Regenerated XamlMetadataProviderGenerated.h. It is produced by the T4 in Microsoft.UI.Xaml.Common.targets from the stripped winmd; this is that output, not a hand-written entry. F-10 StringUtil::FormatString ignored the FormatMessage result and constructed an hstring from a null buffer on failure, which the automation peer's comment assumed could not happen - inside a UIA callback, where nothing may escape. The helper now returns an empty string, making the peer's fallback true. F-12 The width announcement's translator comment said keyboard-only; it is raised from DragCompleted for pointer drags too. F-13 The resize cursor never appeared - hovering a divider lit the separator but left the pointer an arrow, so nothing about the element said it was draggable. UpdateResizeCursor ran only from OnApplyTemplate and the DragOrientation change; a ProtectedCursor assigned before the element is live does not stick (microsoft-ui-xaml#7062). The function's own comment already said the shape "gets re-resolved while the pointer is already over us" - that re-resolution was simply never wired up. Now called from OnPointerEntered, where it no-ops if the shape is already in effect. Built: Microsoft.UI.Xaml.Controls.Tabular.vcxproj, amd64chk, clean. Verified by running the TableView sample (amd64chk, unpackaged self-contained): - F-4: UIA Content view has 0 gripper nodes; the Raw view still has all 8, with per-column composed names. - F-13: sweeping the header band, the west-east cursor appears over the column dividers. The same sweep on a build without this change produced 1603 arrow samples and not one resize cursor - so the fix is what makes the difference. Still not proven by either, and deliberately left open: F-1 needs the concurrent-drag trigger performed; the vertical affordance has no consumer that sets DragOrientation="Vertical"; the 8 DIP touch target is unchanged pending design sign-off. * ResizeGripper: own the keyboard, tighten the primitive Follow-up to the review fixes, from comparing the primitive's responsibilities against the Community Toolkit's SizerBase - the closest shipping prior art. Own the keyboard (was F-6, previously cut as "by design") The gripper held all the keyboard policy - direction, the RTL mirror, the step size and the Shift multiplier - but had no OnKeyDown, so a host that set IsTabStop="True" got a control that takes focus, shows a focus visual and ignores arrow keys. Policy without the matching behaviour. It now handles its own keys through the same TryKeyboardStep a host calls, so neither path can drift. TableView is unaffected: its grippers are not tab stops, so it keeps driving TryKeyboardStep from the header cell. Precedent: SizerBase overrides OnKeyDown and runs the same OnDragStarting/OnDragHorizontal sequence a pointer drag uses. Shift stays a multiplier - a LargeKeyboardIncrement property was tried and cut An earlier revision replaced c_largeIncrementMultiplier{4.0} with an absolute LargeKeyboardIncrement DP. That was wrong: an absolute second value does not compose with the base one. A host setting KeyboardIncrement=20 got 32 (1.6x) instead of 80, and at 40 the Shift step collapsed to 40 (1x) against the std::max floor that stops it inverting outright. The multiplier is scale-invariant by construction; the property needed a guard to be merely not-wrong, and no consumer sets either knob today. SizerBase argues the same way - it has no modifier multiplier at all, and records that if one were added it would be sized relative to the base rather than as a second absolute. One definition of the KeyboardIncrement default 8.0 was stated twice with nothing linking them: the IDL's MUX_DEFAULT_VALUE (what an unset property returns) and a c_defaultKeyboardIncrement constant (the fallback when a host supplies 0, NaN or infinity - the "behave as if unset" case). Changing one would silently diverge from the other. The constant now lives in ResizeGripper.h and the IDL names it, so the codegen consumes the same symbol: BoxValueIfNecessary(c_defaultKeyboardIncrement). 8.0 appears once in the control. MUX_DEFAULT_VALUE already takes a C++ expression - ColorPicker and AnimatedVisualPlayer pass enum symbols the same way. Stop the contract overstating itself The IDL called the type a "policy-free drag handle" that "owns no layout", while it owns its presentation, its cursor and the keyboard step. Round 1 of review introduced that wording when the type stopped owning a value; round 2 then moved the visual states and theme keys into it and left the wording alone. It now says what it actually owns. Tightening - The drag axis was derived at five sites in three phrasings, two of which computed the same boolean with opposite-polarity tests (!= Vertical in the pointer path, == Horizontal in the keyboard path). One IsHorizontalDrag() now serves all five - that divergence is where an axis bug hides. - Dropped the UpdateResizeCursor call in OnApplyTemplate; see below. Correction to the previous commit's F-13 claim |
||
|
|
4d9d6f3191 |
Port TableView data-shaping (sort + filter) core from internal lift (#11644)
Adds the ShapedItemsSource + TabularShaping pipeline and wires TableView, TableViewColumn, the automation peers and TableViewSource to it, providing sort and filter on top of the basic data-shaping infra. Product-only port of user/dipesh/tableview/shaping-core-v2 (27 new files, 26 modified). Internal build/PipelineScripts mirroring config is intentionally excluded. ResourceAccessor.h merged to preserve the open-source repo's InkToolbar string defines. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 22e963e3-084f-44c9-be76-8bf90278b146 |
||
|
|
ef280ef1b7 |
Merged PR 16574861: Remove Tabular/TableView source-mirroring exclusions (#11618)
Syncing content from committish 4f78352283d9ba6d2cf2ad1eacec29e199c8cc85
(cherry picked from commit
|