# Tutorial 27: Tooltip Control

We will learn about the tooltip control: What it is and how to create and use it. Download the example.

### Theory:

A tooltip is a small rectangular window that is displayed when the mouse pointer hovers over some specific area. A tooltip window contains some text that the programmer wants to be displayed. In this regard, a tooltip servers the same role as the status window but it disappears when the user clicks or moves the mouse pointer away from the designated area. You'll probably be familiar with the tooltips that are associated with toolbar buttons. Those "tooltips" are conveniencies provided by the toolbar control. If you want tooltips for other windows/controls, you need to create your own tooltip control.
Now that you know what a tooltip is, let's go on to how we can create and use it. The steps are outlined below:

1. Create a tooltip control with CreateWindowEx
2. Define a region that the tooltip control will monitor for mouse pointer movement.
3. Submit the region to the tooltip control
4. Relay mouse messages of the submitted region to the tooltip control (this step may occur earlier, depending on the method used to relay the messages)

We wll next examine each step in detail.

#### Tooltip Creation

A tooltip control is a common control. As such, you need to call InitCommonControls somewhere in your source code so that MASM implicitly links your program to comctl32.dll. You create a tooltip control with CreateWindowEx. The typical scenario would be like this:

> .data
> TooltipClassName db "Tooltips_class32",0
> .code
> .....
> invoke InitCommonControls
> invoke CreateWindowEx, NULL, addr TooltipClassName, NULL, TIS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL

Note the window style: TIS_ALWAYSTIP. This style specifies that the tooltip will be shown when the mouse pointer is over the designated area regardless of the status of the window that contains the area. Put simply, if you use this flag, when the mouse pointer hovers over the area you register to the tooltip control, the tooltip window will appear even if the window under the mouse pointer is inactive.
You don't have to include WS_POPUP and WS_EX_TOOLWINDOW styles in CreateWindowEx because the tooltip control's window procedure adds them automatically. You also don't need to specify the coordinate, the height and width of the tooltip window: the tooltip control will adjust them automatically to fit the tooltip text that will be displayed, thus we supply CW_USEDEFAULT in all four parameters. The remaining parameters are not remarkable.

#### Specifying the tool

The tooltip control is created but it's not shown immediately. We want the tooltip window to show up when the mouse pointer hovers over some area. Now is the time to specify that area. We call such area "tool". A tool is a rectangular area on the client area of a window which the tooltip control will monitor for mouse pointer. If the mouse pointer hovers over the tool, the tooltip window will appear. The rectangular area can cover the whole client area or only a part of it. So we can divided tool into two types: one that is implemented as a window and another that is implemented as a rectangular area in the client area of some window. Both has their uses. The tool that covers the whole client area of a window is most frequently used with controls such as buttons, edit controls and so on. You don't need to specify the coordinate and the dimensions of the tool: it's assumed to be the whole client area of the window. The tool that is implemented as a rectangular area on the client area is useful when you want to divide the client area of a window into several regions without using child windows. With this type of tool, you need to specify the coordinate of the upper left corner and the width and height of the tool.
You specify the tool with the TOOLINFO structure which has the following definition:

> `TOOLINFO STRUCT`
> `cbSize DWORD ?`
> `uFlags DWORD ?`
> `hWnd DWORD ?`
> `uId DWORD ?`
> `rect RECT <>`
> `hInst DWORD ?`
> `lpszText DWORD ?`
> `lParam LPARAM ?`
> `TOOLINFO ENDS`

Field Name | Explanation
cbSize | The size of the TOOLINFO structure. You MUST fill this member. Windows will not flag error if this field is not filled properly but you will receive strange, unpredictable results.
uFlags | The bit flags that specifies the characteristics of the tool. This value can be a combination of the following flags: TTF_IDISHWND "ID is hWnd". If you specify this flag, it means you want to use a tool that covers the whole client area of a window (the first type of tool above). If you use this flag, you must fill the uId member of this structure with the handle of the window you want to use. If you don't specify this flag, it means you want to use the second type of tool, the one that is implemented as the rectangular area on the client window. In that case, you need to fill the rect member with the dimension of the rectangle. TTF_CENTERTIP Normally the tooltip window will appear to the right and below the mouse pointer. If you specify this flag, the tooltip window will always appear directly below the tool and is centered regardless of the position of the mouse pointer. TTF_RTLREADING You can forget about this flag if your program is not designed specifically for Arabic or Hebrew systems. This flag displays the tooltip text with right-to-left reading order. Doesn't work under other systems. TTF_SUBCLASS If you use this flag, it means you tell the tooltip control to subclass the window that the tool is on so that the tooltip control can intercept mouse messages that are sent to the window. This flag is very handy. If you don't use this flag, you have to do more work to relay the mouse messages to the tooltip control.
hWnd | Handle to the window that contains the tool. If you specify TTF_IDISHWND flag, this field is ignored since Windows will use the value in uId member as the window handle. You need to fill this field if: You don't use TTF_IDISHWND flag (in other words, you use a rectangular tool) You specify the value LPSTR_TEXTCALLBACK in lpszText member. This value tells the tooltip control that, when it needs to display the tooltip window, it must ask the window that contains the tool for the text to be displayed. This is a kind of dynamic realtime tooltip text update. If you want to change your tooltip text dynamically, you should specify LPSTR_TEXTCALLBACK value in lpszText member. The tooltip control will send TTN_NEEDTEXT notification message to the window identified by the handle in hWnd field.
uId | The value in this field can have two meanings, depending on whether the uFlags member contains the flag TTF_IDISHWND. Application-defined tool ID if the TTF_IDISHWND flag is not specified. Since this means you use a tool which covers only a part of the client area, it's logical that you can have many such tools on the same client area (without overlap). The tooltip control needs a way to differentiate between them. In this case, the window handle in hWnd member is not enough since all tools are on the same window. The application-defined IDs are thus necessary. The IDs can be any value so long as they are unique among themselves. The handle to the window whose whole client area is used as the tool if the TTF_IDISHWND flag is specified. You may wonder why this field is used to store the window handle instead of the hWnd field above. The answer is: the hWnd member may already be filled if the value LPSTR_TEXTCALLBACK is specified in the lpszText member and the window that is responsible for supplying the tooltip text and the window that contains the tool may NOT be the same ( You can design your program so that a single window can serve both roles but this is too restrictive. In this case, Microsoft gives you more freedom. Cheers.)
rect | A RECT structure that specifies the dimension of the tool. This structure defines a rectangle relative to the upper left corner of the client area of the window specified by the hWnd member. In short, you must fill this structure if you want to specify a tool that covers only a part of the client area. The tooltip control will ignore this field if you specify TTF_IDISHWND flag (you choose to use a tool that covers the whole client area)
hInst | The handle of the instance that contains the string resource that will be used as the tooltip text if the value in the lpszText member specifies the string resource identifier. This may sound confusing. Read the explanation of the lpszText member first and you will understand what this field is used for. The tooltip control ignores this field if the lpszText field doesn't contain a string resource identifier.
lpszText | This field can have several values: If you specify the value LPSTR_TEXTCALLBACK in this field, the tooltip control will send TTN_NEEDTEXT notification message to the window identified by the handle in hWnd field for the text string to be displayed in the tooltip window. This is the most dynamic method of tooltip text update: you can change the tooltip text each time the tooltip window is displayed. If you specify a string resource identifier in this field, when the tooltip control needs to display the tooltip text in the tooltip window, it searches for the string in the string table of the instance specified by hInst member. The tooltip control identifies a string resource identifier by checking the high word of this field. Since a string resource identifier is a 16-bit value, the high word of this field will always be zero. This method is useful if you plan to port your program to other languages. Since the string resource is defined in a resource script, you don't need to modify the source code.You only have to modify the string table and the tooltip texts will change without the risk of introducing bugs into your program. If the value in this field is not LPSTR_TEXTCALLBACK and the high word is not zero, the tooltip control interprets the value as the pointer to a text string that will be used as the tooltip text. This method is the easiest to use but the least flexible.

To recapitulate, you need to fill the TOOLINFO structure prior to submitting it to the tooltip control. This structure describes the characteristics of the tool you desire.

#### Register the tool with the tooltip control

After you fill the TOOLINFO structure, you must submit it to tooltip control. A tooltip control can service many tools so it is usually unnecessary to create more than one tooltip control for a window. To register a tool with a tooltip control, you send the TTM_ADDTOOL message to the tooltip control. The wParam is not used and the lParam must contain the address of the TOOLINFO structure you want to register.

> .data?
> ti TOOLINFO <>
> .......
> .code
> .......
> <fill the TOOLINFO structure>
> .......
> invoke SendMessage, hwndTooltip, TTM_ADDTOOL, NULL, addr ti

SendMessage for this message will return TRUE if the tool is successfully registered with the tooltip control, FALSE otherwise.
You can unregister the tool by sending TTM_DELTOOL message to the tooltip control.

#### Relaying Mouse Messages to the Tooltip Control

When the above step is completed, the tooltip control knows which area it should monitor for mouse messages and what text it should display in the tooltip window. The only thing it lacks is the *trigger* for that action. Think about it: the area specified by the tool is on the client area of the other window. How can the tooltip control intercept the mouse messages destined for that window? It needs to do so in order that it can measure the amount of time the mouse pointer hovers over a point in the tool so that when the specified amount of time elapses, the tooltip control shows the tooltip window. There are two methods of accomplishing this goal, one that requires the cooperation of the window that contains the tool and the other without the cooperation on the part of the window.

- The window that contains the tool must relay the mouse messages to the tooltip control by sending TTM_RELAYEVENT messages to the control. The lParam of this message must contain the address of a MSG structure that specifies the message to be relayed to the tooltip control. A tooltip control processes only the following mouse messages:

- WM_LBUTTONDOWN
- WM_MOUSEMOVE
- WM_LBUTTONUP
- WM_RBUTTONDOWN
- WM_MBUTTONDOWN
- WM_RBUTTONUP
- WM_MBUTTONUP

All other messages are ignored. Thus in the window procedure of the window that contains the tool, there must be a switch that does something like this:

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.......
if uMsg==WM_CREATE
.............
elseif uMsg==WM_LBUTTONDOWN || uMsg==WM_MOUSEMOVE || uMsg==WM_LBUTTONUP || uMsg==WM_RBUTTONDOWN || uMsg==WM_MBUTTONDOWN || uMsg==WM_RBUTTONUP || uMsg==WM_MBUTTONUP
invoke SendMessage, hwndTooltip, TTM_RELAYEVENT, NULL, addr msg
..........

- You can specify TTF_SUBCLASS flag in the uFlags member of the TOOLINFO structure. This flag tells the tooltip control to subclass the window that contains the tool so it can intercept the mouse messages without the cooperation of the window. This method is easier to use since it doesn't require more coding than specifying TTF_SUBCLASS flag and the tooltip control handles all the message interception itself.

That's it. At this step, your tooltip control is fully functional. There are several useful tooltip-related messages you should know about.

- TTM_ACTIVATE. If you want to disable/enable the tooltip control dynamically, this message is for you. If the wParam value is TRUE, the tooltip control is enabled. If the wParam value is FALSE, the tooltip control is disabled. A tooltip control is enabled when it first created so you don't need to send this message to activate it.
- TTM_GETTOOLINFO and TTM_SETTOOLINFO. If you want to obtain/change the values in the TOOLINFO structure after it was submitted to the tooltip control, use these messages. You need to specify the tool you need to change with the correct uId and hWnd values. If you only want to change the rect member, use TTM_NEWTOOLRECT message. If you only want to change the tooltip text, use TTM_UPDATETIPTEXT.
- TTM_SETDELAYTIME. With this message, you can specify the time delay the tooltip control uses when it's displaying the tooltip text and much more.

### Example:

The following example is a simple dialog box with two buttons. The client area of the dialog box is divided into 4 areas: upper left, upper right, lower left and lower right. Each area is specified as a tool with its own tooltip text. The two buttons also has their own tooltip texts.

> `.386`
> `.model flat,stdcall`
> `option casemap:none`
> `include \masm32\include\windows.inc`
> `include \masm32\include\kernel32.inc`
> `include \masm32\include\user32.inc`
> `include \masm32\include\comctl32.inc`
> `includelib \masm32\lib\comctl32.lib`
> `includelib \masm32\lib\user32.lib`
> `includelib \masm32\lib\kernel32.lib`
> `DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD`
> `EnumChild proto :DWORD,:DWORD`
> `SetDlgToolArea proto :DWORD,:DWORD,:DWORD,:DWORD,:DWORD`
> `.const`
> `IDD_MAINDIALOG equ 101`
> `.data`
> `ToolTipsClassName db "Tooltips_class32",0`
> `MainDialogText1 db "This is the upper left area of the dialog",0`
> `MainDialogText2 db "This is the upper right area of the dialog",0`
> `MainDialogText3 db "This is the lower left area of the dialog",0`
> `MainDialogText4 db "This is the lower right area of the dialog",0`
> `.data?`
> `hwndTool dd ?`
> `hInstance dd ?`
> `.code`
> `start:`
> `invoke GetModuleHandle,NULL`
> `mov hInstance,eax`
> `invoke DialogBoxParam,hInstance,IDD_MAINDIALOG,NULL,addr DlgProc,NULL`
> `invoke ExitProcess,eax```

> `DlgProc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD`
> `LOCAL ti:TOOLINFO`
> `LOCAL id:DWORD`
> `LOCAL rect:RECT`
> `.if uMsg==WM_INITDIALOG`
> `invoke InitCommonControls`
> `invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\`
> `TTS_ALWAYSTIP,CW_USEDEFAULT,\`
> `CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\`
> `hInstance,NULL`
> `mov hwndTool,eax`
> `mov id,0`
> `mov ti.cbSize,sizeof TOOLINFO`
> `mov ti.uFlags,TTF_SUBCLASS`
> `push hDlg`
> `pop ti.hWnd`
> `invoke GetWindowRect,hDlg,addr rect`
> `invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect`
> `inc id`
> `invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText2,id,addr rect`
> `inc id`
> `invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText3,id,addr rect`
> `inc id`
> `invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText4,id,addr rect`
> `invoke EnumChildWindows,hDlg,addr EnumChild,addr ti`
> `.elseif uMsg==WM_CLOSE`
> `invoke EndDialog,hDlg,NULL`
> `.else`
> `mov eax,FALSE`
> `ret`
> `.endif`
> `mov eax,TRUE`
> `ret`
> `DlgProc endp```

> `EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD`
> `LOCAL buffer[256]:BYTE`
> `mov edi,lParam`
> `assume edi:ptr TOOLINFO`
> `push hwndChild`
> `pop [edi].uId`
> `or [edi].uFlags,TTF_IDISHWND`
> `invoke GetWindowText,hwndChild,addr buffer,255`
> `lea eax,buffer`
> `mov [edi].lpszText,eax`
> `invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi`
> `assume edi:nothing`
> `ret`
> `EnumChild endp```

> `SetDlgToolArea proc uses edi esi hDlg:DWORD,lpti:DWORD,lpText:DWORD,id:DWORD,lprect:DWORD`
> `mov edi,lpti`
> `mov esi,lprect`
> `assume esi:ptr RECT`
> `assume edi:ptr TOOLINFO`
> `.if id==0`
> `mov [edi].rect.left,0`
> `mov [edi].rect.top,0`
> `mov eax,[esi].right`
> `sub eax,[esi].left`
> `shr eax,1`
> `mov [edi].rect.right,eax`
> `mov eax,[esi].bottom`
> `sub eax,[esi].top`
> `shr eax,1`
> `mov [edi].rect.bottom,eax`
> `.elseif id==1`
> `mov eax,[esi].right`
> `sub eax,[esi].left`
> `shr eax,1`
> `inc eax`
> `mov [edi].rect.left,eax`
> `mov [edi].rect.top,0`
> `mov eax,[esi].right`
> `sub eax,[esi].left`
> `mov [edi].rect.right,eax`
> `mov eax,[esi].bottom`
> `sub eax,[esi].top`
> `mov [edi].rect.bottom,eax`
> `.elseif id==2`
> `mov [edi].rect.left,0`
> `mov eax,[esi].bottom`
> `sub eax,[esi].top`
> `shr eax,1`
> `inc eax`
> `mov [edi].rect.top,eax`
> `mov eax,[esi].right`
> `sub eax,[esi].left`
> `shr eax,1`
> `mov [edi].rect.right,eax`
> `mov eax,[esi].bottom`
> `sub eax,[esi].top`
> `mov [edi].rect.bottom,eax`
> `.else`
> `mov eax,[esi].right`
> `sub eax,[esi].left`
> `shr eax,1`
> `inc eax`
> `mov [edi].rect.left,eax`
> `mov eax,[esi].bottom`
> `sub eax,[esi].top`
> `shr eax,1`
> `inc eax`
> `mov [edi].rect.top,eax`
> `mov eax,[esi].right`
> `sub eax,[esi].left`
> `mov [edi].rect.right,eax`
> `mov eax,[esi].bottom`
> `sub eax,[esi].top`
> `mov [edi].rect.bottom,eax`
> `.endif`
> `push lpText`
> `pop [edi].lpszText`
> `invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti`
> `assume edi:nothing`
> `assume esi:nothing`
> `ret`
> `SetDlgToolArea endp`
> `end start`

### Analysis:

After the main dialog window is created, we create the tooltip control with CreateWindowEx.

> `invoke InitCommonControls`
> `invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\`
> `TTS_ALWAYSTIP,CW_USEDEFAULT,\`
> `CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\`
> `hInstance,NULL`
> `mov hwndTool,eax`

After that, we proceed to define four tools for each corner of the dialog box.

`mov id,0 ; used as the tool ID`
`mov ti.cbSize,sizeof TOOLINFO`
`mov ti.uFlags,TTF_SUBCLASS ; tell the tooltip control to subclass the dialog window.`
`push hDlg`
`pop ti.hWnd ; handle to the window that contains the tool`
`invoke GetWindowRect,hDlg,addr rect ; obtain the dimension of the client area`
`invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect`

We initialize the members of TOOLINFO structure. Note that we want to divide the client area into 4 tools so we need to know the dimension of the client area. That's why we call GetWindowRect. We don't want to relay mouse messages to the tooltip control ourselves so we specify TIF_SUBCLASS flag.
SetDlgToolArea is a function that calculates the bounding rectangle of each tool and registers the tool to the tooltip control. I won't go into gory detail on the calculation, suffice to say that it divides the client area into 4 areas with the same sizes. Then it sends TTM_ADDTOOL message to the tooltip control, passing the address of the TOOLINFO structure in the lParam parameter.

`invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti`

After all 4 tools are registered, we can go on to the buttons on the dialog box. We can handle each button by its ID but this is tedious. Instead, we will use EnumChildWindows API call to enumerate all controls on the dialog box and then registers them to the tooltip control. EnumChildWindows has the following syntax:

> EnumChildWindows proto hWnd:DWORD, lpEnumFunc:DWORD, lParam:DWORD

hWnd is the handle to the parent window. lpEnumFunc is the address of the EnumChildProc function that will be called for each control enumerated. lParam is the application-defined value that will be passed to the EnumChildProc function. The EnumChildProc function has the following definition:

> EnumChildProc proto hwndChild:DWORD, lParam:DWORD

hwndChild is the handle to a control enumerated by EnumChildWindows. lParam is the same lParam value you pass to EnumChildWindows.
In our example, we call EnumChildWindows like this:

> `invoke EnumChildWindows,hDlg,addr EnumChild,addr ti`

We pass the address of the TOOLINFO structure in the lParam parameter because we will register each child control to the tooltip control in the EnumChild function. If we don't use this method, we need to declare ti as a global variable which can introduce bugs.
When we call EnumChildWindows, Windows will enumerate the child controls on our dialog box and call the EnumChild function once for each control enumerated. Thus if our dialog box has two controls, EnumChild will be called twice.
The EnumChild function fills the relevant members of the TOOLINFO structure and then registers the tool with the tooltip control.

> `EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD`
> `LOCAL buffer[256]:BYTE`
> `mov edi,lParam`
> `assume edi:ptr TOOLINFO`
> `push hwndChild`
> `pop [edi].uId ; we use the whole client area of the control as the tool`
> `or [edi].uFlags,TTF_IDISHWND`
> `invoke GetWindowText,hwndChild,addr buffer,255`
> `lea eax,buffer ; use the window text as the tooltip text`
> `mov [edi].lpszText,eax`
> `invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi`
> `assume edi:nothing`
> `ret`
> `EnumChild endp`

Note that in this case, we use a different type of tool: one that covers the whole client area of the window. We thus need to fill the uID field with the handle to the window that contains the tool. Also we must specify TTF_IDISHWND flag in the uFlags member.

[Iczelion's Win32 Assembly Homepage]