我们将学习工具提示控件:它是什么如何创建和使用.
工具提示是当鼠标在某特定区域上停留时显示的一个矩形窗口.工具提示窗口包含一些编程者想要显示的文本.在这点上,工具提示同状态栏的作用是一样的,所不同的是工具提示当单击或者远离指定区域的时候就会消逝,你可能熟悉与工具栏相关联的工具提示,那些"提示"是工具栏控件提供的便利.如果你想要在其它窗口、控件中显示工具提示的话,就不得不自己创建他们.
既然已经了解了什么是工具提示,就让我们来看看如何创建他们.大致步骤如下:
.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注意窗口风格:TIS_ALWAYSTIP指定了工具提示不管包含指定区域的窗口状态如何,当鼠标移过指定区域的时候,工具提示总是显示.简单的说就是,即使窗口处于非激活状态,鼠标移过工具提示指定区域的时候,工具提示也会出现.
工具提示控件创建了但还没有显示,我们想要当鼠标指针在某个区域之上时显示工具提示窗口.现在需要指定这个区域.我们称这样的区域为"工具",“工具”就是工具提示控件监视鼠标指针是否移过的位于窗口客户区的一个方形区域.如果鼠标指针移过"工具",工具提示窗口就显示."工具"可覆盖整个客户区或者仅仅是它的一部分.因此我们把"工具"分成两种类型,一种是作为一个窗口,另一种则是某窗口客户区的一部分.两种各有所用.覆盖整个客户区的"工具"通常用于按钮、编辑控件等,你不必指定焦点域的坐标和大小:它被假定为窗口的整个客户区.仅覆盖窗口客户区一部分的"工具"在你想把窗口客户区分成几个部分但又不想使用子窗口时特别有用,但需要指定左上角的坐标和宽高.
使用如下的 TOOLINFO 结构定义"工具":
TOOLINFO STRUCT CBSIZE DWORD ? UFLAGS DWORD ? HWND DWORD ? UID DWORD ? rect RECT <> HINST DWORD ? LPSZTEXT DWORD ? lParam LPARAM ? TOOLINFO ENDS
域名 | 说明 |
cbSize | TOOLINFO结构的大小.必须填充, 如果这个区域不被正确填充Windows并不会报错,但你会得到不可预料的奇怪结果. |
uFlags | 指定焦点域的属性,可以是如下标志的联合:
|
hWnd | 包含"工具"的窗口句柄,如果你指定了TTF_IDISHWND标志,Windows将忽略该值,而使用uId成员的值作为窗口句柄.你需要填充这个域域如果:
|
uId | 这个域的值可能有两种含义,依 uFlags 是否包含TTF_IDISHWND.
|
rect | 指定"工具"大小的rect结构.这个结构定义了一个以hWnd指定窗口客户区左上角为基点的方形大小,简言之,如果你想指定客户区的一部分作为"工具"就得填充这个结构,如果你指定了TTF_IDISHWND标志 ,控件就会忽略这个值.(你已经选择整个客户区作为"工具") |
hInst | 如果lpszText指定了字符串资源的标识,包含将作为工具提文本字符串资源的实例句柄.听起来有点费解,阅读一下lpszText的说明就可以明白这个域是干什么用的了.若lpszText不包含字符串资源标识,控件会忽略这个域. |
lpszText | 这个域可以有如下几个值:
|
总言之,你需要将TOOLINFO结构传递给工具提示控件之前填充填充好,它描述了你期望的"工具"属性.
填充完TOOLINFO结构后, 必须将其传递给控件 . 一个工具提示控件可以控制很多"工具",因此不必为一个窗口创建很多控件,为了注册"工具",向控件发送TTM_ADDTOOL消息 wParam不使用,lParam必须包含要注册的TOOLINFO结构的指针
.DATA? ti TOOLINFO <> ....... .CODE .............. INVOKE SENDMESSAGE, HWNDTOOLTIP, TTM_ADDTOOL, NULL, ADDR TI
成功返回 TRUE,否则返回 FALSE.
发送 TTM_DELTOOL消息取消注册.
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
分析:
创建主对话框窗口之后,使用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
之后,我们继续定义对话框四个角作为焦点域.
MOV ID,0 ; 焦点域ID 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
我们初始化TOOLINFO结构. 注意我们要把客户区分成4个焦点域,因此我们需要知道客户区的大小,所以调用GetWindowRect.因为我们不想自己向控件转发消息,因此指定TIF_SUBCLASS
标志.
SetDlgToolArea 是计算焦点域矩形范围的并向控件注册的函数,我不详细解释计算过程.只说明它把对话框分成4个焦点域.然后向控件发送TTM_ADDTOOL
消息, 在lParam参数中传递TOOLINFO结构的地址.
invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti
在四个控件注册之后,我们来看看对话框的按钮,我们可以用ID来处理每个按钮,但是实在太乏味了.我们使用EnumChildWindows函数列举对话框上的所有控件并把他们注册给控件,EnumChildWindows原型如下:
EnumChildWindows proto hWnd:DWORD, lpEnumFunc:DWORD, lParam:DWORD
hWnd 是父窗口句柄.
lpEnumFunc 是每个控件将调用的EnumChildProc函数地址.lParam 是应用程序定义的要传给EnumChildProc 函数的值. EnumChildProc 函数定义如下:
EnumChildProc proto hwndChild:DWORD, lParam:DWORDhwndChild是EnumChildWindows函数枚举的句柄. lParam 就是你传递给EnumChildWindows函数的同一个lParam.
invoke EnumChildWindows,hDlg,addr EnumChild,addr ti
我们把TOOLINFO结构的地址放在lParam参数中传递,是因为我们要在EnumChild函数中注册每个子控件.如果我们不使用这种方法,就需要将ti声明为全局变量,但这可能会引入很多bug.
当我们调用 EnumChildWindows时, Windows会枚举出对话框上所有的子控件并为每个子控件调用一次EnumChild
f函数. 这样如果我们的对话框有两个控件,EnumChild将被调用两次.
EnumChild 函数填充TOOLINFO 结构的相应成员并向控件注册.
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
注意在例子中,我们使用了一种不同"工具":覆盖整个客户区的"工具",因此我们需要用包含"工具"窗口的句柄来填充uID成员,也必须在uFlags 成员中指定TTF_IDISHWND标志.