工具提示控制元件(轉)

heying1229發表於2007-07-28
提示控制元件:

  我們將學習提示控制元件:它是什麼如何建立和使用.下載例子

理論:
提示是當滑鼠在某特定區域上停留時顯示的一個矩形視窗.提示視窗包含一些程式設計者想要顯示的文字.在這點上,提示同狀態列的作用是一樣的,所不同的是提示當單擊或者遠離指定區域的時候就會消逝,你可能熟悉與欄相關聯的提示,那些"提示"是欄控制元件提供的便利.如果你想要在其它視窗、控制元件中顯示提示的話,就不得不自己建立他們.

既然已經瞭解了什麼是提示,就讓我們來看看如何建立他們.大致步驟如下:

用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
.......

.......
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 masm32includewindows.inc
include masm32includekernel32.inc
include masm32includeuser32.inc
include masm32includecomctl32.inc
includelib masm32libcomctl32.lib
includelib masm32libuser32.lib
includelib masm32libkernel32.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標誌.

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10172717/viewspace-928767/,如需轉載,請註明出處,否則將追究法律責任。

相關文章