In short: Use WinMove with SetWinDelay -1.
Flicker occurs because of two things:
- 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.
- Whenever the tooltip's text or position is changed via the "proper" means, the entire window is updated and fully repainted.
- 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.
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.
}
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
}
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.)