Tabular: add opt-in per-cell tooltips to TableView - #11669
Open
Hitesh Kumar (hiteshkrmsft) wants to merge 5 commits into
Open
Tabular: add opt-in per-cell tooltips to TableView#11669Hitesh Kumar (hiteshkrmsft) wants to merge 5 commits into
Hitesh Kumar (hiteshkrmsft) wants to merge 5 commits into
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Hitesh Kumar (hiteshkrmsft)
force-pushed
the
user/hik/tableview-cell-tooltips
branch
2 times, most recently
from
August 31, 2026 21:17
1262bcb to
9527bc1
Compare
Hitesh Kumar (hiteshkrmsft)
marked this pull request as ready for review
August 31, 2026 21:17
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
Author
|
/azp run |
|
Azure Pipelines: 1 pipeline(s) were filtered out due to trigger conditions. |
Hitesh Kumar (hiteshkrmsft)
force-pushed
the
user/hik/tableview-cell-tooltips
branch
from
September 1, 2026 13:28
9527bc1 to
2226e88
Compare
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>
Hitesh Kumar (hiteshkrmsft)
force-pushed
the
user/hik/tableview-cell-tooltips
branch
from
September 1, 2026 13:40
2226e88 to
46e4efc
Compare
…ipBinding 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
- 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
- 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
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>
Contributor
Author
|
/azp run |
|
Azure Pipelines: 1 pipeline(s) were filtered out due to trigger conditions. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds an opt-in per-cell tooltip to
TableView.Text cells render with
CharacterEllipsisand no wrapping, so a value wider than its column is unreadable.TableViewColumn.CellToolTipBindingsurfaces the full value:A CLR
Bindingproperty, matchingTableViewTextColumn.Binding. Use anIValueConverterfor computed content.Design
The binding is set once on the cell wrapper, so the row's
DataContextdrives it: a recycled row re-resolves through ordinary inheritance, and a sourcePropertyChangedupdates a live tooltip in place. There is no invalidation API, and the control never calls app code while realizing a cell — so no re-entrancy surface.The control owns the
ToolTipand its placement (PlacementMode.Mouse, matchingTabViewItem). The bound value is the tooltip's content, not aToolTip; aUIElementis parented by that cell's tooltip, so a converter returns a fresh element per evaluation. A private attached record holds the tooltip the control attached and theHelpTextit published, so a tooltip the app set — including a bare string — is left alone. On recycle, tooltips are neutralized in place rather than detached, also matchingTabViewItem.Accessibility
String content is published as the cell's
AutomationProperties.HelpText.TableViewCellAutomationPeer::GetHelpTextCoresuppresses it when it merely repeats the cell's own text, gated on the ownership record so text the app set is never dropped.Non-string content is mouse-only: no
HelpTextis published, and cell focus is row-level, so keyboard and screen-reader users get nothing.TabViewItemandNavigationViewItemrefuse non-string tooltip content for this reason. Use a converter that returns text when the value must be accessible; parity needs a public cell element, which is post-v1.Notes
PART_HeaderScrollerisIsHitTestVisible="False", so a header tooltip could never open.TableViewProperties, soXamlMetadataProviderGenerated.ttemits their include and clear call. Both are marked as hand edits.TableView.vcxitems; verified with a local Tabular build and the sample's Cell tooltips page.TableViewtest project, and Tabular is not inMUXControls.sln.