理論:
工具提示是當鼠標在某特定區域上停留時顯示的一個矩形窗口.工具提示窗口包含一些編程者想要顯示的文本.在這點上,工具提示同狀態欄的作用是一樣的,所不同的是工具提示當單擊或者遠離指定區域的時候就會消逝,你可能熟悉與工具欄相關聯的工具提示,那些"提示"是工具欄控件提供的便利.如果你想要在其它窗口、控件中顯示工具提示的話,就不得不自己創建他們.
既然已經了解了什麼是工具提示,就讓我們來看看如何創建他們.大致步驟如下:
用CreateWindowEx函數創建工具提示控件.
定義一個工具提示控件將要監視鼠標移動的區域.
將區域傳遞給工具提示控件
將傳遞區域的鼠標消息轉送給工具提示控件.(這步或許更早,具體依據轉播消息的方法)
下面我們就來詳細的討論每一步.
工具提示控件的創建
工具提示控件是一種通用控件.同樣,要在源代碼某處調用InitCommonControls以便MASM能夠將你的程序和comctl32.dll連接. 用CreateWindowEx創建工具提示控件,典型代碼如下:
.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指定了工具提示不管包含指定區域的窗口狀態如何,當鼠標移過指定區域的時候,工具提示總是顯示.簡單的說就是,即使窗口處於非激活狀態,鼠標移過工具提示指定區域的時候,工具提示也會出現.
你不必在CreateWindowEx中包括WS_POPUP 和 WS_EX_TOOLWINDOW風格,因為工具提示處理過程會自動加上,你也不必指定工具提示窗口的坐標和寬高,控件會依據要顯示的文字自動調節.四個參數,均使用CW_USEDEFAULT ,其余的參數都不太重要.
指定工具
工具提示控件創建了但還沒有顯示,我們想要當鼠標指針在某個區域之上時顯示工具提示窗口.現在需要指定這個區域.我們稱這樣的區域為"工具",“工具”就是工具提示控件監視鼠標指針是否移過的位於窗口客戶區的一個方形區域.如果鼠標指針移過"工具",工具提示窗口就顯示."工具"可覆蓋整個客戶區或者僅僅是它的一部分.因此我們把"工具"分成兩種類型,一種是作為一個窗口,另一種則是某窗口客戶區的一部分.兩種各有所用.覆蓋整個客戶區的"工具"通常用於按鈕、編輯控件等,你不必指定焦點域的坐標和大小:它被假定為窗口的整個客戶區.僅覆蓋窗口客戶區一部分的"工具"在你想把窗口客戶區分成幾個部分但又不想使用子窗口時特別有用,但需要指定左上角的坐標和寬高.
使用如下的 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 指定焦點域的屬性,可以是如下標志的聯合:
TTF_IDISHWND "ID is hWnd".如果你指定了這個標志,就意味著你要使用覆蓋整個客戶區的"工具" (上面第一種"工具"). 如果你使用了這個標志,你必須用你要使用的窗口句柄填充uId成員,如果你不指定這個成員,就意味著你要使用第二種"工具"、客戶區窗口的一方形區域.在這種情況下,你就必須以方形區域的大小填充rect成員.
TTF_CENTERTIP 通常工具提示窗口顯示在鼠標的右下方,如果你指定了這個標志,不管鼠標的位置如何,工具提示總顯示在焦點域總的中下方.
TTF_RTLREADING .如果你的程序不是為阿拉伯或者希伯來語系統設計的,你完全可以不理它,它使得提示文本以從右至左的順序顯示,在其它系統中無效.
TTF_SUBCLASS 如果你使用了這個標志,工具提示控件將子類化"工具"所在窗口以便截取發送給它的的鼠標消息,這個標志非常有用,否則你將不得不做更多的工作來向工具提示控件轉發消息.
hWnd 包含"工具"的窗口句柄,如果你指定了TTF_IDISHWND標志,Windows將忽略該值,而使用uId成員的值作為窗口句柄.你需要填充這個域域如果:
你不使用 TTF_IDISHWND標志 (換句話說,你使用局部"工具")
你在 lpszText 成員中指定了LPSTR_TEXTCALLBACK .這個值告訴工具提示控件當需要顯示提示窗口時,必須向包含"工具"的窗口查詢應該顯示什麼. 這是一種實時的控件文本更新.如果你需要動態改變提示文本,你應當在 lpszText成員中指定LPSTR_TEXTCALLBACK值,控件就會向hWnd指定的窗口發送TTN_NEEDTEXT 消息.
uId 這個域的值可能有兩種含義,依 uFlags 是否包含TTF_IDISHWND.
如果TTF_IDISHWND標志沒有被指定就代表應用程序定義的"工具"ID,由於這意味著你使用僅覆蓋客戶區一部分的"工具",邏輯的推出一個客戶區可能存在多個同樣的焦點域(不存在交迭),Hwnd成員的一個窗口句柄就不夠了,應用程序定義ID以區分他們因此而顯得必要,只要唯一ID可以是任何值.
如果TTF_IDISHWND標志被指定就表示整個客戶區都作為焦點域的窗口句柄,你或許會奇怪為什麼不用上面提到的hWnd成員的值來儲存窗口句柄.答案是:如果lpszText指定為LPSTR_TEXTCALLBACK,Hwnd 可能已經被填充了.還有提供提示文本的窗口和包含"工具"的窗口可能不是同一個!(你可以設計一個提供兩種服務的窗口程序,但太嚴格了,在這點上,微軟為我們提供了更大的自由,歡呼吧!)
rect 指定"工具"大小的rect結構.這個結構定義了一個以hWnd指定窗口客戶區左上角為基點的方形大小,簡言之,如果你想指定客戶區的一部分作為"工具"就得填充這個結構,如果你指定了TTF_IDISHWND標志 ,控件就會忽略這個值.(你已經選擇整個客戶區作為"工具")
hInst 如果lpszText指定了字符串資源的標識,包含將作為工具提文本字符串資源的實例句柄.聽起來有點費解,閱讀一下lpszText的說明就可以明白這個域是干什麼用的了.若lpszText不包含字符串資源標識,控件會忽略這個域.
lpszText 這個域可以有如下幾個值:
如果指定為LPSTR_TEXTCALLBACK, 工具提示控件就會向HWnd窗口發送TTN_NEEDTEXT消息以獲得將要顯示的字符串.提示文本的動態更新方法:每次顯示提示窗口都改變提示文本.
如果在這個域中指定字符串資源標識,當控件要在提示窗口中顯示提示文本時,就搜索hInst成員標識的實例的字符串資源列表.由於字符串資源列表標識總是16位值,這個域的高字節將永遠為0,這種方法在你想移植程序時非常有用,由於字符串資源以腳本形式定義,你不必修改源代碼.只需要修改字符串列表提示文本就會相應改變,而不必擔心引進bugs.
如果這個域的值不是LPSTR_TEXTCALLBACK並且高字節不為零, 控件截取這個值作為提示文本的指針,這是最簡單的方法但也最不穩定.通過檢查高字節區分字符串資源標識.
總言之,你需要將TOOLINFO結構傳遞給工具提示控件之前填充填充好,它描述了你期望的"工具"屬性.
向工具提示控件注冊"工具"
填充完TOOLINFO結構後, 必須將其傳遞給控件 . 一個工具提示控件可以控制很多"工具",因此不必為一個窗口創建很多控件,為了注冊"工具",向控件發送TTM_ADDTOOL消息 wParam不使用,lParam必須包含要注冊的TOOLINFO結構的指針
.data?
ti TOOLINFO <>
.......
.code
.......
<fill the TOOLINFO structure>
.......
invoke SendMessage, hwndTooltip, TTM_ADDTOOL, NULL, addr ti
成功返回 TRUE,否則返回 FALSE.
發送 TTM_DELTOOL消息取消注冊.
向工具提示控件轉發鼠標消息
以上步驟完畢之後,控件知道了應當監視那一塊區域和應該在提示窗口顯示什麼.唯一缺乏的就是激發機制. 想想看:"工具"指定的其它窗口的客戶區的區域.控件如何截取發送向該窗口的消息呢?實際中需要截取消息以便了解鼠標停留了多長時間,當指定時間流逝以後,控件顯示提示窗口.有兩種方法: 一種需要包含"工具"窗口的合作,另一種則不需要.
包含"工具"的窗口必須向控件發送 TTM_RELAYEVENT 以轉發消息. lParam是指向要轉發消息MSG的指針 控件僅處理如下鼠標消息 :
WM_LBUTTONDOWN
WM_MOUSEMOVE
WM_LBUTTONUP
WM_RBUTTONDOWN
WM_MBUTTONDOWN
WM_RBUTTONUP
WM_MBUTTONUP
其它消息全被忽略,因此包含"工具"的窗口的處理過程必須包含像這樣的選擇:
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
..........
.你可以在TOOLINFO結構的uFlags成員指定 TTF_SUBCLASS標志。此標志告訴控件子類化包含"工具"的窗口以便無需窗口的協作便可捕獲鼠標消息。由於除了控件自己處理截獲的鼠標消息和指定TTF_SUBCLASS標志之外不用編寫多余的代碼,因此很易於使用。
就是這些了,到這步為止,控件已經全功能了.還有幾個你應當知道的相關消息.
TTM_ACTIVATE.如果你想動態的允許或者禁止工具提示控件,這個小消息就是為你而備.wParam值為TRUE,允許控件.若為FALSE,禁止控件.控件初始創建的時候無需發送消息激活他,便被自動設為允許狀態.
TTM_GETTOOLINFO and TTM_SETTOOLINFO. 如果你想在把TOOLINFO結構傳遞給控件之後獲得或者改變其值,使用此消息.你需要用正確的uId and hWnd值指定要改變的"工具".如果你只想改變rect成員的值,使用TTM_NEWTOOLRECT 消息,如果僅想改變提示文本,使用TTM_UPDATETIPTEXT消息.
TTM_SETDELAYTIME. 使用此消息指定控件顯示提示文本時的時間延遲.
例子:
例子是一個有兩個按鈕的對話框,對話框的客戶區分為4部分:左上、右上、左下、右下.每個區域都指定為有自己提示文本的"工具",兩個按鈕也有自己的提示文本.
.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:DWORD
hwndChild是EnumChildWindows函數枚舉的句柄. lParam 就是你傳遞給EnumChildWindows函數的同一個lParam.
在例子中.我們如此調用 EnumChildWindows 函數:
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標志.