Page 1 of 2

Class ToolTipOptions - 2024-03-27

Posted: 30 Jan 2023, 10:20
by just me
Just for fun!

This class was inspired by @lexikos' ToolTipFont / ToolTipColor - options for the ToolTip command.
Different from that it's using Global Subclassing for the ToolTip controls created by the script.

How to use:
  • You must call ToolTipOptions.Init() to initialize the subclassing before you can set options.
  • Now you can call any of the other methods to set the options you want to be used by newly created tooltips. You'll find the descriptions within the class code.
  • All changes will be applied only to newly created tooltips.
  • If you don't want to use options any more, call ToolTipOptions.Reset() to remove the subclassing.
Enjoy!

Change history:

ToolTipOptions.ahk (2024-03-27):

Code: Select all

; ======================================================================================================================
; ToolTipOptions        -  additional options for ToolTips
;
; Tooltip control       -> https://learn.microsoft.com/en-us/windows/win32/controls/tooltip-control-reference
; TTM_SETMARGIN         = 1050
; TTM_SETTIPBKCOLOR     = 1043
; TTM_SETTIPTEXTCOLOR   = 1044
; TTM_SETTITLEW         = 1057
; WM_SETFONT            = 0x30
; SetClassLong()        -> https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclasslongw
; ======================================================================================================================
Class ToolTipOptions {
   ; -------------------------------------------------------------------------------------------------------------------
   Static HTT := DllCall("User32.dll\CreateWindowEx", "UInt", 8, "Str", "tooltips_class32", "Ptr", 0, "UInt", 3
                       , "Int", 0, "Int", 0, "Int", 0, "Int", 0, "Ptr", A_ScriptHwnd, "Ptr", 0, "Ptr", 0, "Ptr", 0)
   Static SWP := CallbackCreate(ObjBindMethod(ToolTipOptions, "_WNDPROC_"), , 4) ; subclass window proc
   Static OWP := 0                                                               ; original window proc
   Static ToolTips := Map()
   ; -------------------------------------------------------------------------------------------------------------------
   Static BkgColor := ""
   Static TxtColor := ""
   Static Icon := ""
   Static Title := ""
   Static HFONT := 0
   Static Margins := ""
   ; -------------------------------------------------------------------------------------------------------------------
   Static Call(*) => False ; do not create instances
   ; -------------------------------------------------------------------------------------------------------------------
   ; Init()          -  Initialize some class variables and subclass the tooltip control.
   ; -------------------------------------------------------------------------------------------------------------------
   Static Init() {
      If (This.OWP = 0) {
         This.BkgColor := ""
         This.TxtColor := ""
         This.Icon := ""
         This.Title := ""
         This.Margins := ""
         If (A_PtrSize = 8)
            This.OWP := DllCall("User32.dll\SetClassLongPtr", "Ptr", This.HTT, "Int", -24, "Ptr", This.SWP, "UPtr")
         Else
            This.OWP := DllCall("User32.dll\SetClassLongW", "Ptr", This.HTT, "Int", -24, "Int", This.SWP, "UInt")
         OnExit(ToolTipOptions._EXIT_, -1)
         Return This.OWP
      }
      Else
         Return False
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ;  Reset()        -  Close all existing tooltips, delete the font object, and remove the tooltip's subclass.
   ; -------------------------------------------------------------------------------------------------------------------
   Static Reset() {
      If (This.OWP != 0) {
         For HWND In This.ToolTips.Clone()
            DllCall("DestroyWindow", "Ptr", HWND)
         This.ToolTips.Clear()
         If This.HFONT
            DllCall("DeleteObject", "Ptr", This.HFONT)
         This.HFONT := 0
         If (A_PtrSize = 8)
            DllCall("User32.dll\SetClassLongPtrW", "Ptr", This.HTT, "Int", -24, "Ptr", This.OWP, "UPtr")
         Else
            DllCall("User32.dll\SetClassLongW", "Ptr", This.HTT, "Int", -24, "Int", This.OWP, "UInt")
         This.OWP := 0
         Return True
      }
      Else
         Return False
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; SetColors()     -  Set or remove the text and/or the background color for the tooltip.
   ; Parameters:
   ;     BkgColor    -  color value like used in Gui.BackColor(...)
   ;     TxtColor    -  see above.
   ; -------------------------------------------------------------------------------------------------------------------
   Static SetColors(BkgColor := "", TxtColor := "") {
      This.BkgColor := BkgColor = "" ? "" : BGR(BkgColor)
      This.TxtColor := TxtColor = "" ? "" : BGR(TxtColor)
      BGR(Color, Default := "") { ; converts colors to BGR
         ; HTML Colors (BGR)
         Static HTML := {AQUA:   0xFFFF00, BLACK: 0x000000, BLUE:   0xFF0000, FUCHSIA: 0xFF00FF, GRAY:  0x808080,
                         GREEN:  0x008000, LIME:  0x00FF00, MAROON: 0x000080, NAVY:    0x800000, OLIVE: 0x008080,
                         PURPLE: 0x800080, RED:   0x0000FF, SILVER: 0xC0C0C0, TEAL:    0x808000, WHITE: 0xFFFFFF,
                         YELLOW: 0x00FFFF}
         If HTML.HasProp(Color)
            Return HTML.%Color%
         If (Color Is String) && IsXDigit(Color) && (StrLen(Color) = 6)
            Color := Integer("0x" . Color)
         If IsInteger(Color)
            Return ((Color >> 16) & 0xFF) | (Color & 0x00FF00) | ((Color & 0xFF) << 16)
         Return Default
      }
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; SetFont()       -  Set or remove the font used by the tooltip.
   ; Parameters:
   ;     FntOpts     -  font options like Gui.SetFont(Options, ...)
   ;     FntName     -  font name like Gui.SetFont(..., Name)
   ; -------------------------------------------------------------------------------------------------------------------
   Static SetFont(FntOpts := "", FntName := "") {
      Static HDEF := DllCall("GetStockObject", "Int", 17, "UPtr") ; DEFAULT_GUI_FONT
      Static LOGFONTW := 0
      If (FntOpts = "") && (FntName = "") {
         If This.HFONT
            DllCall("DeleteObject", "Ptr", This.HFONT)
         This.HFONT := 0
         LOGFONTW := 0
      }
      Else {
         If (LOGFONTW = 0) {
            LOGFONTW := Buffer(92, 0)
            DllCall("GetObject", "Ptr", HDEF, "Int", 92, "Ptr", LOGFONTW)
         }
         HDC := DllCall("GetDC", "Ptr", 0, "UPtr")
         LOGPIXELSY := DllCall("GetDeviceCaps", "Ptr", HDC, "Int", 90, "Int")
         DllCall("ReleaseDC", "Ptr", HDC, "Ptr", 0)
         If (FntOpts != "") {
            For Opt In StrSplit(RegExReplace(Trim(FntOpts), "\s+", " "), " ") {
               Switch StrUpper(Opt) {
                  Case "BOLD":      NumPut("Int", 700, LOGFONTW, 16)
                  Case "ITALIC":    NumPut("Char",  1, LOGFONTW, 20)
                  Case "UNDERLINE": NumPut("Char",  1, LOGFONTW, 21)
                  Case "STRIKE":    NumPut("Char",  1, LOGFONTW, 22)
                  Case "NORM":      NumPut("Int", 400, "Char", 0, "Char", 0, "Char", 0, LOGFONTW, 16)
                  Default:
                     O := StrUpper(SubStr(Opt, 1, 1))
                     V := SubStr(Opt, 2)
                     Switch O {
                        Case "C":
                           Continue ; ignore the color option
                        Case "Q":
                           If !IsInteger(V) || (Integer(V) < 0) || (Integer(V) > 5)
                              Throw ValueError("Option Q must be an integer between 0 and 5!", -1, V)
                           NumPut("Char", Integer(V), LOGFONTW, 26)
                        Case "S":
                           If !IsNumber(V) || (Number(V) < 1) || (Integer(V) > 255)
                              Throw ValueError("Option S must be a number between 1 and 255!", -1, V)
                           NumPut("Int", -Round(Integer(V + 0.5) * LOGPIXELSY / 72), LOGFONTW)
                        Case "W":
                           If !IsInteger(V) || (Integer(V) < 1) || (Integer(V) > 1000)
                              Throw ValueError("Option W must be an integer between 1 and 1000!", -1, V)
                           NumPut("Int", Integer(V), LOGFONTW, 16)
                        Default:
                           Throw ValueError("Invalid font option!", -1, Opt)
                     }
                  }
               }
            }
         NumPut("Char", 1, "Char", 4, "Char", 0, LOGFONTW, 23) ; DEFAULT_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS
         NumPut("Char", 0, LOGFONTW, 27) ; FF_DONTCARE
         If (FntName != "")
            StrPut(FntName, LOGFONTW.Ptr + 28, 32)
         If !(HFONT := DllCall("CreateFontIndirectW", "Ptr", LOGFONTW, "UPtr"))
            Throw OSError()
         If This.HFONT
            DllCall("DeleteObject", "Ptr", This.HFONT)
         This.HFONT := HFONT
      }
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; SetMargins()    -  Set or remove the margins used by the tooltip
   ; Parameters:
   ;     L, T, R, B  -  left, top, right, and bottom margin in pixels.
   ; -------------------------------------------------------------------------------------------------------------------
   Static SetMargins(L := 0, T := 0, R := 0, B := 0) {
      If ((L + T + R + B) = 0)
         This.Margins := 0
      Else {
         This.Margins := Buffer(16, 0)
         NumPut("Int", L, "Int", T, "Int", R, "Int", B, This.Margins)
      }
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; SetTitle()      -  Set or remove the title and/or the icon displayed on the tooltip.
   ; Parameters:
   ;     Title       -  string to be used as title.
   ;     Icon        -  icon to be shown in the ToolTip.
   ;                    This can be the number of a predefined icon (1 = info, 2 = warning, 3 = error
   ;                    (add 3 to display large icons on Vista+) or a HICON handle.
   ; -------------------------------------------------------------------------------------------------------------------
   Static SetTitle(Title := "", Icon := "") {
      Switch {
         Case (Title = "") && (Icon != ""):
            This.Icon := Icon
            This.Title := " "
         Case (Title != "") && (Icon = ""):
            This.Icon := 0
            This.Title := Title
         Default:
            This.Icon := Icon
            This.Title := Title
      }
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; For internal use only!
   ; -------------------------------------------------------------------------------------------------------------------
   Static _WNDPROC_(hWnd, uMsg, wParam, lParam) {
      ; WNDPROC -> https://learn.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-wndproc
      Switch uMsg {
         Case 0x0411: ; TTM_TRACKACTIVATE - just handle the first message after the control has been created
            If This.ToolTips.Has(hWnd) && (This.ToolTips[hWnd] = 0) {
               If (This.BkgColor != "")
                  SendMessage(1043, This.BkgColor, 0, hWnd)                ; TTM_SETTIPBKCOLOR
               If (This.TxtColor != "")
                  SendMessage(1044, This.TxtColor, 0, hWnd)                ; TTM_SETTIPTEXTCOLOR
               If This.HFONT
                  SendMessage(0x30, This.HFONT, 0, hWnd)                   ; WM_SETFONT
               If (Type(This.Margins) = "Buffer")
                  SendMessage(1050, 0, This.Margins.Ptr, hWnd)             ; TTM_SETMARGIN
               If (This.Icon != "") || (This.Title != "")
                  SendMessage(1057, This.Icon, StrPtr(This.Title), hWnd)   ; TTM_SETTITLE
               This.ToolTips[hWnd] := 1
            }
         Case 0x0001: ; WM_CREATE
            DllCall("UxTheme.dll\SetWindowTheme", "Ptr", hWnd, "Ptr", 0, "Ptr", StrPtr(""))
            This.ToolTips[hWnd] := 0
         Case 0x0002: ; WM_DESTROY
            This.ToolTips.Delete(hWnd)
      }
      Return DllCall(This.OWP, "Ptr", hWnd, "UInt", uMsg, "Ptr", wParam, "Ptr", lParam, "UInt")
   }
   ; -------------------------------------------------------------------------------------------------------------------
   Static _EXIT_(*) {
      If (ToolTipOptions.OWP != 0)
         ToolTipOptions.Reset()
   }
}

Some sample:

Code: Select all

#Include ToolTipOptions.ahk
; ----------------------------------------------------------------------------------------------------------------------
ToolTipOptions.Init()
; ----------------------------------------------------------------------------------------------------------------------
ToolTipOptions.SetFont("s48 underline italic", "Consolas")
ToolTipOptions.SetMargins(12, 12, 12, 12)
ToolTipOptions.SetTitle("Title" , 4)
ToolTipOptions.SetColors("Green", "White")
ToolTip("Hello world!")
; ----------------------------------------------------------------------------------------------------------------------
Return
; ----------------------------------------------------------------------------------------------------------------------
Esc::ExitApp
Esc::ExitApp
^+s::ToolTip("Hello world!")        ; show a ToolTip
^+a::ToolTip("Another Tooltip!")    ; show another ToolTip
^+h::ToolTip()                      ; destroy the ToolTip
^+f::ToolTipOptions.SetFont()       ; remove the font
^+x::ToolTipOptions.Reset()         ; remove the subclassing
^+y::ToolTipOptions.Init()          ; add the sublassing again

Re: Class ToolTipOptions

Posted: 20 Feb 2023, 05:04
by paveld
GREAT

Re: Class ToolTipOptions

Posted: 05 Mar 2023, 21:01
by hasantr
Thanks. This will be very useful to us.

Re: Class ToolTipOptions

Posted: 14 Apr 2023, 06:30
by LAPIII
What would a script look like if I wanted to make a tooltip in an app with a hotkey?

EDIT: I made a library of the class and I tried:

Code: Select all

#Requires Autohotkey v2.0+
#HotIf WinActive("ahk_exe msedge.exe")
#Include <ToolTipOptions>
CoordMode "ToolTip", "Window" 
ToolTipOptions.Init()

ToolTipOptions.SetFont("s12 underline italic bold", "Ink Free")
ToolTipOptions.SetMargins(12, 12, 12, 12)
ToolTipOptions.SetColors("Teal", "White")
ToolTip("Hello world!", 1104, 0)
The library works but the script doesn't.

Re: Class ToolTipOptions

Posted: 15 Apr 2023, 06:28
by boiler
LAPIII wrote: What would a script look like if I wanted to make a tooltip in an app with a hotkey?
Open a new thread in Ask for Help (v2) if you need help on how to create a hotkey. This is thread is not the place for learning AHK basics.

LAPIII wrote: EDIT: I made a library of the class and I tried:
Your script ends before it has a chance to display the ToolTip. Adding a hotkey or a MsgBox are a couple of ways you could prevent the script from immediately ending, allowing you to see the ToolTip. Again, your questions are not related to the library, so ask for help in a new thread if you need help on how to construct a script.

Re: Class ToolTipOptions

Posted: 10 Sep 2023, 00:53
by theyakutoo
It's doing strange things when using multiple tooltips. On exit, it's looping, showing a windows center screen so fast they don't fade in all the way.

Enabling the commented exit loop resolves the issue by closing the tips before the class does it's exit routine.

Warning: It seems to be exponential... enabling 5 tips may require waiting quite some time... 2 is enough to see the issue.

Code: Select all

#Requires AutoHotkey >=2.0- <2.1
#Singleinstance force

#Include ToolTipOptions.ahk

; ----------------------------------------------------------------------------------------------------------------------

ToolTipOptions.Init()
ToolTipOptions.SetMargins(0, 0, 0, 0)
ToolTipOptions.SetTitle()
ToolTipOptions.SetFont("s12", "Consolas")


TID:=1	,	ToolTipOptions.SetColors("black", "white")	,	ToolTip("TIP " a_index,10,TID*30,TID)
TID:=2	,	ToolTipOptions.SetColors("black", "green")	,	ToolTip("TIP " a_index,10,TID*30,TID)
;TID:=3	,	ToolTipOptions.SetColors("black", "yellow")	,	ToolTip("TIP " a_index,10,TID*30,TID)
;TID:=4	,	ToolTipOptions.SetColors("black", "maroon")	,	ToolTip("TIP " a_index,10,TID*30,TID)
;TID:=5	,	ToolTipOptions.SetColors("black", "red")	,	ToolTip("TIP " a_index,10,TID*30,TID)

return

; ----------------------------------------------------------------------------------------------------------------------

Esc:: 
{

;	loop 20
;		ToolTip("",,,a_index)

	ExitApp

}

Re: Class ToolTipOptions - 2023-09-10

Posted: 10 Sep 2023, 07:08
by just me
Thanks for reporting. Elements from the ToolTipOptions.ToolTips Map object were deleted in _WNDPROC_() while enumerating the object in Reset(). Should be fixed now.

Re: Class ToolTipOptions - 2023-09-10

Posted: 10 Sep 2023, 23:28
by theyakutoo
It works! Thanks!

Re: Class ToolTipOptions - 2023-09-10

Posted: 07 Oct 2023, 06:04
by theyakutoo
Error when opening start menu. Sometimes it takes a few tries to get the error.

image.png
image.png (11.35 KiB) Viewed 2472 times

Code: Select all

#Requires AutoHotkey >=2.0- <2.1
#Singleinstance force

#Include ToolTipOptions.ahk

ToolTipOptions.Init()
ToolTipOptions.SetMargins(0, 0, 0, 0)
ToolTipOptions.SetTitle()
ToolTipOptions.SetFont("s10", "Consolas")
ToolTipOptions.SetColors("black", "red")

while 1 {
	sleep 11
	title		:= WinGetTitle("A")
	tooltip(title)
}

Esc::ExitApp

Re: Class ToolTipOptions - 2023-09-10

Posted: 07 Oct 2023, 07:07
by rommmcek
This issue seems nothing to do with Class ToolTipOptions.
Try your loop w/o this Class.
If you slow down a loop there is no problem in any case.

P.s.: AutoHotkey v1 seems not to have such an issue, therefore it might be a v2 bug.

Re: Class ToolTipOptions - 2023-09-10

Posted: 07 Oct 2023, 08:42
by andymbody
theyakutoo wrote:
07 Oct 2023, 06:04
Error when opening start menu. Sometimes it takes a few tries to get the error.
I have not looked over this thread completely, so I don't know if this is the same issue I ran into with another script.

But I found that the taskbar receives active focus briefly when minimizing or closing windows. And at that moment no title can be found because the taskbar has no title associated with it. The same thing happens when showing the desktop. See if this is the case here.

I added a try/catch to my project to avoid the error when no title can be found

See if this resolves it. I included a different message so you will be aware when the issue happens.
Or you can simply test to see if active window class is Shell_TrayWnd or WorkerW, and only get title when it is not

Code: Select all

errorCount := 0
while 1 {
	sleep 11
	try
	{
		title := WinGetTitle("A")
		tooltip(title)
	}
	catch
	{
		tooltip("no title found := " . ++errorCount)
	}
}

Andy

Re: Class ToolTipOptions - 2023-09-10

Posted: 07 Oct 2023, 10:00
by theyakutoo
Using try solved the issue.

Thanks guys!

Re: Class ToolTipOptions - 2023-09-10

Posted: 16 Mar 2024, 09:44
by kunkel321
I just wanted to say thanks to Just Me, for making and sharing this. It has been integrated into a couple of my regularly-used scripts. It's nice for those of us with failing eyesight. I've started using it in place of MsgBox (MsgBox's font is too small.)

FYI for noobs (like me): If you use variables for the colors, and if you use just the 6-character hex code, be sure to include the zero-x at the beginning, like: Fixed -- See below. Thanks Descolada!

Re: Class ToolTipOptions - 2024-03-17

Posted: 17 Mar 2024, 08:34
by just me
Hi @kunkel321,
thanks! I changed the class to support 6-xdigit color strings. (I mistakenly assumed that AHK v2 doesn't support such color strings any more.)
Continue to have fun with AHK!

Re: Class ToolTipOptions - 2024-03-17

Posted: 25 Mar 2024, 12:52
by dipahk
I get the following error message using this class:
image.png
image.png (20.71 KiB) Viewed 1035 times
I then replaced "TktColor" to "TxtColor" in lines 21 and 34 of ToolTipOptions.ahk and then it works great.
Typing error?

Re: Class ToolTipOptions - 2024-03-27

Posted: 27 Mar 2024, 03:25
by just me
Hi @dipahk, thanks, fixed!

Re: Class ToolTipOptions - 2024-03-27

Posted: 05 Apr 2024, 11:27
by kunkel321
Hi @just me,
Just a heads up, the colorization in my VSCode set up showed that some of your "HTML color" names and the corresponding hex codes are inverted... EDIT: Nope... Explained by Just Me, below.

Re: Class ToolTipOptions - 2024-03-27

Posted: 07 Apr 2024, 03:01
by just me
Hi @kunkel321, it's BGR:

Code: Select all

      BGR(Color, Default := "") { ; converts colors to BGR
         ; HTML Colors (BGR) <<<<<

Re: Class ToolTipOptions - 2024-03-27

Posted: 07 Apr 2024, 08:46
by kunkel321
just me wrote:
07 Apr 2024, 03:01
Hi @kunkel321, it's BGR:

Code: Select all

      BGR(Color, Default := "") { ; converts colors to BGR
         ; HTML Colors (BGR) <<<<<
My bad! I had googled a couple of the hex values, but I didn't actually try them in the code. Lesson learned. :oops:

Re: Class ToolTipOptions - 2024-03-27

Posted: 24 Apr 2024, 06:14
by Bobak
I'm about to get crazy.

If I change the color and title it is not consistent.
I use the same text, namely the col[ix] in both the title and in the Tooltip, but if I press NumLock several times the title is not the same as the text every time. What am I doing (or expecting) wrong?
Please help!

Code: Select all

#Requires AutoHotkey v2.0+
#SingleInstance Force
Persistent

#Include <ToolTipOptions>
ToolTipOptions.Init()

^Esc:: ExitApp()
!Esc:: Reload()

~CapsLock::
~NumLock::
{
	KeyWait("NumLock")
	tipOn := GetKeyState("NumLock", "T")
	anon := GetKeyState("CapsLock", "T")
	col := ["RED", "GREEN", "BLUE", "YELLOW", "WHITE"]
	ix := random(1, col.Length)

	ToolTipOptions.SetFont("s10", "courier")
	ToolTipOptions.SetTitle(col[ix], 1)
	ToolTipOptions.SetColors(col[ix])
	tooltip(col[ix])
}