在这一课中,我们将学习什么是窗口子类化,以及如何使用它才能对你有利。
如果你在windows编程已经有一段时间的话,你将发现一些案例中的窗口几乎有你程序所需要的属性但又不是完全相同。你遭遇过这样的情景吗?当你想要一个能过滤那些不必要的文本的特殊编辑控件时。最简单的方法就是你自己为这个窗口编码。但它真是一件辛苦的工作并且耗费时间。窗口子类化挽救了这一切。
简单的说,窗口子类化允许你“接管”已经被子类化的窗口。你将完全的控制它。假如你需要一个仅能接收16进制的文本框。如果你用一个简单的编辑控件,无论什么时候当你的用户输入一些不是16进制的数字等等在你的文本框中时,你什么都不能说。 如果用户在你的文本框中输入”zb+q”字串,你除了拒接整个字符串之外,你无能为力。这至少是不专业的。在本质上,你需要用户输入的时候检查每一个字符的能力,以便保证他敲进文本框中的每一个字符都是正确的 。
当用户输入一些东西在一个文本框的时候,windows发送WM_CHAR消息给编辑控件的窗口过程。这个窗口过程驻留在windows内部所以我们不能修改它。但是我们能重定向这条消息让它流向我们自己的窗口过程。这样我们的窗口过程将首先获得windows发给编辑控件的任何消息。如果我们的窗口过程愿意对这条消息起作用,它就能这样做。但是如果它不想处理这条消息,它能把它传给原来的窗口过程。这样,我们窗口过程将它自己插入在windows和edit 控件之间。看起来像下面这样:
在窗口子类化之前
Windows ==>Edit 控件的窗口处理函数。
在窗口子类化之后
Windows ==>自定义的窗口处理函数==> Edit 控件的窗口处理函数
现在我们把注意力放在如何子类化一个窗口上。注意,子类化并不是仅限制于控件,它能被用于任何窗口。
让我们想像一下windows是如何知道编辑控件的窗口过程在那儿的?是WNDCLASSEX结构的lpfnWndProc 成员。如果我们能用自己的窗口过程地址替代这个成员,windows也就把消息发送到我们的窗口过程中。我们通过调用SetWindowLong函数做到这点。
SetWindowLong PROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORD
hWnd : 窗口的句柄,将改变后的值放在WNDCLASSEX结构中。
nIndex : 改变的值。
GWL_EXSTYLE : 设置一个新的扩张窗口样式。
GWL_STYLE : 设置一个新的窗口样式
GWL_WNDPROC : 为窗口过程设置一个新的地址
GWL_HINSTANCE:设置一个新的应用程序实例句柄
GWL_ID : 设置一个新的窗口标识符
GWL_USERDATA:设置一个和窗口相关联的32位值。每一个窗口都有一个32位的值当算在应用程
序创建窗口的时候使用。
dwNewLong : 替换值
所以我们的工作是简单的:我们编写一个窗口过程,它将处理这个编辑控件的消息,然后调用把我们窗口过程的地址传递给SetWindowLong函数作为它的第三个参数,并设置第二个参数的值为GWL_WNDPROC标记。如果这个函数调用成功,它返回的是先前指定的32位整数值,在我们的例子中,是原来的窗口过程的地址。为了在我们的窗口过程中使用它我们需要储存这个值。记住,这里有一些消息我们并不想处理,我们将它传递给原始的窗口过程。我们能通过调用CallWindowProc这个函数来做到这一点。
CallWindowProc PROTO lpPrevWndFunc:DWORD, \
hWnd:DWORD,\
Msg:DWORD,\
wParam:DWORD,\
lParam:DWORD
lpPrevWndFunc :原来的窗口过程地址
剩下的四个参数是传递给我们的窗口过程的,我们直接把它们传递给CallWindowProc函数就可。
Code Sample:
.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
在编辑控件被创建后,我们通过调用SetWindowLong函数让它子类化,用我们自己窗口过程地址替换原来的窗口过程地址。注意,我们必须储存原来的窗口过程地址以便以后调用CallWindowProc函数。注意:EditWndProc是一个普通的窗口过程。
.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
在EditWndProc中,我们筛选WM_CHAR消息。如果字符在0-9或者是a-f之间,我们通过把消息传递给它原来的窗口过程来接受它。如果它们是小写字母,我们让它们都加上20h,来将它们全部转换为大写。注意,如果字符不是我们期望的,我们抛弃它。我们并不传递它给原来的窗口过程。所以当用户输入一些不在0-9或者是a-f的字符时,这些字符将不显示在编辑控件上。
.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
通过陷入回车键,我想进一步的示范窗口子类化的能力。EditWndProc检查WM_KEYDOWN消息看它是否是VK_RETURN(回车键)。如果它是,它将弹出一个对话框并显示文本“you pressed the enter key in the box!”如果它不是回车键,它将这个消息传递给原来的窗口程序。
你能用窗口子类化来控制其它的窗口。它是种有力的技术,你应该将它存入你的兵工厂。