Skip to content

Fixed unreliable header tooltips (issue #728) - #1396

Open
TetzkatLipHoka wants to merge 1 commit into
JAM-Software:masterfrom
TetzkatLipHoka:fix/issue-728-header-hint
Open

Fixed unreliable header tooltips (issue #728)#1396
TetzkatLipHoka wants to merge 1 commit into
JAM-Software:masterfrom
TetzkatLipHoka:fix/issue-728-header-hint

Conversation

@TetzkatLipHoka

Copy link
Copy Markdown
Contributor

Fixes #728.

Root cause

The header occupies the window's non-client area (WMNCCalcSize reserves its height, WMNCHitTest returns HTBORDER), so hovering it produces WM_NCMOUSEMOVE messages. The stock THintWindow.IsHintMsg returns True for WM_NCMOUSEMOVE unconditionally, which makes TApplication.IsHintMsg call CancelHint and drop the pending header hint.

TVirtualTreeHintWindow.IsHintMsg already filters those messages out — but it only takes effect while Application.FHintWindow is an instance of that class. That is exactly why the reporters observed the hint working only after a tree hint had been shown (which makes Application.FHintWindow a TVirtualTreeHintWindow), and breaking again after another control's hint replaced it with a stock THintWindow. It also matches @gwideman's 2023 observation that activating another control's hint reliably breaks the header hints and scrolling a tree row restores them.

Both possible causes named by @joachimmarder in the issue discussion are two faces of this one mechanism.

Fix

Install TVirtualTreeHintWindow as the application-wide hint window class so its IsHintMsg override is always in effect. For hints that do not belong to a tree, it falls back to the stock rendering (the AData / FHintData.Tree = nil branches added to CalcHintRect and Paint), so other controls' hints look and behave exactly as before. Only the default THintWindow is replaced, leaving applications that install their own hint window class untouched.

The change is confined to VirtualTrees.AncestorVCL and contains no conditional compilation.

Tests

Tests/VTHeaderHintIssue728Tests.pas pins down both facts the fix relies on: that the class is registered application-wide, and that the foreign-control fallback returns a non-empty hint rect (it previously returned an empty rect, which is why the app-wide class could not simply be registered before).

The end-to-end cancellation behaviour was verified separately with a deterministic measurement harness that drives the real TApplication.IsHintMsg cancel path and counts visible hint windows before/after a synthetic WM_NCMOUSEMOVE:

Application.FHintWindow hint shown after WM_NCMOUSEMOVE
stock THintWindow yes cancelled
TVirtualTreeHintWindow (this fix) yes survives

Stable across repeated runs on Delphi 7 through 13.1.

The header occupies the window's non-client area (WMNCCalcSize reserves it,
WMNCHitTest returns HTBORDER), so hovering it produces WM_NCMOUSEMOVE messages.
The stock THintWindow.IsHintMsg tells the VCL to cancel a pending hint on every
such message. TVirtualTreeHintWindow.IsHintMsg already filters those out, but it
only takes effect while Application.FHintWindow is an instance of that class -
which is why header hints worked only once a tree hint had been shown and broke
again after another control's hint replaced the application's hint window.

Install TVirtualTreeHintWindow as the application-wide hint window class so the
override is always in effect. For hints that do not belong to a tree it falls
back to the stock rendering (the AData / FHintData.Tree = nil branches in
CalcHintRect and Paint), leaving other controls' hints unaffected. Only the
default THintWindow is replaced, so applications using their own hint window
class are left untouched.

Tests/VTHeaderHintIssue728Tests.pas pins down both facts (the registration and
the foreign-hint fallback); the end-to-end cancel behaviour is verified by a
deterministic measurement harness.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@joachimmarder joachimmarder self-assigned this Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Header tooltip not always displaying

2 participants