Tutorial 26: Splash Screen | Tutorial 27: Tooltip Control | Tutorial 28: Win32 Debug API Part 1 |
We will learn about the tooltip control: What it is and how to create and use it.
Download the example.
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:
We will next examine each step in detail.
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.
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:
|
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:
|
uId | The value in this field can have two meanings,
depending on whether the uFlags member
contains the flag
TTF_IDISHWND.
|
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:
|
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.
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.
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.
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
..........
.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
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 TOOLINFOstructure.
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.
Tutorial 26: Splash Screen | Overview | Tutorial 28: Win32 Debug API Part 1 |