GuiCtrlTips - Add tooltips to your Gui.Controls (2023-05-28)

Post your working scripts, libraries and tools.
just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

GuiCtrlTips - Add tooltips to your Gui.Controls (2023-05-28)

Post by just me » 17 Apr 2023, 11:17

I decided to replace the function GuiCtrlSetTips() with the class GuiCtrlTips because the handling of the settings seems easier to me.
Change history

GuiCtrlTips.ahk:

Code: Select all

#Requires AutoHotkey v2.0
; ======================================================================================================================
; Release date: 2023-05-28
; ======================================================================================================================
; Add tooltips to your Gui controls.
; Tooltips are managed per Gui, so you first have to create a new instance of the class for the Gui, e.g.:
;     MyGui := Gui()
;     MyGui.Tips := GuiCtrlTips(MyGui)
; Then you can create your controls and add tooltips, e.g.:
;     MyBtn := MyGui.AddButton(...)
;     MyGui.Tips.SetTip(MyBtn, "My Tooltip!")
; You can activate, deactivate, or change the delay times for all tooltips at any time by calling the corresponding
; methods.
; To remove a tooltip from a single control pass an empty text, e.g.:
;     MyGui.Tips.SetTip(MyBtn, "")
; Text and Picture controls require the SS_NOTIFY (+0x0100) style or a 'Click' event function.
; ----------------------------------------------------------------------------------------------------------------------
; Tooltip control: https://learn.microsoft.com/en-us/windows/win32/controls/tooltip-control-reference
; ======================================================================================================================
Class GuiCtrlTips {
   Static TOOLINFO {
      Get {
         Static SizeOfTI := 24 + (A_PtrSize * 6)
         Local TI := Buffer(SizeOfTI, 0)
         NumPut("UInt", SizeOfTI, TI)
         Return TI
      }
   }
   ; -------------------------------------------------------------------------------------------------------------------
   Static ToolTipFont {
      Get {
         Static SizeOfLFW  := 92                      ; LOGFONTW structure
         Static SizeOfNCM := 44 + (SizeOfLFW * 5)      ; NONCLIENTMETRICSW structure
         Static OffStatusFont := 40 + (SizeOfLFW * 3)  ; lfStatusFont
         Static LOGFONTW := 0
         If !IsObject(LOGFONTW) { ; first call
            Local NCM := Buffer(SizeOfNCM, 0)
            NumPut("UInt", SizeOfNCM, NCM)
            DllCall("SystemParametersInfoW", "UInt", 0x0029, "UInt", 0, "Ptr", NCM.Ptr, "UInt", 0) ; SPI_GETNONCLIENTMETRICS
            LOGFONTW := Buffer(SizeOfLFW, 0)
            DllCall("RtlMoveMemory", "Ptr", LOGFONTW.Ptr, "Ptr", NCM.Ptr + OffStatusFont, "Ptr", SizeOfLFW)
         }
         Local LF := Buffer(SizeOfLFW, 0)
         DllCall("RtlMoveMemory", "Ptr", LF.Ptr, "Ptr", LOGFONTW.Ptr, "Ptr", SizeOfLFW)
         Return LF
      }
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-addtool
   ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setmaxtipwidth
   ; https://learn.microsoft.com/en-us/windows/win32/api/commctrl/ns-commctrl-tttoolinfow
   __New(GuiObj, UseAhkStyle := True, UseComboEdit := True) {
      Local Flags, HGUI, HTIP, TI
      If !(GuiObj Is Gui)
         Throw TypeError(A_ThisFunc . ": Expected a Gui object!", -1 "GuiObj")
      HGUI := GuiObj.Hwnd
      ; Create the TOOLINFO structure
      Flags := 0x11 ; TTF_SUBCLASS | TTF_IDISHWND
      TI := GuiCtrlTips.TOOLINFO
      NumPut("UInt", Flags, "UPtr", HGUI, "UPtr", HGUI, TI, 4) ; uFlags, hwnd, uID
      ; Create a tooltip control for this Gui
      If !(HTIP := DllCall("CreateWindowEx", "UInt", 0, "Str", "tooltips_class32", "Ptr", 0, "UInt", 0x80000003
                                           , "Int", 0x80000000, "Int", 0x80000000, "Int", 0x80000000, "Int", 0x80000000
                                           , "Ptr", HGUI, "Ptr", 0, "Ptr", 0, "Ptr", 0, "UPtr"))
         Throw Error(A_ThisFunc . ": Could not create a tooltip control", -1)
      If (UseAhkStyle)
         DllCall("Uxtheme.dll\SetWindowTheme", "Ptr", HTIP, "Ptr", 0, "Str", " ")
      SendMessage(0x0418, 0, A_ScreenWidth, HTIP) ; TTM_SETMAXTIPWIDTH
      ; SendMessage(0x0432, 0, TI.Ptr, HTIP) ; TTM_ADDTOOLW <--- doesn't seem required any more
      This.DefineProp("HTIP", {Get: (*) => HTIP})
      This.DefineProp("HGUI", {Get: (*) => HGUI})
      This.DefineProp("UAS",  {Get: (*) => !!UseAhkStyle})
      This.DefineProp("UCE",  {Get: (*) => !!UseComboEdit})
      This.Ctrls := Map()
   }
   ; -------------------------------------------------------------------------------------------------------------------
   __Delete() {
      If This.HasProp("HTIP")
         DllCall("DestroyWindow", "Ptr", This.HTIP)
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-activate
   Activate() {
      SendMessage(0x0401, True, 0, This.HTIP) ; TTM_ACTIVATE
      Return True
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-activate
   Deactivate() {
      SendMessage(0x0401, False, 0, This.HTIP) ; TTM_ACTIVATE
      Return True
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-settipbkcolor
   SetBkColor(Color) {
      If IsInteger(Color) {
         Color := ((Color & 0x0000FF) << 16) | (Color & 0x00FF00) |  ((Color & 0xFF0000) >> 16)
         SendMessage(0x0413, Color, 0, This.HTIP)
      }
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime
   ; Flag      - one of the string keys defined in Flags
   ; MilliSecs - time in millisecons, pass -1 to reset to the default
   SetDelayTime(Flag, MilliSecs) {
      Static Flags := {AUTOMATIC: 0, AUTOPOP: 2, INITIAL: 3, RESHOW: 1}
      If !Flags.HasProp(Flag) || !IsInteger(MilliSecs)
         Return False
      If  (MilliSecs < -1)
         MilliSecs := -1
      SendMessage(0x0403, Flags.%Flag%, MilliSecs, This.HTIP)
      Return True
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; Any value passed in Bold and Italic will set the related font option
   SetFont(FontSize?, FontName?, Bold?, Italic?) {
      Static LOGPIXELSY := 0, PrevFont := 0
      If (LOGPIXELSY = 0) { ; first call
         Local HDC := DllCall("GetDC", "Ptr", 0, "UPtr")
         LOGPIXELSY := DllCall("GetDeviceCaps", "Ptr", HDC, "Int", 90, "Int") ; LOGPIXELSY
         DllCall("ReleaseDC", "Ptr", 0, "Ptr", HDC)
      }
      Local LOGFONT := GuiCtrlTips.ToolTipFont
      If IsSet(FontSize) && IsNumber(FontSize)
         NumPut("Int", -Round(FontSize * LOGPIXELSY / 72), LOGFONT)
      If IsSet(Bold)
         NumPut("Int", 700, LOGFONT, 16)
      If IsSet(Italic)
         NumPut("UChar", 1, LOGFONT, 20)
      If IsSet(FontName)
         StrPut(FontName, LOGFONT.Ptr + 28, 32)
      Local HFONT := DllCall("CreateFontIndirectW", "Ptr", LOGFONT.Ptr, "UPtr")
      SendMessage(0x0030, HFONT, 1, This.HTIP)
      If PrevFont
         DllCall("DeleteObject", "Ptr", PrevFont)
      PrevFont := HFONT
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setmargin
   SetMargins(L := 0, T := 0, R := 0, B := 0) {
      RC := Buffer(16, 0)
      NumPut("Int", L, "Int", T, "Int", R, "Int", B, RC)
      SendMessage(0x041A, 0, RC.Ptr, This.HTIP)
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-addtool
   ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-deltool
   ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-updatetiptext
   SetTip(GuiCtrl, TipText, CenterTip := False) {
      Local Flags, HCTL, TI
      ; Check the passed GuiCtrl
      If !(GuiCtrl Is Gui.Control) || (GuiCtrl.Gui.Hwnd != This.HGUI)
         Return False
      If (GuiCtrl.Type = "ComboBox") && This.UCE ; use the Edit control of the Combobox
         HCTL := DllCall("FindWindowExW", "Ptr", GuiCtrl.Hwnd, "Ptr", 0, "Ptr", 0, "Ptr", 0, "UPtr")
      Else
         HCTL := GuiCtrl.Hwnd
      ; Create the TOOLINFO structure
      Flags := 0x11 | (CenterTip ? 0x02 : 0x00) ; TTF_SUBCLASS | TTF_IDISHWND [| TTF_CENTERTIP]
      TI := GuiCtrlTips.TOOLINFO
      NumPut("UInt", Flags, "UPtr", This.HGUI, "UPtr", HCTL, TI, 4) ; cbSize, uFlags, hwnd, uID
      If (TipText = "") {
         If This.Ctrls.Has(HCTL) {
            SendMessage(0x0433, 0, TI.Ptr, This.HTIP) ; TTM_DELTOOLW
            This.Ctrls.Delete(HCTL)
         }
         Return True
      }
      If !This.Ctrls.Has(HCTL) {
         SendMessage(0x0432, 0, TI.Ptr, This.HTIP) ; TTM_ADDTOOLW
         This.Ctrls[HCTL] := True
      }
      ; Set / Update the tool's text.
      NumPut("UPtr", StrPtr(TipText), TI, 24 + (A_PtrSize * 3))  ; lpszText
   	SendMessage(0x0439, 0, TI.Ptr, This.HTIP) ; TTM_UPDATETIPTEXTW
   	Return True
   }
   ; -------------------------------------------------------------------------------------------------------------------
   ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-settiptextcolor
   SetTxColor(Color) {
      If IsInteger(Color) {
         Color := ((Color & 0x0000FF) << 16) | (Color & 0x00FF00) |  ((Color & 0xFF0000) >> 16)
         SendMessage(0x0414, Color, 0, This.HTIP)
      }
   }
}
; ======================================================================================================================
GuiCtrlTips_sample.ahk:

Code: Select all

#Requires AutoHotkey v2.0
; ======================================================================================================================
; Release date: 2023-05-28
; ======================================================================================================================
#Include GuiCtrlTips.ahk
MainGui := Gui( , "ToolTips")
MainGui.Tips := GuiCtrlTips(MainGui)      ; create a GuiCtrlTips instance
MainGui.Tips.SetBkColor(0x404040)         ; set the background colour
MainGui.Tips.SetTxColor(0xFFFFFF)         ; set the text colour
MainGui.Tips.SetMargins(4, 4, 4, 4)       ; set the tect margins
MainGui.Tips.SetFont(24, "Arial", , 1)    ; set the font
MainGui.MarginX := 10
MainGui.MarginY := 10
MainBtn1 := MainGui.AddButton("w200 h20", "Button1")
MainBtn1.OnEvent("Click", ToggleBtn1)
MainGui.Tips.SetTip(MainBtn1, "Click me first")
Btn1TT := True
MainBtn2 := MainGui.AddButton("w200 h20", "Button2")
MainGui.Tips.SetTip(MainBtn2, "I'm button 2!`r`nI show a multi-line tooltip.`r`nLet's see, how long it is shown!")
MainBtn3 := MainGui.AddButton("w200 h20", "Show 10 seconds!")
MainBtn3.OnEvent("Click", (*) => MainGui.Tips.SetDelayTime("AUTOPOP", 10000))
MainBtn4 := MainGui.AddButton("w200 h20", "Default time!")
MainBtn4.OnEvent("Click", (*) => MainGui.Tips.SetDelayTime("AUTOPOP", -1))
MainTxt5 := MainGui.AddText("w200 h20 +0x0100 Border", "Text")
MainGui.Tips.SetTip(MainTxt5, "Gotcha!")
MainBtn6 := MainGui.AddButton("w200 h20 ym", "Button6")
MainBtn7 := MainGui.AddButton("w200 h20", "Button7")
MainBtn8 := MainGui.AddButton("w200 h20", "Avtivate!")
MainBtn8.OnEvent("Click", (*) => MainGui.Tips.Activate())
MainBtn9 := MainGui.AddButton("w200 h20", "Deactivate!")
MainBtn9.OnEvent("Click", (*) => MainGui.Tips.Deactivate())
MainEdt0 := MainGui.AddEdit("w200 h20", "Edit")
MainGui.Tips.SetTip(MainEdt0, "Finally we have an Edit!", True)
MainCBB1 := MainGui.AddComboBox("xm w410", ["Red", "Green", "Blue"])
MainGui.Tips.SetTip(MainCBB1, "That's the tooltip of a ComboBox")
MainGui.Show()
; ----------------------------------------------------------------------------------------------------------------------
ToggleBtn1(*) {
   Global Btn1TT := !Btn1TT
   MainGui.Tips.SetTip(MainBtn1, Btn1TT ? "Click me first" : "")
}


Edit: Sources on GitHub



Previous version (function)
Last edited by just me on 10 Aug 2023, 05:33, edited 6 times in total.

User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: GuiCtrlSetTip() - Add tooltips to your Gui.Controls

Post by SKAN » 15 May 2023, 03:16

Hi @just me
I've never used tooltips_class32 till date.
I find this very interesting and useful for me.
Thanks for sharing.
:thumbup:

just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: GuiCtrlSetTip() - Add tooltips to your Gui.Controls

Post by just me » 15 May 2023, 03:22

Hi @SKAN, you're welcome. ;)

Krd
Posts: 405
Joined: 10 Mar 2020, 02:46

Re: GuiCtrlSetTip() - Add tooltips to your Gui.Controls

Post by Krd » 15 May 2023, 06:32

Never mind. It worked just as it is.

This is a useful function! So thank you! :bravo:

Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: GuiCtrlSetTip() - Add tooltips to your Gui.Controls

Post by Spitzi » 16 May 2023, 16:11

Love this! Thanks @just me

This Tool viewtopic.php?t=30079 for AHKV1 is what I used before. Things I liked about it were
- easy activate / deactivate all tooltips
- adjust time it takes for tooltip to show
- adjust time it takes for tooltip to disapear on its own

I would implement it myself if I knew how... but DLLs and NumPuts are over my head.

Just a thought. Greets and thanks again!!

Krd
Posts: 405
Joined: 10 Mar 2020, 02:46

Re: GuiCtrlSetTip() - Add tooltips to your Gui.Controls

Post by Krd » 17 May 2023, 12:23

The tooltip disappears quickly when the text is longer. How can I increase the timeout?

Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: GuiCtrlSetTip() - Add tooltips to your Gui.Controls

Post by Spitzi » 17 May 2023, 17:05

@just me Another nice thing would be multiline tooltips

GuiCtrlSetTip(chckBoxObj, "ToolTipText1`nToolTipText2`nToolTipText3")

at the moment only shows ToolTipText1.

just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: GuiCtrlSetTip() - Add tooltips to your Gui.Controls (2023-05-18)

Post by just me » 18 May 2023, 06:14

Update on 2023-05-18 - Enabled multi-line tooltips.

Krd
Posts: 405
Joined: 10 Mar 2020, 02:46

Re: GuiCtrlSetTip() - Add tooltips to your Gui.Controls (2023-05-18)

Post by Krd » 18 May 2023, 12:28

Any answer would be great if timeout is not supported.

Meanwhile I deleted using this as it becomes useless for me when I myself don't have enough time to read the tooltip before it disappears. So good luck to my users. :)

Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: GuiCtrlSetTip() - Add tooltips to your Gui.Controls (2023-05-18)

Post by Spitzi » 18 May 2023, 15:05

@just me
Thanks for very much this update, it works nicely!!
Krd has a point. If the tooltip is longer, it's hard to read till the end before it disappears...

AddTooltipV2 viewtopic.php?t=30079 for AHK1 had it implemented somehow...

Greets

just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: GuiCtrlTips - Add tooltips to your Gui.Controls (2023-05-19)

Post by just me » 19 May 2023, 08:57

Replaced the function with a class. The class supports activating and deactivating of all tooltips. You can also set the delay times for all tooltips.

Krd
Posts: 405
Joined: 10 Mar 2020, 02:46

Re: GuiCtrlTips - Add tooltips to your Gui.Controls (2023-05-19)

Post by Krd » 20 May 2023, 05:59

Thanks for splitting the class. This is much better now!

I can now simply set this before Gui.show to set the timeout for all controls:

Code: Select all

StolenGui.Tips.SetDelayTime("AUTOPOP", 1000000)
It's perfect. Thank you again! :bravo:

Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: GuiCtrlTips - Add tooltips to your Gui.Controls (2023-05-19)

Post by Spitzi » 22 May 2023, 03:27

@just me: you are a master of your trade! Thanks alot. Very usefull and nicely implemented.

A few thoughts:
  • activate / deactivate methods are gui-specific. How would you turn off tooltips for all guis in a script at once? Maybe it would make more sense to have just one instance of the class for all guis?
  • same for SetDelayTime, which must be called per gui -> it would be nice to call it just one to set the times for all guis and tips defined later.
  • of course, if you wand to push the usability even more.... custom fonts and font sizes would be nice.
That said, using your class, I am very happy to be able to use tooltips in AHK2 again, and can carry on migrating my AHK1-app to AHK2. Thanks a ton. Greets.

just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: GuiCtrlTips - Add tooltips to your Gui.Controls (2023-05-23)

Post by just me » 23 May 2023, 04:40

2023-05-23: bugfix and update

Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: GuiCtrlTips - Add tooltips to your Gui.Controls (2023-05-23)

Post by Spitzi » 24 May 2023, 02:19

@just me

Thanks for your latest update. Works nicely, Tooltips look great now!
I noticed that on a combobox, the tooltip shows only on the dropdown arrow, but not on the edit-field part of the control...

It's probably not possible to set font and font size, right? There is no such message defined in the microsoft documentation.

Thanks for this awesome class. Greets.

just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: GuiCtrlTips - Add tooltips to your Gui.Controls (2023-05-28)

Post by just me » 28 May 2023, 05:02

2023-05-28: Update: Added UseComboEdit and SetFont()

Krd
Posts: 405
Joined: 10 Mar 2020, 02:46

Re: GuiCtrlTips - Add tooltips to your Gui.Controls (2023-05-28)

Post by Krd » 28 May 2023, 11:09

The magical SetFont()!

This is just awesome, thank you!

No more need for a Gui per button to explain what that button does. :)

Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: GuiCtrlTips - Add tooltips to your Gui.Controls (2023-05-28)

Post by Spitzi » 28 Jun 2023, 01:46

@just me. Love your class. Used it all over my app with about 20 GUIs!! Thanks again for adding all that functionality!

IWantItAll2
Posts: 8
Joined: 14 Jun 2023, 10:00

Re: GuiCtrlTips - Add tooltips to your Gui.Controls (2023-05-28)

Post by IWantItAll2 » 05 Jul 2023, 02:30

Thanks very much for this. I had an issue where I couldn't get it working and I was looking over the code to find the differences between your sample and what I had. For some reason, and I don't know if this is a bug or not. if you have the onEvent handler on the same line as you create the button it doesn't work.

Code: Select all

; code from sample, THIS WORKS
MainBtn1 := EditGui.AddButton("w200 h20", "Button1")
MainBtn1.OnEvent("Click", ToggleBtn1)
EditGui.Tips.SetTip(MainBtn1, "Click me first")
;========
; code that i had, DOESNT SHOW TOOL TIP
MainBtn1 := EditGui.AddButton("w200 h20", "Button1").OnEvent("Click", ToggleBtn1)
EditGui.Tips.SetTip(MainBtn1, "Click me first")
This is a valid way of writing and my onEvents were being executed. just for some reason the tool tips wouldn't show.
Thanks again

just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: GuiCtrlTips - Add tooltips to your Gui.Controls (2023-05-28)

Post by just me » 05 Jul 2023, 02:55

Code: Select all

EditGui := Gui()
MainBtn1 := EditGui.AddButton("w200 h20", "Button1")
MainBtn2 := EditGui.AddButton("w200 h20", "Button2").OnEvent("Click", (*) => ExitApp())
MsgBox("MainBtn1: " . Type(MainBtn1) . "`nMainBtn2: " . Type(MainBtn2), "Variable types", 0)
MsgBox wrote:---------------------------
Variable types
---------------------------
MainBtn1: Gui.Button
MainBtn2: String
---------------------------
OK
---------------------------

You need to pass a GuiControl object to the SetTip() method.

Post Reply

Return to “Scripts and Functions (v2)”