理論:
如果你曾經(jīng)在 Windows 環(huán)境下編過(guò)程序,有時(shí)候就會(huì)發(fā)現(xiàn):有一個(gè)現(xiàn)成的窗口,幾乎有你所需要的全部功能,但還不完全一樣(否則就沒(méi)有必要講這一節(jié)了)。你曾遇到過(guò)這樣的處境嗎,如果你需要一個(gè)具有過(guò)濾特殊字符功能的 Edit 控件。當(dāng)然最直接的方法就是自己用代碼來(lái)實(shí)現(xiàn),但這的確是一個(gè)費(fèi)時(shí)又很困難的任務(wù),而窗口子類化就可以用來(lái)做這種事情。
窗口子類化允許你接管被子類化的窗口,使你對(duì)它有絕對(duì)的控制權(quán)。舉個(gè)例子了來(lái)闡明一下:例如你需要一個(gè)只接受十六進(jìn)制數(shù)字輸入的文本編輯框,如果使用一個(gè)簡(jiǎn)單的 Edit控件,當(dāng)用戶輸入十六進(jìn)制以外的字符時(shí),你既不知道也無(wú)計(jì)可施。也就是說(shuō),當(dāng)用戶進(jìn)文本框中輸入字符串 "zb+q*" 時(shí),如果除了拒絕接受整個(gè)字符串以外幾乎什么也不能做,至少這顯得特別不專業(yè)。重要的是,你需要具有輸入檢測(cè)的能力,即每當(dāng)用戶輸入一個(gè)字符到編輯框中時(shí)要能檢測(cè)這個(gè)字符。
現(xiàn)在來(lái)解釋實(shí)現(xiàn)細(xì)節(jié):當(dāng)用戶往文本框中輸入字符時(shí),Windows 會(huì)給Edit控件的窗口函數(shù)發(fā)送 WM_CHAR 消息。這個(gè)窗口函數(shù)本身寄生于 Windows 中,因此不能直接修改它。但是我們可以重定向這個(gè)消息使之發(fā)送到我們自己編寫(xiě)的窗口處理函數(shù)。如果自定義窗口要處理這個(gè)消息那就可以處理它,如果不處理就可以把這個(gè)消息轉(zhuǎn)發(fā)到它原來(lái)窗口處理函數(shù)。通過(guò)這種方式,自定義的窗口處理函數(shù)就把它自己插入到 Windows 系統(tǒng)和 Edit 控件之間。
看下面的流程:
窗口子類化之前
Windows ==>Edit 控件的窗口處理函數(shù)。
子類化之后
Windows ==>自定義的窗口處理函數(shù)==> Edit 控件的窗口處理函數(shù)。
注意子類化并不局限于控件,可以子類化任何窗口,現(xiàn)在我們要把精力集中到怎樣實(shí)現(xiàn)子類化一個(gè)窗口上。讓我們想想Windows 怎樣知道 Edit 控件的窗口處理函數(shù)放在什么地方。猜的?…肯定不是。原來(lái) WNDCLASSEX 結(jié)構(gòu)的成員 lpfnWndProc 指出了窗口函數(shù)地址。如果能用自己編寫(xiě)的窗口函數(shù)的地址來(lái)替換這個(gè)成員變量,那 Windows 不就把消息發(fā)到自定義的窗口函數(shù)了嗎! 我們通過(guò)調(diào)用函數(shù)SetWindowLong 來(lái)實(shí)現(xiàn)這個(gè)任務(wù),此函數(shù)的原型為:
SetWindowLong PROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORD
hWnd = 將要實(shí)施子類化的窗口的句柄
nIndex = 函數(shù)了功能索引
GWL_EXSTYLE 設(shè)置窗口的擴(kuò)展風(fēng)格.
GWL_STYLE 設(shè)置新的窗口風(fēng)格
GWL_WNDPROC 設(shè)置新的窗口處理函數(shù)地址
GWL_HINSTANCE 設(shè)置新的應(yīng)用程序句柄
GWL_ID 設(shè)置新的窗口標(biāo)識(shí)
GWL_USERDATA 設(shè)置一個(gè)與這個(gè)窗口相關(guān)的給用戶使用的32位的數(shù)據(jù)
dwNewLong = 用來(lái)更新的數(shù)據(jù)
我們的工作還是比較簡(jiǎn)單的:
寫(xiě)一個(gè)窗口函數(shù)用于處理發(fā)給 Edit 控件的消息。
用參數(shù)GWL_WNDPROC調(diào)用SetWindowLong 函數(shù),如果調(diào)用成功那么返回值就是與調(diào)用功能相聯(lián)系的一個(gè)32位的整數(shù)
在我們的程序中,返回值就是原先窗口函數(shù)的地址。我們要保存這個(gè)值以便以后使用。 記?。河幸恍┪覀儾惶幚淼南?,需要把它們派遣給原來(lái)的窗口函數(shù)來(lái)處理,這就用到另外一個(gè)函數(shù) CallWindowProc, 函數(shù)原型為:
CallWindowProc PROTO lpPrevWndFunc:DWORD, hWnd:DWORD, Msg:DWORD, wParam:DWORD, lParam:DWORD
lpPrevWndFunc = 窗口原來(lái)函數(shù)的地址. 剩下的四個(gè)參數(shù)就是發(fā)給自定義函數(shù)的參數(shù),直接把它們傳給函數(shù) CallWindowProc 就行了。
代碼舉例:
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
ClassName db "SubclassWinClass",0
AppName db "Subclassing Demo",0
EditClass db "EDIT",0
Message db "You pressed Enter in the text box!",0
.data?
hInstance HINSTANCE ?
hwndEdit dd ?
OldWndProc dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR EditClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
20,300,25,hWnd,NULL,\
hInstance,NULL
mov hwndEdit,eax
invoke SetFocus,eax
;-----------------------------------------
; Subclass it!
;-----------------------------------------
invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc
mov OldWndProc,eax
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_CHAR
mov eax,wParam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
ret
.endif
.elseif uMsg==WM_KEYDOWN
mov eax,wParam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
EditWndProc endp
end start
分析:
invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc
mov OldWndProc,eax
在創(chuàng)建 Edit 控件后,通過(guò)調(diào)用 SetWindowLong 把原來(lái)的窗口函數(shù)地址替換為自定義函數(shù)的地址,從而對(duì)它實(shí)施了窗口子類化,要注意 為了調(diào)用函數(shù) CallWindowProc,我們存儲(chǔ)了原窗口函數(shù)地址,自已編寫(xiě)的EditWndProc 僅僅是個(gè)普普通通的窗口函數(shù)。當(dāng)然也可以再調(diào)用一次 SetWindowLong 函數(shù)來(lái)存儲(chǔ)這個(gè)32位的值,
invoke SetWindowLong ,hwndEdit,GWL_USERDATA,eax 。
當(dāng)然用的時(shí)候就要調(diào)用GetWindowLong 來(lái)取回這個(gè)值。
.if uMsg==WM_CHAR
mov eax,wParam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
ret
.endif
在函數(shù) EditWndProc 中,我們自己處理了WM_CHAR消息: 如果輸入的字符是'0'--'9'、'A'-'F'或者是'a'--'f'就接受,并且把此消息轉(zhuǎn)發(fā)給原窗口函數(shù),其中若輸入的是小寫(xiě)的'a'--'f'就把它變?yōu)榇髮?xiě)。如果輸入的不是十六進(jìn)制字符,就丟掉它,并且也不轉(zhuǎn)發(fā)此消息。因此當(dāng)輸入是非十六進(jìn)制字符時(shí),這個(gè)字符就不會(huì)顯示在 Edit 控件中。
.elseif uMsg==WM_KEYDOWN
mov eax,wParam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.end
在這里我們通過(guò)處理 回車(Enter) 鍵進(jìn)一步示范了子類化的能力。EditWndProc 通過(guò)檢查 WM_KEYDONW 消息來(lái)判斷是否是 回車鍵,若是顯示提示消息框,否則轉(zhuǎn)發(fā)此消息。 你可以用窗口子類化來(lái)控制另外的窗口,這是必須掌握的十分有用的技術(shù)之一。
如果你曾經(jīng)在 Windows 環(huán)境下編過(guò)程序,有時(shí)候就會(huì)發(fā)現(xiàn):有一個(gè)現(xiàn)成的窗口,幾乎有你所需要的全部功能,但還不完全一樣(否則就沒(méi)有必要講這一節(jié)了)。你曾遇到過(guò)這樣的處境嗎,如果你需要一個(gè)具有過(guò)濾特殊字符功能的 Edit 控件。當(dāng)然最直接的方法就是自己用代碼來(lái)實(shí)現(xiàn),但這的確是一個(gè)費(fèi)時(shí)又很困難的任務(wù),而窗口子類化就可以用來(lái)做這種事情。
窗口子類化允許你接管被子類化的窗口,使你對(duì)它有絕對(duì)的控制權(quán)。舉個(gè)例子了來(lái)闡明一下:例如你需要一個(gè)只接受十六進(jìn)制數(shù)字輸入的文本編輯框,如果使用一個(gè)簡(jiǎn)單的 Edit控件,當(dāng)用戶輸入十六進(jìn)制以外的字符時(shí),你既不知道也無(wú)計(jì)可施。也就是說(shuō),當(dāng)用戶進(jìn)文本框中輸入字符串 "zb+q*" 時(shí),如果除了拒絕接受整個(gè)字符串以外幾乎什么也不能做,至少這顯得特別不專業(yè)。重要的是,你需要具有輸入檢測(cè)的能力,即每當(dāng)用戶輸入一個(gè)字符到編輯框中時(shí)要能檢測(cè)這個(gè)字符。
現(xiàn)在來(lái)解釋實(shí)現(xiàn)細(xì)節(jié):當(dāng)用戶往文本框中輸入字符時(shí),Windows 會(huì)給Edit控件的窗口函數(shù)發(fā)送 WM_CHAR 消息。這個(gè)窗口函數(shù)本身寄生于 Windows 中,因此不能直接修改它。但是我們可以重定向這個(gè)消息使之發(fā)送到我們自己編寫(xiě)的窗口處理函數(shù)。如果自定義窗口要處理這個(gè)消息那就可以處理它,如果不處理就可以把這個(gè)消息轉(zhuǎn)發(fā)到它原來(lái)窗口處理函數(shù)。通過(guò)這種方式,自定義的窗口處理函數(shù)就把它自己插入到 Windows 系統(tǒng)和 Edit 控件之間。
看下面的流程:
窗口子類化之前
Windows ==>Edit 控件的窗口處理函數(shù)。
子類化之后
Windows ==>自定義的窗口處理函數(shù)==> Edit 控件的窗口處理函數(shù)。
注意子類化并不局限于控件,可以子類化任何窗口,現(xiàn)在我們要把精力集中到怎樣實(shí)現(xiàn)子類化一個(gè)窗口上。讓我們想想Windows 怎樣知道 Edit 控件的窗口處理函數(shù)放在什么地方。猜的?…肯定不是。原來(lái) WNDCLASSEX 結(jié)構(gòu)的成員 lpfnWndProc 指出了窗口函數(shù)地址。如果能用自己編寫(xiě)的窗口函數(shù)的地址來(lái)替換這個(gè)成員變量,那 Windows 不就把消息發(fā)到自定義的窗口函數(shù)了嗎! 我們通過(guò)調(diào)用函數(shù)SetWindowLong 來(lái)實(shí)現(xiàn)這個(gè)任務(wù),此函數(shù)的原型為:
SetWindowLong PROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORD
hWnd = 將要實(shí)施子類化的窗口的句柄
nIndex = 函數(shù)了功能索引
GWL_EXSTYLE 設(shè)置窗口的擴(kuò)展風(fēng)格.
GWL_STYLE 設(shè)置新的窗口風(fēng)格
GWL_WNDPROC 設(shè)置新的窗口處理函數(shù)地址
GWL_HINSTANCE 設(shè)置新的應(yīng)用程序句柄
GWL_ID 設(shè)置新的窗口標(biāo)識(shí)
GWL_USERDATA 設(shè)置一個(gè)與這個(gè)窗口相關(guān)的給用戶使用的32位的數(shù)據(jù)
dwNewLong = 用來(lái)更新的數(shù)據(jù)
我們的工作還是比較簡(jiǎn)單的:
寫(xiě)一個(gè)窗口函數(shù)用于處理發(fā)給 Edit 控件的消息。
用參數(shù)GWL_WNDPROC調(diào)用SetWindowLong 函數(shù),如果調(diào)用成功那么返回值就是與調(diào)用功能相聯(lián)系的一個(gè)32位的整數(shù)
在我們的程序中,返回值就是原先窗口函數(shù)的地址。我們要保存這個(gè)值以便以后使用。 記?。河幸恍┪覀儾惶幚淼南?,需要把它們派遣給原來(lái)的窗口函數(shù)來(lái)處理,這就用到另外一個(gè)函數(shù) CallWindowProc, 函數(shù)原型為:
CallWindowProc PROTO lpPrevWndFunc:DWORD, hWnd:DWORD, Msg:DWORD, wParam:DWORD, lParam:DWORD
lpPrevWndFunc = 窗口原來(lái)函數(shù)的地址. 剩下的四個(gè)參數(shù)就是發(fā)給自定義函數(shù)的參數(shù),直接把它們傳給函數(shù) CallWindowProc 就行了。
代碼舉例:
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
ClassName db "SubclassWinClass",0
AppName db "Subclassing Demo",0
EditClass db "EDIT",0
Message db "You pressed Enter in the text box!",0
.data?
hInstance HINSTANCE ?
hwndEdit dd ?
OldWndProc dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR EditClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
20,300,25,hWnd,NULL,\
hInstance,NULL
mov hwndEdit,eax
invoke SetFocus,eax
;-----------------------------------------
; Subclass it!
;-----------------------------------------
invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc
mov OldWndProc,eax
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_CHAR
mov eax,wParam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
ret
.endif
.elseif uMsg==WM_KEYDOWN
mov eax,wParam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
EditWndProc endp
end start
分析:
invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc
mov OldWndProc,eax
在創(chuàng)建 Edit 控件后,通過(guò)調(diào)用 SetWindowLong 把原來(lái)的窗口函數(shù)地址替換為自定義函數(shù)的地址,從而對(duì)它實(shí)施了窗口子類化,要注意 為了調(diào)用函數(shù) CallWindowProc,我們存儲(chǔ)了原窗口函數(shù)地址,自已編寫(xiě)的EditWndProc 僅僅是個(gè)普普通通的窗口函數(shù)。當(dāng)然也可以再調(diào)用一次 SetWindowLong 函數(shù)來(lái)存儲(chǔ)這個(gè)32位的值,
invoke SetWindowLong ,hwndEdit,GWL_USERDATA,eax 。
當(dāng)然用的時(shí)候就要調(diào)用GetWindowLong 來(lái)取回這個(gè)值。
.if uMsg==WM_CHAR
mov eax,wParam
.if (al>="0" && al<="9") || (al>="A" && al<="F") || (al>="a" && al<="f") || al==VK_BACK
.if al>="a" && al<="f"
sub al,20h
.endif
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam
ret
.endif
在函數(shù) EditWndProc 中,我們自己處理了WM_CHAR消息: 如果輸入的字符是'0'--'9'、'A'-'F'或者是'a'--'f'就接受,并且把此消息轉(zhuǎn)發(fā)給原窗口函數(shù),其中若輸入的是小寫(xiě)的'a'--'f'就把它變?yōu)榇髮?xiě)。如果輸入的不是十六進(jìn)制字符,就丟掉它,并且也不轉(zhuǎn)發(fā)此消息。因此當(dāng)輸入是非十六進(jìn)制字符時(shí),這個(gè)字符就不會(huì)顯示在 Edit 控件中。
.elseif uMsg==WM_KEYDOWN
mov eax,wParam
.if al==VK_RETURN
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SetFocus,hEdit
.else
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam
ret
.end
在這里我們通過(guò)處理 回車(Enter) 鍵進(jìn)一步示范了子類化的能力。EditWndProc 通過(guò)檢查 WM_KEYDONW 消息來(lái)判斷是否是 回車鍵,若是顯示提示消息框,否則轉(zhuǎn)發(fā)此消息。 你可以用窗口子類化來(lái)控制另外的窗口,這是必須掌握的十分有用的技術(shù)之一。