ToolTip following mouse without flicker

Put simple Tips and Tricks that are not entire Tutorials in this forum
lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

ToolTip following mouse without flicker

Post by lexikos » 30 Apr 2022, 18:20

On Windows Vista and later, ToolTip has the unfortunate habit of flickering whenever its text or position is updated.

In short: Use WinMove with SetWinDelay -1. ;)

Flicker occurs because of two things:
  1. When the tooltip window paints, it first erases the window background, then paints the text. Sometimes the screen updates while the text is still being painted, so part of the text is momentarily invisible.
  2. Whenever the tooltip's text or position is changed via the "proper" means, the entire window is updated and fully repainted.
Some workarounds were presented for v1 in the following topic: ToolTip which follows the mouse is flickering - Archive Forum. I notice a couple of problems with the answers there:
  • TTM_TRACKPOSITION is presented as a solution to the flicker, but is insufficient. Large tooltips still flicker when they move. (There is further explanation at the bottom of my post.)
  • The code for TTM_TRACKPOSITION purports to support x64, but it uses GetCursorInfo and doesn't account for the CURSORINFO::hCursor member being larger on x64. I have no idea why it doesn't just use GetCursorPos.
  • MoveWindow is used via DllCall because it is "much faster"; that's only because A_WinDelay defaults to 100.
  • MoveWindow alone doesn't account for the mouse coming close to the screen edges.
More recently, some functions were posted here: [Function] FFToolTip: Flicker-Free ToolTip. The functions go to some effort to position the tooltip intelligently. There are multiple versions, with the multiple-monitor version being very long due to additional functions needed. The v2 version is currently outdated and the script unfortunately doesn't work correctly on the second screen in my setup, which has a different DPI setting to the primary screen. Otherwise, I hear it works quite well.

Before presenting my solution, I'll also point out that the ToolTip command/function itself doesn't correctly handle multiple monitors with v1.1.33.11 or v2.0-beta.3, but that's being fixed. Windows 10 (or thereabouts) has a tendency to put the tooltip at the top of the screen when it would otherwise overlap the taskbar, but that's also being fixed (with a workaround).

So what's the solution?

The simple fix is just WinMove.

Code: Select all

#Requires AutoHotkey v2.0-beta.3

text := "
(
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.
)"

ToolTip text

; Get *our* tooltip window.
ttw := WinExist("ahk_class tooltips_class32 ahk_pid " ProcessExist())

SetWinDelay -1 ; Remove the default delay of 100ms after each WinMove.
CoordMode "Mouse" ; Use screen coordinates to match WinMove.

Loop {
    MouseGetPos &x, &y
    WinMove x+16, y+16,,, 'ahk_id ' ttw
    Sleep 10 ; Rest the CPU a bit.
}
However, this doesn't account for the screen edges. It might seem the solution for that is to determine which screen the mouse is on, find its bounds, and limit the position according to a bunch of logical checks. If only there was a function that took care of all that and told us where to put the window. Windows has to have the code for this, to show standard system tooltips and context menus, right?

Actually, there's CalculatePopupWindowPosition in Windows 7 and later. This one function takes care of calculating a position for the window within the bounds of the appropriate screen (actually, its working area) with care to avoid passing out the sides of the screen, and optionally avoiding overlapping with a specific rectangle, such as the area around the mouse pointer.

Code: Select all

#Requires AutoHotkey v2.0-beta.3

text := "
(
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.
)"

ToolTip text

; Get *our* tooltip window.
ttw := WinExist("ahk_class tooltips_class32 ahk_pid " ProcessExist())

SetWinDelay -1 ; Remove the default delay of 100ms after each WinMove.
CoordMode "Mouse" ; Use screen coordinates to match WinMove.

anchorPt := Buffer(8)
windowRect := Buffer(16), windowSize := windowRect.ptr + 8
excludeRect := Buffer(16)
outRect := Buffer(16)

DllCall("GetClientRect", "ptr", ttw, "ptr", windowRect)

; Windows 7 permits overlap with the taskbar, whereas Windows 10 requires the
; tooltip to be within the work area (WinMove can subvert that, so this is just
; for consistency with the normal behaviour).
flags := VerCompare(A_OSVersion, "6.2") < 0 ? 0 : 0x10000 ; TPM_WORKAREA

Loop {
    MouseGetPos &x, &y
    
    ; ToolTip normally shows at an offset of 16,16 from the cursor.
    NumPut "int", x+16, "int", y+16, anchorPt
    
    ; Avoid the area around the mouse pointer.
    NumPut "int", x-3, "int", y-3, "int", x+3, "int", y+3, excludeRect
    
    DllCall("CalculatePopupWindowPosition"
        , "ptr", anchorPt
        , "ptr", windowSize
        , "uint", flags
        , "ptr", excludeRect
        , "ptr", outRect)
    
    WinMove NumGet(outRect, 0, 'int'), NumGet(outRect, 4, 'int'),,, ttw
    Sleep 10
}
This behaves similarly to the built-in ToolTip function, except that when the mouse is near the bottom-right corner of the screen, this will just move the tooltip to the left instead of moving it above and to the left of the pointer. A side-effect of that is that it doesn't "jump around" as much if you are moving the mouse around that area of the screen.

Note that this function also has flags to align to the right or centre, vertically or horizontally.



I have done quite a bit of testing and found that both the TTM_UPDATETEXT message used to change the text and the TTM_TRACKPOSITION message used to update the position cause a full refresh of the tooltip. This not only causes flicker, but also causes the ToolTip function to take longer. Future versions of AutoHotkey will be optimized to avoid sending TTM_UPDATETEXT if the text is the same, but unfortunately there is no way around TTM_TRACKPOSITION. If WinMove or equivalent is used instead, updating the tooltip's text will cause it to revert to its last "official" position, which is noticeable even if it is moved immediately after. So since I can't solve the problem fully, I am posting my findings here.

(If someone would like to add a brief note and example for avoiding flicker to the documentation, go for it.)

SandyClams
Posts: 63
Joined: 02 Jul 2020, 11:55

Re: ToolTip following mouse without flicker

Post by SandyClams » 15 May 2022, 21:36

I haven't had much use for tooltips personally, but I appreciate the heads up about CalculatePopupWindowPosition. Would've been ideal to know about it before I implemented my own version (probably multiple times), but I'm assuming this will be waaaaay faster than mine anyway so it'll still be very nice to have

Post Reply

Return to “Tips and Tricks”