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 am providing two versions below. One for AHK v1.1 and a second one for AHK v2.0. Both work for single-monitor and multiple-monitor configurations. Neither requires any additional function. If the DPI settings of multiple monitors don't match, use the workaround described in the documentation.
For reference, @lexikos provided a review of a few functions that address the same problem as well as a simple fix for AHK v2 here. In addition, @NPerovic posted ToolTipEx that does the same and more.
; ===============================================================================================================================
; FFToolTip(Text := "", X := "", Y := "", WhichToolTip := 1)
; Function: Creates a tooltip window on any screen in a single or multiple-monitor environment. Unlike the built-in ToolTip
; function, calling this function repeatedly will not cause the tooltip window to flicker. Otherwise, it behaves
; much the same way, except around the bottom-right corner of each monitor. If neither coordinate is specified,
; the tooltip window will not cover any part of the taskbar.
; 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 destroyed.
; - 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 function). 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: If the tooltip is being shown for the first time or is being updated, the function returns the handle of the
; tooltip window. If the Text parameter is blank or omitted, the function returns zero.
; Global vars: None
; Dependencies: None
; Requirements: AHK v1.1
; Tested with: AHK v1.1.37.02 (A32/U32/U64)
; Tested on: Win 10 Pro (x64)
; Written by: iPhilip
; Forum link: https://www.autohotkey.com/boards/viewtopic.php?f=6&t=62607
; ===============================================================================================================================
FFToolTip(Text := "", X := "", Y := "", WhichToolTip := 1) {
static TTS := []
static DeltaX := 16 ; Horizontal gap between the mouse and the left edge of the tooltip window
static DeltaY := 16 ; Vertical gap between the mouse and the top edge of the tooltip window
static DeltaZ := 3 ; Vertical gap between the mouse and the bottom edge of the tooltip window when in the bottom-right corner of the screen
static Flags := 0x10040 ; TPM_WORKAREA = 0x10000 | TPM_VERTICAL = 0x0040
static POS := VarSetCapacity(POS, 16)
static EXC := VarSetCapacity(EXC, 16)
static OUT := VarSetCapacity(OUT, 16)
static PID := DllCall("GetCurrentProcessId", "UInt")
local
if (Text = "") { ; Destroy the tooltip
ToolTip, , , , WhichToolTip
TTS.Delete(WhichToolTip)
hwnd := 0
} else if !TTS.HasKey(WhichToolTip) { ; First call
ToolTip, %Text%, X, Y, WhichToolTip
hwnd := WinExist("ahk_class tooltips_class32 ahk_pid " PID)
WinGetPos, X, Y, W, H, ahk_id %hwnd%
TTS[WhichToolTip] := {X:X, Y:Y, W:W, H:H, hwnd:hwnd, Text:Text}
} else if !(Text == TTS[WhichToolTip].Text) { ; The tooltip text changed.
ToolTip, %Text%, X, Y, WhichToolTip
hwnd := TTS[WhichToolTip].hwnd
WinGetPos, X, Y, W, H, ahk_id %hwnd%
TTS[WhichToolTip] := {X:X, Y:Y, W:W, H:H, hwnd:hwnd, Text:Text}
} else { ; The tooltip is being repositioned.
IsEmptyX := X = ""
IsEmptyY := Y = ""
if (IsEmptyX || IsEmptyY) {
DllCall("GetCursorPos", "Ptr", &POS, "Int")
MouseX := NumGet(POS, 0, "Int")
MouseY := NumGet(POS, 4, "Int")
}
;
; Convert input coordinates to screen coordinates.
;
if (A_CoordModeToolTip = "Window") {
WinGetPos, WinX, WinY, , , A
X := IsEmptyX ? MouseX : X + WinX
Y := IsEmptyY ? MouseY : Y + WinY
} else if (A_CoordModeToolTip = "Client") {
NumPut(IsEmptyX ? 0 : X, POS, 0, "Int")
NumPut(IsEmptyY ? 0 : Y, POS, 4, "Int")
DllCall("ClientToScreen", "Ptr", WinExist("A"), "Ptr", &POS, "Int")
X := IsEmptyX ? MouseX : NumGet(POS, 0, "Int")
Y := IsEmptyY ? MouseY : NumGet(POS, 4, "Int")
} else { ; A_CoordModeToolTip = "Screen"
X := IsEmptyX ? MouseX : X
Y := IsEmptyY ? MouseY : Y
}
W := TTS[WhichToolTip].W
H := TTS[WhichToolTip].H
;
; If neither coordinate is specified, force the tooltip window to be within the working area of the monitors.
;
if IsEmptyX && IsEmptyY
&& DllCall("SetRect", "Ptr", &POS, "Int", X + DeltaX, "Int", Y + DeltaY, "Int", W, "Int", H, "Int")
&& DllCall("SetRect", "Ptr", &EXC, "Int", X - DeltaZ, "Int", Y - DeltaZ, "Int", X + DeltaX, "Int", Y + DeltaY, "Int")
&& DllCall("CalculatePopupWindowPosition", "Ptr", &POS, "Ptr", &POS + 8, "UInt", Flags, "Ptr", &EXC, "Ptr", &OUT, "Int")
X := NumGet(OUT, 0, "Int"), Y := NumGet(OUT, 4, "Int")
;
; If necessary, store the coordinates and move the tooltip window.
;
hwnd := TTS[WhichToolTip].hwnd
if (X != TTS[WhichToolTip].X || Y != TTS[WhichToolTip].Y) {
TTS[WhichToolTip].X := X, TTS[WhichToolTip].Y := Y
DllCall("MoveWindow", "Ptr", hwnd, "Int", X, "Int", Y, "Int", W, "Int", H, "Int", false, "Int")
}
}
return hwnd
}
#Requires AutoHotkey v1.1.33
#NoEnv
ToolTipText := "
(
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.
)"
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 taskbar.`n
Click OK to continue.
)
SetTimer, Timer2, 10
MsgBox Click OK to exit.
SetTimer Timer2, Off
FFToolTip()
return
Timer1:
ToolTip, %ToolTipText%
Return
Timer2:
FFToolTip(ToolTipText)
Return
; ===============================================================================================================================
; FFToolTip(Text := "", X := "", Y := "", WhichToolTip := 1)
; Function: Creates a tooltip window on any screen in a single or multiple-monitor environment. Unlike the built-in ToolTip
; function, calling this function repeatedly will not cause the tooltip window to flicker. Otherwise, it behaves
; much the same way, except around the bottom-right corner of each monitor. If neither coordinate is specified,
; the tooltip window will not cover any part of the taskbar.
; 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 destroyed.
; - 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 function). 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: If the tooltip is being shown for the first time or is being updated, the function returns the handle of the
; tooltip window. If the Text parameter is blank or omitted, the function returns zero.
; Global vars: None
; Dependencies: None
; Requirements: AHK v1.1
; Tested with: AHK v1.1.37.02 (A32/U32/U64)
; Tested on: Win 10 Pro (x64)
; Written by: iPhilip
; Forum link: https://www.autohotkey.com/boards/viewtopic.php?f=6&t=62607
; ===============================================================================================================================
FFToolTip(Text := "", X := "", Y := "", WhichToolTip := 1) {
static TTS := []
static DeltaX := 16 ; Horizontal gap between the mouse and the left edge of the tooltip window
static DeltaY := 16 ; Vertical gap between the mouse and the top edge of the tooltip window
static DeltaZ := 3 ; Vertical gap between the mouse and the bottom edge of the tooltip window when in the bottom-right corner of the screen
static Flags := 0x10040 ; TPM_WORKAREA = 0x10000 | TPM_VERTICAL = 0x0040
static POS := VarSetCapacity(POS, 16)
static EXC := VarSetCapacity(EXC, 16)
static OUT := VarSetCapacity(OUT, 16)
static PID := DllCall("GetCurrentProcessId", "UInt")
local
if (Text = "") { ; Destroy the tooltip
ToolTip, , , , WhichToolTip
TTS.Delete(WhichToolTip)
hwnd := 0
} else if !TTS.HasKey(WhichToolTip) { ; First call
ToolTip, %Text%, X, Y, WhichToolTip
hwnd := WinExist("ahk_class tooltips_class32 ahk_pid " PID)
WinGetPos, X, Y, W, H, ahk_id %hwnd%
TTS[WhichToolTip] := {X:X, Y:Y, W:W, H:H, hwnd:hwnd, Text:Text}
} else if !(Text == TTS[WhichToolTip].Text) { ; The tooltip text changed.
ToolTip, %Text%, X, Y, WhichToolTip
hwnd := TTS[WhichToolTip].hwnd
WinGetPos, X, Y, W, H, ahk_id %hwnd%
TTS[WhichToolTip] := {X:X, Y:Y, W:W, H:H, hwnd:hwnd, Text:Text}
} else { ; The tooltip is being repositioned.
IsEmptyX := X = ""
IsEmptyY := Y = ""
if (IsEmptyX || IsEmptyY) {
DllCall("GetCursorPos", "Ptr", &POS, "Int")
MouseX := NumGet(POS, 0, "Int")
MouseY := NumGet(POS, 4, "Int")
}
;
; Convert input coordinates to screen coordinates.
;
if (A_CoordModeToolTip = "Window") {
WinGetPos, WinX, WinY, , , A
X := IsEmptyX ? MouseX : X + WinX
Y := IsEmptyY ? MouseY : Y + WinY
} else if (A_CoordModeToolTip = "Client") {
NumPut(IsEmptyX ? 0 : X, POS, 0, "Int")
NumPut(IsEmptyY ? 0 : Y, POS, 4, "Int")
DllCall("ClientToScreen", "Ptr", WinExist("A"), "Ptr", &POS, "Int")
X := IsEmptyX ? MouseX : NumGet(POS, 0, "Int")
Y := IsEmptyY ? MouseY : NumGet(POS, 4, "Int")
} else { ; A_CoordModeToolTip = "Screen"
X := IsEmptyX ? MouseX : X
Y := IsEmptyY ? MouseY : Y
}
W := TTS[WhichToolTip].W
H := TTS[WhichToolTip].H
;
; If neither coordinate is specified, force the tooltip window to be within the working area of the monitors.
;
if IsEmptyX && IsEmptyY
&& DllCall("SetRect", "Ptr", &POS, "Int", X + DeltaX, "Int", Y + DeltaY, "Int", W, "Int", H, "Int")
&& DllCall("SetRect", "Ptr", &EXC, "Int", X - DeltaZ, "Int", Y - DeltaZ, "Int", X + DeltaX, "Int", Y + DeltaY, "Int")
&& DllCall("CalculatePopupWindowPosition", "Ptr", &POS, "Ptr", &POS + 8, "UInt", Flags, "Ptr", &EXC, "Ptr", &OUT, "Int")
X := NumGet(OUT, 0, "Int"), Y := NumGet(OUT, 4, "Int")
;
; If necessary, store the coordinates and move the tooltip window.
;
hwnd := TTS[WhichToolTip].hwnd
if (X != TTS[WhichToolTip].X || Y != TTS[WhichToolTip].Y) {
TTS[WhichToolTip].X := X, TTS[WhichToolTip].Y := Y
DllCall("MoveWindow", "Ptr", hwnd, "Int", X, "Int", Y, "Int", W, "Int", H, "Int", false, "Int")
}
}
return hwnd
}
; ===============================================================================================================================
; FFToolTip(Text?, X?, Y?, WhichToolTip := 1)
; Function: Creates a tooltip window on any screen in a single or multiple-monitor environment. Unlike the built-in ToolTip
; function, calling this function repeatedly will not cause the tooltip window to flicker. Otherwise, it behaves
; much the same way, except around the bottom-right corner of each monitor. If neither coordinate is specified,
; the tooltip window will not cover any part of the taskbar.
; 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 destroyed.
; - 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 function). 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: If the tooltip is being shown for the first time or is being updated, the function returns the handle of the
; tooltip window. If the Text parameter is blank or omitted, the function returns zero.
; Global vars: None
; Dependencies: None
; Requirements: AHK v2.0
; Tested with: AHK v2.0.16 (U32/U64)
; Tested on: Win 10 Pro (x64)
; Written by: iPhilip
; Forum link: https://www.autohotkey.com/boards/viewtopic.php?f=6&t=62607
; ===============================================================================================================================
FFToolTip(Text?, X?, Y?, WhichToolTip := 1) {
static TTS := []
static DeltaX := 16 ; Horizontal gap between the mouse and the left edge of the tooltip window
static DeltaY := 16 ; Vertical gap between the mouse and the top edge of the tooltip window
static DeltaZ := 3 ; Vertical gap between the mouse and the bottom edge of the tooltip window when in the bottom-right corner of the screen
static Flags := 0x10040 ; TPM_WORKAREA = 0x10000 | TPM_VERTICAL = 0x0040
static POS := Buffer(16)
static EXC := Buffer(16)
static OUT := Buffer(16)
if !TTS.Length
TTS.Length := 20
if !IsSet(Text) || Text = '' { ; Destroy the tooltip.
hwnd := ToolTip( , , , WhichToolTip)
TTS.Delete(WhichToolTip)
} else if !TTS.Has(WhichToolTip) || Text !== TTS[WhichToolTip].Text { ; First call or tooltip text changed.
hwnd := ToolTip(Text, X?, Y?, WhichToolTip)
WinGetPos(&X, &Y, &W, &H, hwnd)
TTS[WhichToolTip] := {X:X, Y:Y, W:W, H:H, hwnd:hwnd, Text:Text}
} else { ; The tooltip is being repositioned.
IsSetX := IsSet(X)
IsSetY := IsSet(Y)
if !IsSetX || !IsSetY {
DllCall('GetCursorPos', 'Ptr', POS, 'Int')
MouseX := NumGet(POS, 0, 'Int')
MouseY := NumGet(POS, 4, 'Int')
}
;
; Convert input coordinates to screen coordinates.
;
if A_CoordModeToolTip = 'Window' {
WinGetPos &WinX, &WinY, , , 'A'
X := IsSetX ? X + WinX : MouseX
Y := IsSetY ? Y + WinY : MouseY
} else if A_CoordModeToolTip = 'Client' {
NumPut 'Int', X ?? 0, 'Int', Y ?? 0, POS
DllCall('ClientToScreen', 'Ptr', WinExist('A'), 'Ptr', POS, 'Int')
X := IsSetX ? NumGet(POS, 0, 'Int') : MouseX
Y := IsSetY ? NumGet(POS, 4, 'Int') : MouseY
} else { ; A_CoordModeToolTip = 'Screen'
X := X ?? MouseX
Y := Y ?? MouseY
}
W := TTS[WhichToolTip].W
H := TTS[WhichToolTip].H
;
; If neither coordinate is specified, force the tooltip window to be within the working area of the monitors.
;
if !IsSetX && !IsSetY
&& DllCall('SetRect', 'Ptr', POS, 'Int', X + DeltaX, 'Int', Y + DeltaY, 'Int', W, 'Int', H, 'Int')
&& DllCall('SetRect', 'Ptr', EXC, 'Int', X - DeltaZ, 'Int', Y - DeltaZ, 'Int', X + DeltaX, 'Int', Y + DeltaY, 'Int')
&& DllCall('CalculatePopupWindowPosition', 'Ptr', POS, 'Ptr', POS.Ptr + 8, 'UInt', Flags, 'Ptr', EXC, 'Ptr', OUT, 'Int')
X := NumGet(OUT, 0, 'Int'), Y := NumGet(OUT, 4, 'Int')
;
; If necessary, store the coordinates and move the tooltip window.
;
hwnd := TTS[WhichToolTip].hwnd
if X != TTS[WhichToolTip].X || Y != TTS[WhichToolTip].Y {
TTS[WhichToolTip].X := X, TTS[WhichToolTip].Y := Y
DllCall('MoveWindow', 'Ptr', hwnd, 'Int', X, 'Int', Y, 'Int', W, 'Int', H, 'Int', false, 'Int')
}
}
return hwnd
}
#Requires AutoHotkey v2.0
ToolTipText := '
(
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.
)'
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 Timer := () => ToolTip(ToolTipText), 10
MsgBox
SetTimer Timer, 0
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 taskbar.`n
Click OK to continue.
)'
SetTimer Timer := () => FFToolTip(ToolTipText), 10
MsgBox 'Click OK to exit.'
SetTimer Timer, 0
FFToolTip()
; ===============================================================================================================================
; FFToolTip(Text?, X?, Y?, WhichToolTip := 1)
; Function: Creates a tooltip window on any screen in a single or multiple-monitor environment. Unlike the built-in ToolTip
; function, calling this function repeatedly will not cause the tooltip window to flicker. Otherwise, it behaves
; much the same way, except around the bottom-right corner of each monitor. If neither coordinate is specified,
; the tooltip window will not cover any part of the taskbar.
; 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 destroyed.
; - 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 function). 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: If the tooltip is being shown for the first time or is being updated, the function returns the handle of the
; tooltip window. If the Text parameter is blank or omitted, the function returns zero.
; Global vars: None
; Dependencies: None
; Requirements: AHK v2.0
; Tested with: AHK v2.0.16 (U32/U64)
; Tested on: Win 10 Pro (x64)
; Written by: iPhilip
; Forum link: https://www.autohotkey.com/boards/viewtopic.php?f=6&t=62607
; ===============================================================================================================================
FFToolTip(Text?, X?, Y?, WhichToolTip := 1) {
static TTS := []
static DeltaX := 16 ; Horizontal gap between the mouse and the left edge of the tooltip window
static DeltaY := 16 ; Vertical gap between the mouse and the top edge of the tooltip window
static DeltaZ := 3 ; Vertical gap between the mouse and the bottom edge of the tooltip window when in the bottom-right corner of the screen
static Flags := 0x10040 ; TPM_WORKAREA = 0x10000 | TPM_VERTICAL = 0x0040
static POS := Buffer(16)
static EXC := Buffer(16)
static OUT := Buffer(16)
if !TTS.Length
TTS.Length := 20
if !IsSet(Text) || Text = '' { ; Destroy the tooltip.
hwnd := ToolTip( , , , WhichToolTip)
TTS.Delete(WhichToolTip)
} else if !TTS.Has(WhichToolTip) || Text !== TTS[WhichToolTip].Text { ; First call or tooltip text changed.
hwnd := ToolTip(Text, X?, Y?, WhichToolTip)
WinGetPos(&X, &Y, &W, &H, hwnd)
TTS[WhichToolTip] := {X:X, Y:Y, W:W, H:H, hwnd:hwnd, Text:Text}
} else { ; The tooltip is being repositioned.
IsSetX := IsSet(X)
IsSetY := IsSet(Y)
if !IsSetX || !IsSetY {
DllCall('GetCursorPos', 'Ptr', POS, 'Int')
MouseX := NumGet(POS, 0, 'Int')
MouseY := NumGet(POS, 4, 'Int')
}
;
; Convert input coordinates to screen coordinates.
;
if A_CoordModeToolTip = 'Window' {
WinGetPos &WinX, &WinY, , , 'A'
X := IsSetX ? X + WinX : MouseX
Y := IsSetY ? Y + WinY : MouseY
} else if A_CoordModeToolTip = 'Client' {
NumPut 'Int', X ?? 0, 'Int', Y ?? 0, POS
DllCall('ClientToScreen', 'Ptr', WinExist('A'), 'Ptr', POS, 'Int')
X := IsSetX ? NumGet(POS, 0, 'Int') : MouseX
Y := IsSetY ? NumGet(POS, 4, 'Int') : MouseY
} else { ; A_CoordModeToolTip = 'Screen'
X := X ?? MouseX
Y := Y ?? MouseY
}
W := TTS[WhichToolTip].W
H := TTS[WhichToolTip].H
;
; If neither coordinate is specified, force the tooltip window to be within the working area of the monitors.
;
if !IsSetX && !IsSetY
&& DllCall('SetRect', 'Ptr', POS, 'Int', X + DeltaX, 'Int', Y + DeltaY, 'Int', W, 'Int', H, 'Int')
&& DllCall('SetRect', 'Ptr', EXC, 'Int', X - DeltaZ, 'Int', Y - DeltaZ, 'Int', X + DeltaX, 'Int', Y + DeltaY, 'Int')
&& DllCall('CalculatePopupWindowPosition', 'Ptr', POS, 'Ptr', POS.Ptr + 8, 'UInt', Flags, 'Ptr', EXC, 'Ptr', OUT, 'Int')
X := NumGet(OUT, 0, 'Int'), Y := NumGet(OUT, 4, 'Int')
;
; If necessary, store the coordinates and move the tooltip window.
;
hwnd := TTS[WhichToolTip].hwnd
if X != TTS[WhichToolTip].X || Y != TTS[WhichToolTip].Y {
TTS[WhichToolTip].X := X, TTS[WhichToolTip].Y := Y
DllCall('MoveWindow', 'Ptr', hwnd, 'Int', X, 'Int', Y, 'Int', W, 'Int', H, 'Int', false, 'Int')
}
}
return hwnd
}
I hope you find it useful.
- iPhilip
Previous post
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.
; ===============================================================================================================================
; 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")
}
}
}
#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):
; ===============================================================================================================================
; 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
}
#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
Here is the function for single-monitor configurations:
; ===============================================================================================================================
; FFToolTip(Text:="", X:="", Y:="", WhichToolTip:=1)
; Function: Creates a tooltip window anywhere on the screen. Unlike the built-in ToolTip function, 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 function). 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 2.0-a100-52515e2 (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")
}
}
}
global 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
}
Timer2() {
FFToolTip(ToolTipText)
}
Esc::ExitApp
; ===============================================================================================================================
; FFToolTip(Text:="", X:="", Y:="", WhichToolTip:=1)
; Function: Creates a tooltip window anywhere on the screen. Unlike the built-in ToolTip function, 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 function). 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 2.0-a100-52515e2 (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):
; ===============================================================================================================================
; FFToolTip(Text:="", X:="", Y:="", WhichToolTip:=1)
; Function: Creates a tooltip window on any screen in a multiple monitor environment. Unlike the built-in ToolTip function,
; 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 function). 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 2.0-a100-52515e2 (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
}
global 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
}
Timer2() {
FFToolTip(ToolTipText)
}
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 function,
; 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 function). 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 2.0-a100-52515e2 (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
}
I hope you find it useful.
- iPhilip
EDIT: 20240609 - The functions now return the handle of the tooltip window or zero if the tooltip was destroyed.
Last edited by iPhilip on 09 Jun 2024, 09:47, edited 3 times in total.
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
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....
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)
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.
@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
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)
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 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)
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 ;).
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, 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)
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.
@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.