[Function] FFToolTip: Flicker-Free ToolTip

Post your working scripts, libraries and tools for AHK v1.1 and older
iPhilip
Posts: 814
Joined: 02 Oct 2013, 12:21

[Function] FFToolTip: Flicker-Free ToolTip

08 Mar 2019, 17:43

Hi Folks,

If you ever use a tooltip in a timer to show text next to the cursor, you will likely have noticed flickering of the tooltip window. The function below FFToolTip() addresses that problem. It functions much like the ToolTip command but without the flickering.

I provided two versions: a simpler one for single-monitor configurations and a more complex one for multi-monitor configurations. The later relies on the MDMF library, an updated version of which is included below. Of course, you can use the multi-monitor version for a single-monitor configuration.

For reference, Learning One posted a similar function here.

Here is the function for single-monitor configurations:

Code: Select all

; ===============================================================================================================================
; FFToolTip(Text:="", X:="", Y:="", WhichToolTip:=1)
; Function:       Creates a tooltip window anywhere on the screen. Unlike the built-in ToolTip command, calling this function
;                 repeatedly will not cause the tooltip window to flicker. Otherwise, it behaves the same way. Use this function
;                 without the first three parameters, i.e. FFToolTip(), in order to hide the tooltip.
; Parameters:     Text - The text to display in the tooltip. To create a multi-line tooltip, use the linefeed character (`n) in
;                    between each line, e.g. Line1`nLine2. If blank or omitted, the existing tooltip will be hidden.
;                 X - The x position of the tooltip. This position is relative to the active window, the active window's client
;                    area, or the entire screen depending on the coordinate mode (see the CoordMode command). In the default
;                    mode, the coordinates that are relative to the active window.
;                 Y - The y position of the tooltip. See the above X parameter for more information. If both the X and Y
;                    coordinates are omitted, the tooltip will be shown near the mouse cursor.
;                 WhichToolTip - A number between 1 and 20 to indicate which tooltip window to operate upon. If unspecified, the
;                    default is 1.
; Return values:  None
; Global vars:    None
; Dependencies:   None
; Tested with:    AHK 1.1.30.01 (A32/U32/U64)
; Tested on:      Win 7 (x64)
; Written by:     iPhilip
; ===============================================================================================================================
; MSDN Links:
; https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getcursorpos - GetCursorPos function
; https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-clienttoscreen - ClientToScreen function
; https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-movewindow - MoveWindow function
; ===============================================================================================================================

FFToolTip(Text:="", X:="", Y:="", WhichToolTip:=1) {
   static ID := [], Xo, Yo, W, H, SavedText
        , PID := DllCall("GetCurrentProcessId")
        , _ := VarSetCapacity(Point, 8)
   
   if (Text = "") {  ; Hide the tooltip
      ToolTip, , , , WhichToolTip
      ID.Delete(WhichToolTip)
   } else if not ID[WhichToolTip] {  ; First call
      ToolTip, %Text%, X, Y, WhichToolTip
      ID[WhichToolTip] := WinExist("ahk_class tooltips_class32 ahk_pid " PID)
      WinGetPos, , , W, H, % "ahk_id " ID[WhichToolTip]
      SavedText := Text
   } else if (Text != SavedText) {  ; The tooltip text changed
      ToolTip, %Text%, X, Y, WhichToolTip
      WinGetPos, , , W, H, % "ahk_id " ID[WhichToolTip]
      SavedText := Text
   } else {  ; The tooltip is being repositioned
      if (Flag := X = "" || Y = "") {
         DllCall("GetCursorPos", "Ptr", &Point, "Int")
         MouseX := NumGet(Point, 0, "Int")
         MouseY := NumGet(Point, 4, "Int")
      }
      ;
      ; Convert input coordinates to screen coordinates
      ;
      if (A_CoordModeToolTip = "Window") {
         WinGetPos, WinX, WinY, , , A
         X := X = "" ? MouseX + 16 : X + WinX
         Y := Y = "" ? MouseY + 16 : Y + WinY
      } else if (A_CoordModeToolTip = "Client") {
         NumPut(X, Point, 0, "Int"), NumPut(Y, Point, 4, "Int")
         DllCall("ClientToScreen", "Ptr", WinExist("A"), "Ptr", &Point, "Int")
         X := X = "" ? MouseX + 16 : NumGet(Point, 0, "Int")
         Y := Y = "" ? MouseY + 16 : NumGet(Point, 4, "Int")
      } else {  ; A_CoordModeToolTip = "Screen"
         X := X = "" ? MouseX + 16 : X
         Y := Y = "" ? MouseY + 16 : Y
      }
      ;
      ; Deal with the bottom and right edges of the screen
      ;
      if Flag {
         X := X + W >= A_ScreenWidth  ? A_ScreenWidth  - W - 1 : X
         Y := Y + H >= A_ScreenHeight ? A_ScreenHeight - H - 1 : Y
         if (MouseX >= X && MouseX <= X + W && MouseY >= Y && MouseY <= Y + H)
            X := MouseX - W - 3, Y := MouseY - H - 3
      }
      ;
      ; If necessary, store the coordinates and move the tooltip window
      ;
      if (X != Xo || Y != Yo) {
         Xo := X, Yo := Y
         DllCall("MoveWindow", "Ptr", ID[WhichToolTip], "Int", X, "Int", Y, "Int", W, "Int", H, "Int", false, "Int")
      }
   }
}
Here is a self-contained single-monitor example:

Code: Select all

#NoEnv
SetBatchLines, -1

ToolTipText := "I urge you`nto please`nnotice when`nyou are`nhappy...`n-Kurt Vonnegut"

MsgBox,
(
First, let's show a tooltip as it tracks with mouse movement
using the built-in method.`n
Notice the flickering of the tooltip window.`n
Click OK to start.
)
SetTimer, Timer1, 10
MsgBox
SetTimer, Timer1, Off
ToolTip
MsgBox,
(
Now, let's show a tooltip as it tracks with mouse movement
using the FFToolTip() function.`n
Notice the absence of any flickering and the behavior of the
tooltip window near the bottom-right corner of the screen.`n
Click OK to start.
)
SetTimer, Timer2, 10
MsgBox Click OK to exit.
SetTimer, Timer2, Off
FFToolTip()
ExitApp

Timer1:
ToolTip, %ToolTipText%
Return

Timer2:
FFToolTip(ToolTipText)
Return

Esc::ExitApp

; ===============================================================================================================================
; FFToolTip(Text:="", X:="", Y:="", WhichToolTip:=1)
; Function:       Creates a tooltip window anywhere on the screen. Unlike the built-in ToolTip command, calling this function
;                 repeatedly will not cause the tooltip window to flicker. Otherwise, it behaves the same way. Use this function
;                 without the first three parameters, i.e. FFToolTip(), in order to hide the tooltip.
; Parameters:     Text - The text to display in the tooltip. To create a multi-line tooltip, use the linefeed character (`n) in
;                    between each line, e.g. Line1`nLine2. If blank or omitted, the existing tooltip will be hidden.
;                 X - The x position of the tooltip. This position is relative to the active window, the active window's client
;                    area, or the entire screen depending on the coordinate mode (see the CoordMode command). In the default
;                    mode, the coordinates that are relative to the active window.
;                 Y - The y position of the tooltip. See the above X parameter for more information. If both the X and Y
;                    coordinates are omitted, the tooltip will be shown near the mouse cursor.
;                 WhichToolTip - A number between 1 and 20 to indicate which tooltip window to operate upon. If unspecified, the
;                    default is 1.
; Return values:  None
; Global vars:    None
; Dependencies:   None
; Tested with:    AHK 1.1.30.01 (A32/U32/U64)
; Tested on:      Win 7 (x64)
; Written by:     iPhilip
; ===============================================================================================================================
; MSDN Links:
; https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getcursorpos - GetCursorPos function
; https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-clienttoscreen - ClientToScreen function
; https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-movewindow - MoveWindow function
; ===============================================================================================================================

FFToolTip(Text:="", X:="", Y:="", WhichToolTip:=1) {
   static ID := [], Xo, Yo, W, H, SavedText
        , PID := DllCall("GetCurrentProcessId")
        , _ := VarSetCapacity(Point, 8)
   
   if (Text = "") {  ; Hide the tooltip
      ToolTip, , , , WhichToolTip
      ID.Delete(WhichToolTip)
   } else if not ID[WhichToolTip] {  ; First call
      ToolTip, %Text%, X, Y, WhichToolTip
      ID[WhichToolTip] := WinExist("ahk_class tooltips_class32 ahk_pid " PID)
      WinGetPos, , , W, H, % "ahk_id " ID[WhichToolTip]
      SavedText := Text
   } else if (Text != SavedText) {  ; The tooltip text changed
      ToolTip, %Text%, X, Y, WhichToolTip
      WinGetPos, , , W, H, % "ahk_id " ID[WhichToolTip]
      SavedText := Text
   } else {  ; The tooltip is being repositioned
      if (Flag := X = "" || Y = "") {
         DllCall("GetCursorPos", "Ptr", &Point, "Int")
         MouseX := NumGet(Point, 0, "Int")
         MouseY := NumGet(Point, 4, "Int")
      }
      ;
      ; Convert input coordinates to screen coordinates
      ;
      if (A_CoordModeToolTip = "Window") {
         WinGetPos, WinX, WinY, , , A
         X := X = "" ? MouseX + 16 : X + WinX
         Y := Y = "" ? MouseY + 16 : Y + WinY
      } else if (A_CoordModeToolTip = "Client") {
         NumPut(X, Point, 0, "Int"), NumPut(Y, Point, 4, "Int")
         DllCall("ClientToScreen", "Ptr", WinExist("A"), "Ptr", &Point, "Int")
         X := X = "" ? MouseX + 16 : NumGet(Point, 0, "Int")
         Y := Y = "" ? MouseY + 16 : NumGet(Point, 4, "Int")
      } else {  ; A_CoordModeToolTip = "Screen"
         X := X = "" ? MouseX + 16 : X
         Y := Y = "" ? MouseY + 16 : Y
      }
      ;
      ; Deal with the bottom and right edges of the screen
      ;
      if Flag {
         X := X + W >= A_ScreenWidth  ? A_ScreenWidth  - W - 1 : X
         Y := Y + H >= A_ScreenHeight ? A_ScreenHeight - H - 1 : Y
         if (MouseX >= X && MouseX <= X + W && MouseY >= Y && MouseY <= Y + H)
            X := MouseX - W - 3, Y := MouseY - H - 3
      }
      ;
      ; If necessary, store the coordinates and move the tooltip window
      ;
      if (X != Xo || Y != Yo) {
         Xo := X, Yo := Y
         DllCall("MoveWindow", "Ptr", ID[WhichToolTip], "Int", X, "Int", Y, "Int", W, "Int", H, "Int", false, "Int")
      }
   }
}
Here is the function for multi-monitor configurations (including the MDMF library):

Code: Select all

; ===============================================================================================================================
; FFToolTip(Text:="", X:="", Y:="", WhichToolTip:=1)
; Function:       Creates a tooltip window on any screen in a multiple monitor environment. Unlike the built-in ToolTip command,
;                 calling this function repeatedly will not cause the tooltip window to flicker. Furthermore, if one or both of
;                 the coordinates is not specified, the tooltip window will not hide the bottom-right corner of any of the
;                 monitors. Use this function without the first three parameters, i.e. FFToolTip(), in order to hide the tooltip.
; Parameters:     Text - The text to display in the tooltip. To create a multi-line tooltip, use the linefeed character (`n) in
;                    between each line, e.g. Line1`nLine2. If blank or omitted, the existing tooltip will be hidden.
;                 X - The x position of the tooltip. This position is relative to the active window, the active window's client
;                    area, or the entire screen depending on the coordinate mode (see the CoordMode command). In the default
;                    mode, the coordinates that are relative to the active window.
;                 Y - The y position of the tooltip. See the above X parameter for more information. If both the X and Y
;                    coordinates are omitted, the tooltip will be shown near the mouse cursor.
;                 WhichToolTip - A number between 1 and 20 to indicate which tooltip window to operate upon. If unspecified, the
;                    default is 1.
; Return values:  None
; Global vars:    None
; Dependencies:   MDMF Library (https://www.autohotkey.com/boards/viewtopic.php?p=266388#p266388)
; Tested with:    AHK 1.1.30.01 (A32/U32/U64)
; Tested on:      Win 7 (x64)
; Written by:     iPhilip
; ===============================================================================================================================
; MSDN Links:
; https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getcursorpos - GetCursorPos function
; https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-clienttoscreen - ClientToScreen function
; https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-movewindow - MoveWindow function
; ===============================================================================================================================

FFToolTip(Text:="", X:="", Y:="", WhichToolTip:=1) {
   static ID := [], Xo, Yo, W, H, SavedText, Monitors
        , PID := DllCall("GetCurrentProcessId")
        , _ := VarSetCapacity(Point, 8)
   
   if not Monitors
      Monitors := MDMF_Enum()
   if (Text = "") {  ; Hide the tooltip
      ToolTip, , , , WhichToolTip
      ID.Delete(WhichToolTip)
   } else if not ID[WhichToolTip] {  ; First call
      ToolTip, %Text%, X, Y, WhichToolTip
      ID[WhichToolTip] := WinExist("ahk_class tooltips_class32 ahk_pid " PID)
      WinGetPos, , , W, H, % "ahk_id " ID[WhichToolTip]
      SavedText := Text
   } else if (Text != SavedText) {  ; The tooltip text changed
      ToolTip, %Text%, X, Y, WhichToolTip
      WinGetPos, , , W, H, % "ahk_id " ID[WhichToolTip]
      SavedText := Text
   } else {  ; The tooltip is being repositioned
      if (Flag := X = "" || Y = "") {
         DllCall("GetCursorPos", "Ptr", &Point, "Int")
         MouseX := NumGet(Point, 0, "Int"), MouseY := NumGet(Point, 4, "Int")
         hMonitor := MDMF_FromPoint(X = "" ? MouseX : X, Y = "" ? MouseY : Y)
         MonitorRight := Monitors[hMonitor].Right
         MonitorBottom := Monitors[hMonitor].Bottom
      }
      ;
      ; Convert input coordinates to screen coordinates
      ;
      if (A_CoordModeToolTip = "Window") {
         WinGetPos, WinX, WinY, , , A
         X := X = "" ? MouseX + 16 : X + WinX
         Y := Y = "" ? MouseY + 16 : Y + WinY
      } else if (A_CoordModeToolTip = "Client") {
         NumPut(X, Point, 0, "Int"), NumPut(Y, Point, 4, "Int")
         DllCall("ClientToScreen", "Ptr", WinExist("A"), "Ptr", &Point, "Int")
         X := X = "" ? MouseX + 16 : NumGet(Point, 0, "Int")
         Y := Y = "" ? MouseY + 16 : NumGet(Point, 4, "Int")
      } else {  ; A_CoordModeToolTip = "Screen"
         X := X = "" ? MouseX + 16 : X
         Y := Y = "" ? MouseY + 16 : Y
      }
      ;
      ; Deal with the bottom and right edges of the current monitor
      ;
      if Flag {
         X := X + W >= MonitorRight  ? MonitorRight  - W - 1 : X
         Y := Y + H >= MonitorBottom ? MonitorBottom - H - 1 : Y
         if (MouseX >= X && MouseX <= X + W && MouseY >= Y && MouseY <= Y + H)
            X := MouseX - W - 3, Y := MouseY - H - 3
      }
      ;
      ; If necessary, store the coordinates and move the tooltip window
      ;
      if (X != Xo || Y != Yo) {
         Xo := X, Yo := Y
         DllCall("MoveWindow", "Ptr", ID[WhichToolTip], "Int", X, "Int", Y, "Int", W, "Int", H, "Int", false, "Int")
      }
   }
}

; ----------------------------------------------------------------------------------------------------------------------
; Name ..........: MDMF - Multiple Display Monitor Functions
; Description ...: Various functions for multiple display monitor environments
; Tested with ...: AHK 1.1.30.01 (A32/U32/U64) and 2.0-a100-52515e2 (U32/U64)
; Original Author: just me (https://www.autohotkey.com/boards/viewtopic.php?f=6&t=4606)
; Mod Author ....: iPhilip
; Changes .......: Modified MDMF_Enum() so that it works under both AHK v1 and v2.
; ................ Modified MDMF_EnumProc() to provide Count and Primary keys to the Monitors array.
; ................ Modified MDMF_FromHWND() to allow flag values that determine the function's return value if the
; ................    window does not intersect any display monitor.
; ................ Modified MDMF_FromPoint() to allow the cursor position to be returned ByRef if not specified and
; ................    allow flag values that determine the function's return value if the point is not contained within
; ................    any display monitor.
; ................ Modified MDMF_FromRect() to allow flag values that determine the function's return value if the
; ................    rectangle does not intersect any display monitor.
;................. Modified MDMF_GetInfo() with minor changes.
; ----------------------------------------------------------------------------------------------------------------------
;
; ======================================================================================================================
; Multiple Display Monitors Functions -> msdn.microsoft.com/en-us/library/dd145072(v=vs.85).aspx =======================
; ======================================================================================================================
; Enumerates display monitors and returns an object containing the properties of all monitors or the specified monitor.
; ======================================================================================================================
MDMF_Enum(HMON := "") {
   Static CallbackFunc := Func(A_AhkVersion < "2" ? "RegisterCallback" : "CallbackCreate")
   Static EnumProc := CallbackFunc.Call("MDMF_EnumProc")
   Static Monitors := {}
   If (HMON = "") ; new enumeration
      Monitors := {Count: 0}
   If (Monitors.MaxIndex() = "") ; enumerate
      If !DllCall("User32.dll\EnumDisplayMonitors", "Ptr", 0, "Ptr", 0, "Ptr", EnumProc, "Ptr", &Monitors, "Int")
         Return False
   Return (HMON = "") ? Monitors : Monitors.HasKey(HMON) ? Monitors[HMON] : False
}
; ======================================================================================================================
;  Callback function that is called by the MDMF_Enum function.
; ======================================================================================================================
MDMF_EnumProc(HMON, HDC, PRECT, ObjectAddr) {
   Monitors := Object(ObjectAddr)
   Monitors[HMON] := MDMF_GetInfo(HMON)
   Monitors.Count++
   If (Monitors[HMON].Primary)
      Monitors.Primary := HMON
   Return True
}
; ======================================================================================================================
; Retrieves the display monitor that has the largest area of intersection with a specified window.
; The following flag values determine the function's return value if the window does not intersect any display monitor:
;    MONITOR_DEFAULTTONULL    = 0 - Returns NULL.
;    MONITOR_DEFAULTTOPRIMARY = 1 - Returns a handle to the primary display monitor. 
;    MONITOR_DEFAULTTONEAREST = 2 - Returns a handle to the display monitor that is nearest to the window.
; ======================================================================================================================
MDMF_FromHWND(HWND, Flag := 0) {
   Return DllCall("User32.dll\MonitorFromWindow", "Ptr", HWND, "UInt", Flag, "Ptr")
}
; ======================================================================================================================
; Retrieves the display monitor that contains a specified point.
; If either X or Y is empty, the function will use the current cursor position for this value and return it ByRef.
; The following flag values determine the function's return value if the point is not contained within any
; display monitor:
;    MONITOR_DEFAULTTONULL    = 0 - Returns NULL.
;    MONITOR_DEFAULTTOPRIMARY = 1 - Returns a handle to the primary display monitor. 
;    MONITOR_DEFAULTTONEAREST = 2 - Returns a handle to the display monitor that is nearest to the window.
; ======================================================================================================================
MDMF_FromPoint(ByRef X := "", ByRef Y := "", Flag := 0) {
   If (X = "") || (Y = "") {
      VarSetCapacity(PT, 8, 0)
      DllCall("User32.dll\GetCursorPos", "Ptr", &PT, "Int")
      If (X = "")
         X := NumGet(PT, 0, "Int")
      If (Y = "")
         Y := NumGet(PT, 4, "Int")
   }
   Return DllCall("User32.dll\MonitorFromPoint", "Int64", (X & 0xFFFFFFFF) | (Y << 32), "UInt", Flag, "Ptr")
}
; ======================================================================================================================
; Retrieves the display monitor that has the largest area of intersection with a specified rectangle.
; Parameters are consistent with the common AHK definition of a rectangle, which is X, Y, W, H instead of
; Left, Top, Right, Bottom.
; The following flag values determine the function's return value if the rectangle does not intersect any
; display monitor:
;    MONITOR_DEFAULTTONULL    = 0 - Returns NULL.
;    MONITOR_DEFAULTTOPRIMARY = 1 - Returns a handle to the primary display monitor. 
;    MONITOR_DEFAULTTONEAREST = 2 - Returns a handle to the display monitor that is nearest to the window.
; ======================================================================================================================
MDMF_FromRect(X, Y, W, H, Flag := 0) {
   VarSetCapacity(RC, 16, 0)
   NumPut(X, RC, 0, "Int"), NumPut(Y, RC, 4, "Int"), NumPut(X + W, RC, 8, "Int"), NumPut(Y + H, RC, 12, "Int")
   Return DllCall("User32.dll\MonitorFromRect", "Ptr", &RC, "UInt", Flag, "Ptr")
}
; ======================================================================================================================
; Retrieves information about a display monitor.
; ======================================================================================================================
MDMF_GetInfo(HMON) {
   NumPut(VarSetCapacity(MIEX, 40 + (32 << !!A_IsUnicode)), MIEX, 0, "UInt")
   If DllCall("User32.dll\GetMonitorInfo", "Ptr", HMON, "Ptr", &MIEX, "Int")
      Return {Name:      (Name := StrGet(&MIEX + 40, 32))  ; CCHDEVICENAME = 32
            , Num:       RegExReplace(Name, ".*(\d+)$", "$1")
            , Left:      NumGet(MIEX, 4, "Int")    ; display rectangle
            , Top:       NumGet(MIEX, 8, "Int")    ; "
            , Right:     NumGet(MIEX, 12, "Int")   ; "
            , Bottom:    NumGet(MIEX, 16, "Int")   ; "
            , WALeft:    NumGet(MIEX, 20, "Int")   ; work area
            , WATop:     NumGet(MIEX, 24, "Int")   ; "
            , WARight:   NumGet(MIEX, 28, "Int")   ; "
            , WABottom:  NumGet(MIEX, 32, "Int")   ; "
            , Primary:   NumGet(MIEX, 36, "UInt")} ; contains a non-zero value for the primary monitor.
   Return False
}
Here is a self-contained multi-monitor example:

Code: Select all

#NoEnv
SetBatchLines, -1

ToolTipText := "I urge you`nto please`nnotice when`nyou are`nhappy...`n-Kurt Vonnegut"

MsgBox,
(
First, let's show a tooltip as it tracks with mouse movement
using the built-in method.`n
Notice the flickering of the tooltip window.`n
Click OK to start.
)
SetTimer, Timer1, 10
MsgBox
SetTimer, Timer1, Off
ToolTip
MsgBox,
(
Now, let's show a tooltip as it tracks with mouse movement
using the FFToolTip() function.`n
Notice the absence of any flickering and the behavior of the
tooltip window near the bottom-right corner of the screen.`n
Click OK to start.
)
SetTimer, Timer2, 10
MsgBox Click OK to exit.
SetTimer, Timer2, Off
FFToolTip()
ExitApp

Timer1:
ToolTip, %ToolTipText%
Return

Timer2:
FFToolTip(ToolTipText)
Return

Esc::ExitApp

; ===============================================================================================================================
; FFToolTip(Text:="", X:="", Y:="", WhichToolTip:=1)
; Function:       Creates a tooltip window on any screen in a multiple monitor environment. Unlike the built-in ToolTip command,
;                 calling this function repeatedly will not cause the tooltip window to flicker. Furthermore, if one or both of
;                 the coordinates is not specified, the tooltip window will not hide the bottom-right corner of any of the
;                 monitors. Use this function without the first three parameters, i.e. FFToolTip(), in order to hide the tooltip.
; Parameters:     Text - The text to display in the tooltip. To create a multi-line tooltip, use the linefeed character (`n) in
;                    between each line, e.g. Line1`nLine2. If blank or omitted, the existing tooltip will be hidden.
;                 X - The x position of the tooltip. This position is relative to the active window, the active window's client
;                    area, or the entire screen depending on the coordinate mode (see the CoordMode command). In the default
;                    mode, the coordinates that are relative to the active window.
;                 Y - The y position of the tooltip. See the above X parameter for more information. If both the X and Y
;                    coordinates are omitted, the tooltip will be shown near the mouse cursor.
;                 WhichToolTip - A number between 1 and 20 to indicate which tooltip window to operate upon. If unspecified, the
;                    default is 1.
; Return values:  None
; Global vars:    None
; Dependencies:   MDMF Library (https://www.autohotkey.com/boards/viewtopic.php?p=266388#p266388)
; Tested with:    AHK 1.1.30.01 (A32/U32/U64)
; Tested on:      Win 7 (x64)
; Written by:     iPhilip
; ===============================================================================================================================
; MSDN Links:
; https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getcursorpos - GetCursorPos function
; https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-clienttoscreen - ClientToScreen function
; https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-movewindow - MoveWindow function
; ===============================================================================================================================

FFToolTip(Text:="", X:="", Y:="", WhichToolTip:=1) {
   static ID := [], Xo, Yo, W, H, SavedText, Monitors
        , PID := DllCall("GetCurrentProcessId")
        , _ := VarSetCapacity(Point, 8)
   
   if not Monitors
      Monitors := MDMF_Enum()
   if (Text = "") {  ; Hide the tooltip
      ToolTip, , , , WhichToolTip
      ID.Delete(WhichToolTip)
   } else if not ID[WhichToolTip] {  ; First call
      ToolTip, %Text%, X, Y, WhichToolTip
      ID[WhichToolTip] := WinExist("ahk_class tooltips_class32 ahk_pid " PID)
      WinGetPos, , , W, H, % "ahk_id " ID[WhichToolTip]
      SavedText := Text
   } else if (Text != SavedText) {  ; The tooltip text changed
      ToolTip, %Text%, X, Y, WhichToolTip
      WinGetPos, , , W, H, % "ahk_id " ID[WhichToolTip]
      SavedText := Text
   } else {  ; The tooltip is being repositioned
      if (Flag := X = "" || Y = "") {
         DllCall("GetCursorPos", "Ptr", &Point, "Int")
         MouseX := NumGet(Point, 0, "Int"), MouseY := NumGet(Point, 4, "Int")
         hMonitor := MDMF_FromPoint(X = "" ? MouseX : X, Y = "" ? MouseY : Y)
         MonitorRight := Monitors[hMonitor].Right
         MonitorBottom := Monitors[hMonitor].Bottom
      }
      ;
      ; Convert input coordinates to screen coordinates
      ;
      if (A_CoordModeToolTip = "Window") {
         WinGetPos, WinX, WinY, , , A
         X := X = "" ? MouseX + 16 : X + WinX
         Y := Y = "" ? MouseY + 16 : Y + WinY
      } else if (A_CoordModeToolTip = "Client") {
         NumPut(X, Point, 0, "Int"), NumPut(Y, Point, 4, "Int")
         DllCall("ClientToScreen", "Ptr", WinExist("A"), "Ptr", &Point, "Int")
         X := X = "" ? MouseX + 16 : NumGet(Point, 0, "Int")
         Y := Y = "" ? MouseY + 16 : NumGet(Point, 4, "Int")
      } else {  ; A_CoordModeToolTip = "Screen"
         X := X = "" ? MouseX + 16 : X
         Y := Y = "" ? MouseY + 16 : Y
      }
      ;
      ; Deal with the bottom and right edges of the current monitor
      ;
      if Flag {
         X := X + W >= MonitorRight  ? MonitorRight  - W - 1 : X
         Y := Y + H >= MonitorBottom ? MonitorBottom - H - 1 : Y
         if (MouseX >= X && MouseX <= X + W && MouseY >= Y && MouseY <= Y + H)
            X := MouseX - W - 3, Y := MouseY - H - 3
      }
      ;
      ; If necessary, store the coordinates and move the tooltip window
      ;
      if (X != Xo || Y != Yo) {
         Xo := X, Yo := Y
         DllCall("MoveWindow", "Ptr", ID[WhichToolTip], "Int", X, "Int", Y, "Int", W, "Int", H, "Int", false, "Int")
      }
   }
}

; ----------------------------------------------------------------------------------------------------------------------
; Name ..........: MDMF - Multiple Display Monitor Functions
; Description ...: Various functions for multiple display monitor environments
; Tested with ...: AHK 1.1.30.01 (A32/U32/U64) and 2.0-a100-52515e2 (U32/U64)
; Original Author: just me (https://www.autohotkey.com/boards/viewtopic.php?f=6&t=4606)
; Mod Author ....: iPhilip
; Changes .......: Modified MDMF_Enum() so that it works under both AHK v1 and v2.
; ................ Modified MDMF_EnumProc() to provide Count and Primary keys to the Monitors array.
; ................ Modified MDMF_FromHWND() to allow flag values that determine the function's return value if the
; ................    window does not intersect any display monitor.
; ................ Modified MDMF_FromPoint() to allow the cursor position to be returned ByRef if not specified and
; ................    allow flag values that determine the function's return value if the point is not contained within
; ................    any display monitor.
; ................ Modified MDMF_FromRect() to allow flag values that determine the function's return value if the
; ................    rectangle does not intersect any display monitor.
;................. Modified MDMF_GetInfo() with minor changes.
; ----------------------------------------------------------------------------------------------------------------------
;
; ======================================================================================================================
; Multiple Display Monitors Functions -> msdn.microsoft.com/en-us/library/dd145072(v=vs.85).aspx =======================
; ======================================================================================================================
; Enumerates display monitors and returns an object containing the properties of all monitors or the specified monitor.
; ======================================================================================================================
MDMF_Enum(HMON := "") {
   Static CallbackFunc := Func(A_AhkVersion < "2" ? "RegisterCallback" : "CallbackCreate")
   Static EnumProc := CallbackFunc.Call("MDMF_EnumProc")
   Static Monitors := {}
   If (HMON = "") ; new enumeration
      Monitors := {Count: 0}
   If (Monitors.MaxIndex() = "") ; enumerate
      If !DllCall("User32.dll\EnumDisplayMonitors", "Ptr", 0, "Ptr", 0, "Ptr", EnumProc, "Ptr", &Monitors, "Int")
         Return False
   Return (HMON = "") ? Monitors : Monitors.HasKey(HMON) ? Monitors[HMON] : False
}
; ======================================================================================================================
;  Callback function that is called by the MDMF_Enum function.
; ======================================================================================================================
MDMF_EnumProc(HMON, HDC, PRECT, ObjectAddr) {
   Monitors := Object(ObjectAddr)
   Monitors[HMON] := MDMF_GetInfo(HMON)
   Monitors.Count++
   If (Monitors[HMON].Primary)
      Monitors.Primary := HMON
   Return True
}
; ======================================================================================================================
; Retrieves the display monitor that has the largest area of intersection with a specified window.
; The following flag values determine the function's return value if the window does not intersect any display monitor:
;    MONITOR_DEFAULTTONULL    = 0 - Returns NULL.
;    MONITOR_DEFAULTTOPRIMARY = 1 - Returns a handle to the primary display monitor. 
;    MONITOR_DEFAULTTONEAREST = 2 - Returns a handle to the display monitor that is nearest to the window.
; ======================================================================================================================
MDMF_FromHWND(HWND, Flag := 0) {
   Return DllCall("User32.dll\MonitorFromWindow", "Ptr", HWND, "UInt", Flag, "Ptr")
}
; ======================================================================================================================
; Retrieves the display monitor that contains a specified point.
; If either X or Y is empty, the function will use the current cursor position for this value and return it ByRef.
; The following flag values determine the function's return value if the point is not contained within any
; display monitor:
;    MONITOR_DEFAULTTONULL    = 0 - Returns NULL.
;    MONITOR_DEFAULTTOPRIMARY = 1 - Returns a handle to the primary display monitor. 
;    MONITOR_DEFAULTTONEAREST = 2 - Returns a handle to the display monitor that is nearest to the window.
; ======================================================================================================================
MDMF_FromPoint(ByRef X := "", ByRef Y := "", Flag := 0) {
   If (X = "") || (Y = "") {
      VarSetCapacity(PT, 8, 0)
      DllCall("User32.dll\GetCursorPos", "Ptr", &PT, "Int")
      If (X = "")
         X := NumGet(PT, 0, "Int")
      If (Y = "")
         Y := NumGet(PT, 4, "Int")
   }
   Return DllCall("User32.dll\MonitorFromPoint", "Int64", (X & 0xFFFFFFFF) | (Y << 32), "UInt", Flag, "Ptr")
}
; ======================================================================================================================
; Retrieves the display monitor that has the largest area of intersection with a specified rectangle.
; Parameters are consistent with the common AHK definition of a rectangle, which is X, Y, W, H instead of
; Left, Top, Right, Bottom.
; The following flag values determine the function's return value if the rectangle does not intersect any
; display monitor:
;    MONITOR_DEFAULTTONULL    = 0 - Returns NULL.
;    MONITOR_DEFAULTTOPRIMARY = 1 - Returns a handle to the primary display monitor. 
;    MONITOR_DEFAULTTONEAREST = 2 - Returns a handle to the display monitor that is nearest to the window.
; ======================================================================================================================
MDMF_FromRect(X, Y, W, H, Flag := 0) {
   VarSetCapacity(RC, 16, 0)
   NumPut(X, RC, 0, "Int"), NumPut(Y, RC, 4, "Int"), NumPut(X + W, RC, 8, "Int"), NumPut(Y + H, RC, 12, "Int")
   Return DllCall("User32.dll\MonitorFromRect", "Ptr", &RC, "UInt", Flag, "Ptr")
}
; ======================================================================================================================
; Retrieves information about a display monitor.
; ======================================================================================================================
MDMF_GetInfo(HMON) {
   NumPut(VarSetCapacity(MIEX, 40 + (32 << !!A_IsUnicode)), MIEX, 0, "UInt")
   If DllCall("User32.dll\GetMonitorInfo", "Ptr", HMON, "Ptr", &MIEX, "Int")
      Return {Name:      (Name := StrGet(&MIEX + 40, 32))  ; CCHDEVICENAME = 32
            , Num:       RegExReplace(Name, ".*(\d+)$", "$1")
            , Left:      NumGet(MIEX, 4, "Int")    ; display rectangle
            , Top:       NumGet(MIEX, 8, "Int")    ; "
            , Right:     NumGet(MIEX, 12, "Int")   ; "
            , Bottom:    NumGet(MIEX, 16, "Int")   ; "
            , WALeft:    NumGet(MIEX, 20, "Int")   ; work area
            , WATop:     NumGet(MIEX, 24, "Int")   ; "
            , WARight:   NumGet(MIEX, 28, "Int")   ; "
            , WABottom:  NumGet(MIEX, 32, "Int")   ; "
            , Primary:   NumGet(MIEX, 36, "UInt")} ; contains a non-zero value for the primary monitor.
   Return False
}
AHK v2 versions

I hope you find it useful.

- iPhilip
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: [Function] FFToolTip: Flicker-Free ToolTip

10 Mar 2019, 06:37

I tested the single-monitor v2 example, it works great. Good job and thanks for sharing :clap: .

Cheers.
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: [Function] FFToolTip: Flicker-Free ToolTip

10 Mar 2019, 16:50

I tested the multiple monitors one on AHK v1 and windows 10, it displays the Tooltips, but it doesn't move them when I move the mouse, only when the text changes....
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.
iPhilip
Posts: 814
Joined: 02 Oct 2013, 12:21

Re: [Function] FFToolTip: Flicker-Free ToolTip

10 Mar 2019, 18:56

Hi robodesign,

Thank you for the feedback from your tests. Can you post what you used to do your tests? The text doesn't change in the example script that I posted above.

Thank you,

- iPhilip
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
iPhilip
Posts: 814
Joined: 02 Oct 2013, 12:21

Re: [Function] FFToolTip: Flicker-Free ToolTip

10 Mar 2019, 21:37

Helgef wrote:
10 Mar 2019, 06:37
I tested the single-monitor v2 example, it works great. Good job and thanks for sharing :clap: .

Cheers.
Hi Helgef,

Thank you. I appreciate your testing and encouraging words. :)

- iPhilip
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: [Function] FFToolTip: Flicker-Free ToolTip

11 Mar 2019, 08:43

Hello!

@Philip.

I have just a simple timer that runs every 200 millimeters.

The timer only calls your function and I pass to it a global variable. When the string changes the tooltip appears at the mouse position.... , if it doesn't change, the tooltip remains in place.

I use the 64-bit version.

Best regards, Marius.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.
guest3456
Posts: 3462
Joined: 09 Oct 2013, 10:31

Re: [Function] FFToolTip: Flicker-Free ToolTip

11 Mar 2019, 09:14

excellent, will be using this

burque505
Posts: 1732
Joined: 22 Jan 2017, 19:37

Re: [Function] FFToolTip: Flicker-Free ToolTip

11 Mar 2019, 10:24

@iPhilip, thanks for this. Very nice work and nicely commented. I can only test the single-monitor version(s), but on my Win7 64-bit setup (AHK_L 1.1.30.01) it's fantastic. Quite an improvement.
Regard,
burque505
iPhilip
Posts: 814
Joined: 02 Oct 2013, 12:21

Re: [Function] FFToolTip: Flicker-Free ToolTip

11 Mar 2019, 16:16

robodesign wrote:
11 Mar 2019, 08:43
Hello!

@Philip.

I have just a simple timer that runs every 200 millimeters.
I think you meant milliseconds. :)
robodesign wrote:
11 Mar 2019, 08:43
The timer only calls your function and I pass to it a global variable. When the string changes the tooltip appears at the mouse position.... , if it doesn't change, the tooltip remains in place.

I use the 64-bit version.

Best regards, Marius.
I tested the above multi-monitor example under Windows 7 Pro (64 bit) and Windows 10 Enterprise (64 bit) with AutoHotkey 1.1.30.01 (A32/U32/U64). It worked fine under both systems and all 3 versions of AutoHotkey. If you are willing to post a version of your code, I would be happy to take a look at it. Alternatively, I would be interested in knowing if the above script (unmodified) works for you.

Thank you,

- iPhilip
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: [Function] FFToolTip: Flicker-Free ToolTip

11 Mar 2019, 17:59

Hello, I will try it again, unmodified.

I admit I was a bit unorthodox....
I copied and pasted your functions into my KeyPress OSD script.... Where I have a simple timer which displays texts on screen, whatever the OSD is showing it's shown as a tooltip as well. So, something in my script might be messing up with your functions. In particular, with the updating of the tooltip position.

I also use AHK-H.

Best regards, Marius.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.
iPhilip
Posts: 814
Joined: 02 Oct 2013, 12:21

Re: [Function] FFToolTip: Flicker-Free ToolTip

11 Mar 2019, 18:21

robodesign wrote:
11 Mar 2019, 17:59
Hello, I will try it again, unmodified.

I admit I was a bit unorthodox....
I copied and pasted your functions into my KeyPress OSD script.... Where I have a simple timer which displays texts on screen, whatever the OSD is showing it's shown as a tooltip as well. So, something in my script might be messing up with your functions. In particular, with the updating of the tooltip position.

I also use AHK-H.

Best regards, Marius.
FFToolTip was not designed to work with, nor was it tested with AHK-H. I would encourage you to put that information in your signature block.
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: [Function] FFToolTip: Flicker-Free ToolTip

13 Mar 2019, 06:24

Hello, again!

I tested your provided examples and they work with AHK_H :-). For your information , I never found a script or something to work with AHK_L, but not with AHK_H. So, do not be afraid of it :-).

The problem is just when I integrate it into my mammoth script, as I previously suggested....

Anyways, congratulations for the great function ;).

Best regards, Marius.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.
iPhilip
Posts: 814
Joined: 02 Oct 2013, 12:21

Re: [Function] FFToolTip: Flicker-Free ToolTip

13 Mar 2019, 12:35

Hi Marius,

Thank you for your comments and the additional testing. I appreciate it. Because of your testing, I put together a portable version of AutoHotkey that will make it easier for me to test my scripts on Windows 10. :)

- iPhilip
P.S.: I will keep in mind your comments about AHK_H.
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
iPhilip
Posts: 814
Joined: 02 Oct 2013, 12:21

Re: [Function] FFToolTip: Flicker-Free ToolTip

13 Mar 2019, 12:40

guest3456 wrote:
11 Mar 2019, 09:14
excellent, will be using this
Thank you, guest3456. :)
burque505 wrote:
11 Mar 2019, 10:24
@iPhilip, thanks for this. Very nice work and nicely commented. I can only test the single-monitor version(s), but on my Win7 64-bit setup (AHK_L 1.1.30.01) it's fantastic. Quite an improvement.
Regard,
burque505
Thank you, burque505. I appreciate it. :)

- iPhilip
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
User avatar
DataLife
Posts: 447
Joined: 29 Sep 2013, 19:52

Re: [Function] FFToolTip: Flicker-Free ToolTip

15 Mar 2019, 19:29

This is extremely nice, it should replace the standard tooltip.

thanks very much.
Check out my scripts. (MyIpChanger) (ClipBoard Manager) (SavePictureAs)
All my scripts are tested on Windows 10, AutoHotkey 32 bit Ansi unless otherwise stated.
User avatar
Cerberus
Posts: 172
Joined: 12 Jan 2016, 15:46

Re: [Function] FFToolTip: Flicker-Free ToolTip

17 Mar 2019, 22:00

This looks interesting, I'll try it when next I use tooltips!

P.S. Even after so many years, I continue to be astounded by how courteous and helpful everyone is at the Autohotkey fora.
mstrauss2021
Posts: 30
Joined: 13 Feb 2021, 10:34

Re: [Function] FFToolTip: Flicker-Free ToolTip

28 May 2021, 16:47

Just found this yesterday.
Total life saver. Using on Win 10 at work, however, for some reason when I do a multiline it shows the `n.
so Line1`nLine2 shows as Line1`nLine2
I put a MsgBox in your function to show the value of %Text% and it still shows as 1 line which I wasn't expecting.

The only ting I can think of is that I save my tooltips in an ini file and read them back. I don't know if that has anything to do with it.
burque505
Posts: 1732
Joined: 22 Jan 2017, 19:37

Re: [Function] FFToolTip: Flicker-Free ToolTip

28 May 2021, 17:00

@mstrauss2021, I bet you need continuation sections for your tooltips in your .ini file. From the docs for IniWrite:
Value

The string or number that will be written to the right of Key's equal sign (=).

If the text is long, it can be broken up into several shorter lines by means of a continuation section, which might improve readability and maintainability.
mstrauss2021
Posts: 30
Joined: 13 Feb 2021, 10:34

Re: [Function] FFToolTip: Flicker-Free ToolTip

02 Jun 2021, 13:29

Through testing I found that the funtion does something that prevents `n from starting a new line so to correct for it I added:

Reminder := StrReplace(Reminder, "&#x0D;&#x0A;", "`n") after reading my saved tooltip from ini

and

Reminder := StrReplace(Reminder, "`n", "&#x0D;&#x0A;") before saving to ini

This allows me to ro read the tooltips in multiline format
mstrauss2021
Posts: 30
Joined: 13 Feb 2021, 10:34

Re: [Function] FFToolTip: Flicker-Free ToolTip

24 Jun 2021, 12:12

Still using this script and loving it.
Got everything working the way I want, however...

I have the tooltip always following the mouse which is good, but now I realize the tooltip is kind of getting in the way.

I've read the comments in the script and tried altering X variable but no change.

How can I add 75 to the X position of the tooltip and which line needs to be changed?

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: DuyMinh and 57 guests