Skip to content

Tabular: add opt-in column-header tooltips to TableView - #11764

Draft
Hitesh Kumar (hiteshkrmsft) wants to merge 1 commit into
mainfrom
user/hik/tableview-header-tooltip
Draft

Tabular: add opt-in column-header tooltips to TableView#11764
Hitesh Kumar (hiteshkrmsft) wants to merge 1 commit into
mainfrom
user/hik/tableview-header-tooltip

Conversation

@hiteshkrmsft

@hiteshkrmsft Hitesh Kumar (hiteshkrmsft) commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Adds TableViewColumn.HeaderToolTip, the header-side counterpart to the per-cell CellToolTipBinding that shipped in #11669.

<tabular:TableViewTextColumn Header="Notes"
                             Binding="{Binding Notes}"
                             CellToolTipBinding="{Binding Notes}"
                             HeaderToolTip="Free-form notes captured at intake" />

A dependency property typed Object, not a Binding: a header is not bound against a row, so there is nothing to defer. x:Bind and StaticResource both still work against it.

Design

The value is the tooltip's content, not a ToolTip; the control owns the ToolTip and its placement (PlacementMode.Mouse), and never touches a tooltip the app set. That is the merged cell contract, and the ownership record, the content rules and the teardown are reused rather than duplicated — SetOwnedToolTip gained an opt-out for publishing AutomationProperties.HelpText, and nothing else changed in the cell path.

The tooltip is attached to the header cell root, so it covers the header 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 — a tooltip cannot change it. Owned tooltips are closed before RebuildHeaders discards the band, since 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 through a localized format string so neither is dropped. It is suppressed when it merely repeats the header's own name, so the same text is never announced twice — the header-side equivalent of the suppression TableViewCellAutomationPeer already does, and equally gated on the ownership record, so text the app set is never dropped.

This is the one place the header genuinely differs from a cell. The header peer is virtual — owned by the TableView, not by the header element — so AutomationProperties.HelpText on that element would never reach a client, and GetHelpTextCore already reported the sort state. The peer therefore publishes the tooltip text itself and merges the two, rather than the attach path setting HelpText as it does for cells.

Non-string content is mouse-only, matching the cell rule and TabViewItem / NavigationViewItem.

Spec correction

The functional spec claimed column-header tooltips were deferred because the header band is IsHitTestVisible="False" and "a tooltip there could never open". That is wrong: the attribute is on the row's selection indicator, and the header band is hit-testable — which is what makes click-to-sort and resize work. Corrected in this PR.

Not in scope

Tooltips are still not gated on truncation. The header ContentPresenter has no TextTrimming, so header text currently clips without an ellipsis while its cells ellipsize correctly. Making headers trim and then auto-showing a tooltip only when trimmed — for headers and cells — is the natural follow-up, and is worth its own review: it changes the default for every table, and it would also close the non-string accessibility gap, since a truncation-driven tooltip is text by construction. Group-header tooltips remain deferred until grouping is enabled.

Validation

Built and verified locally:

  • Microsoft.UI.Xaml.Controls.dll and Microsoft.UI.Xaml.Controls.Tabular.dll build and link clean (x64).
  • The produced Microsoft.UI.Xaml.winmd carries TableViewColumn.HeaderToolTip with get_/put_ accessors plus HeaderToolTipProperty, read out of the metadata tables.
  • XamlMetadataProviderGenerated.h registers the DP, so HeaderToolTip="…" resolves from markup.
  • The regenerated CsWinRT projection exposes HeaderToolTip to C#, and the ToolTipsPage sample — extended here with header tooltips (text on Name and City, non-string content on Bio, and a tooltip on Score that repeats its own header text to exercise the suppression) plus a button that reads the header peers' help text — compiles against it.

The sample could not be run. TableViewSampleApp crashes at startup with 0xC000027B (stowed E_UNEXPECTED from Microsoft.UI.Xaml.dll) in this tree. That is pre-existing and not caused by this change: an A/B control built from main through the identical package pipeline, differing only in this commit, crashes identically. So the runtime behaviour — tooltip open, placement, teardown on rebuild, in-place re-apply, and the peer's help-text composition — is left to CI and to review.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@microsoft-github-policy-service microsoft-github-policy-service Bot added the needs-triage Issue needs to be triaged by the area owners label Sep 2, 2026
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>
@hiteshkrmsft
Hitesh Kumar (hiteshkrmsft) force-pushed the user/hik/tableview-header-tooltip branch from 2a7c77c to 00f2834 Compare September 2, 2026 10:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-triage Issue needs to be triaged by the area owners

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant