在这一课中,我们将学习什么是超类化以及它能用来做什么。你也将学习到如何在你自己的窗口中为控件提供Tab键导航。(切换窗口)
在你的程序员生涯中,你必定遭遇过这种情形,这里有你需要的一系列控件,但它们的行为又稍微有些不同。例如,你可能需要10仅接收数字的编辑控件。这里有几种方法能完成目标:
1.创建你自己的类并将控件实例化。
2.创建这些编辑控件然后将它们全部子类化。
3.超类化这个编辑控件。
第一种方法是非常乏味的。你必须自己实现编辑控件的每一个功能。艰难的任务不是能被轻松完成的。
第二种方法比第一种方法好很多但是仍要做很多工作。如果你子类化的控件仅有几个,那么这种方法仍可行。但是当你子类化一打(十二个)左右的控件时它将是一个恶梦。在这种场合,超类化才是你应该使用的技术。
超类化用于操纵特别窗口类控件的方法。用操纵控件,我的意思是你能修改这个窗口类的性质来让它于你的目的想匹配,然后你能用它创建一捆的控件。
超类化的步骤 概要如下:
调用GetClassInfoEx函数来获得你想超类化的那个窗口的信息。GetClassInfoEx函数需要一个指向WNDCLADDEX结构体的指针,如果调用成功,这个结构体将被成功返回的信息填满。
修改你想改变的WNDCLASSEX结构的成员。无论如何,这里有两个成员是你必须修改。
hInstance 你必须把你们的应用程序实例句柄放到这个成员中。
LpszClassName 你必须为它提供一个包含新类名的字符指针。
你并不需要修改lpfnWndProc成员,但是在大多数情况下,你还是修改一下。仅需要记住的是如果你要调用的函数是CallWindowProc函数,你要保存lpfnWndProc的初始值。
注册已修改过的WNDCLASSEX结构。你将有一个新的窗口类, 这个新窗口类将有几个特征和老窗口类的相同。
从这个窗口类中创建窗口。
如果你想创建具有相同特征值的多个控件,超类化比子类化好得多。
Example:
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WM_SUPERCLASS equ WM_USER+5
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
ClassName db "SuperclassWinClass",0
AppName db "Superclassing Demo",0
EditClass db "EDIT",0
OurClass db "SUPEREDITCLASS",0
Message db "You pressed the Enter key in the text box!",0
.data?
hInstance dd ?
hwndEdit dd 6 dup(?)
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+WS_EX_CONTROLPARENT,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
CW_USEDEFAULT,350,220,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 uses ebx edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL wc:WNDCLASSEX
.if uMsg==WM_CREATE
mov wc.cbSize,sizeof WNDCLASSEX
invoke GetClassInfoEx,NULL,addr EditClass,addr wc
push wc.lpfnWndProc
pop OldWndProc
mov wc.lpfnWndProc, OFFSET EditWndProc
push hInstance
pop wc.hInstance
mov wc.lpszClassName,OFFSET OurClass
invoke RegisterClassEx, addr wc
xor ebx,ebx
mov edi,20
.while ebx<6
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
edi,300,25,hWnd,ebx,\
hInstance,NULL
mov dword ptr [hwndEdit+4*ebx],eax
add edi,25
inc ebx
.endw
invoke SetFocus,hwndEdit
.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
.elseif al==VK_TAB
invoke GetKeyState,VK_SHIFT
test eax,80000000
.if ZERO?
invoke GetWindow,hEdit,GW_HWNDNEXT
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDFIRST
.endif
.else
invoke GetWindow,hEdit,GW_HWNDPREV
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDLAST
.endif
.endif
invoke SetFocus,eax
xor eax,eax
ret
.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
分析:
程序将创建一个简单的窗口,在窗口的客户区有6个修改过的编辑控件。这些编辑控件仅接收16进制数。实际上,我是修改了子类化的例子做到超类化的。程序正常开始,有趣的部分是在主窗口被创建的时候:
.if uMsg==WM_CREATE
mov wc.cbSize,sizeof WNDCLASSEX
invoke GetClassInfoEx,NULL,addr EditClass,addr wc
首先,我们必须用我们想超类化的那个类的数据成员来填充WNDCLASSEX结构。在我们的例子中,它就是EDIT类。记住,在你调用GetClassInfoEx函数之前,你必须设置WNDCLASSEX结构的cbSize成员的值,否则,WNDCLASSEX结构将不能被完全填充。在GetClassInfoEx返回之后,变量 wc中保存的就是想要创建一个新类所需要的所有信息。
push wc.lpfnWndProc
pop OldWndProc
mov wc.lpfnWndProc, OFFSET EditWndProc
push hInstance
pop wc.hInstance
mov wc.lpszClassName,OFFSET OurClass
现在我们修改wc的一些成员。第一个成员是指向窗口过程的指针。因为在我们自己的窗口过程中要用到原来的窗口过程,我们必须把它保存在一个变量中,那么我们在调用CallWindowProc时就能够使用它。除了没有直接调用SetWindowLong函数来修改WNDCLADSDEX结构外,这个技术和子类化是一样的。接下来的两个成员必须被改变,否则你就不能注册你的新窗口类,它们是hInstance和lpsClassName。你必须用你拥有的程序实例替代原来的实例值并且你必须为这个新类选择一个新类名。
invoke RegisterClassEx, addr wc
当所有的这些都准备好时,注册这个新的类。这个新窗口类将有几个特征和老窗口类的相同。
xor ebx,ebx
mov edi,20
.while ebx<6
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\
edi,300,25,hWnd,ebx,\
hInstance,NULL
mov dword ptr [hwndEdit+4*ebx],eax
add edi,25
inc ebx
.endw
invoke SetFocus,hwndEdit
现在,我们注册了这个窗口类,我们就能基于它创建窗口了。在上面的代码片段中,我用ebx作为创建了多少个窗口的计数器。Edi保存窗口左上角的y坐标值。当一个窗口被创建时,它的句柄就被储存在一个DWORD的数组中。当所有的窗口都被创建时,设置输入焦点给第一个窗口。到这里,你得到了6个仅接收16进制数的编辑控件。取代的窗口过程处理了字符过滤。实际上,它和在子类化例子中的窗口过程一致。如你所见,你不必要去做窗口子类化的那些额外的工作了。
我插入用TAB键处理控件切换的代码片段来让这个例子更有趣。通常,你把一个控件放置在一个对话框上,对话框管理器为你处理定位键,所以你能用TAB键定位到下一个控件或者是用shift-tab键返回到上一个控件。唉,如果你把控件放置在一个窗口上时,这样的特性是无法使用的。你必须子类化它们来让你自己处理tab 键。在我们的例子中,我们不需要一个一个的子类化这些控件,因为我们已经超类化了它们。所以我们为它们提供了一个“中央控制导航管理器”
.elseif al==VK_TAB
invoke GetKeyState,VK_SHIFT
test eax,80000000
.if ZERO?
invoke GetWindow,hEdit,GW_HWNDNEXT
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDFIRST
.endif
.else
invoke GetWindow,hEdit,GW_HWNDPREV
.if eax==NULL
invoke GetWindow,hEdit,GW_HWNDLAST
.endif
.endif
invoke SetFocus,eax
xor eax,eax
ret
上面的是摘自于 EditWndClass 过程的程序片断,它检查用户是否按下了TAB键,如果是,它调用GetKeyState来检查SHIFT键是否也被按下。GetKeyState在eax中返回的值用于确定指定的键是否被按下。如果这个键被按下,eax的高字节被设置,如果没有按下,高字节被清理。所以我们用80000000来测试返回值。若最高位是1则说明用户按下了 SHIFT-Tabs,这需要单独处理
如果用户仅按下TAB键,我们调用GetWindow来获取下一个控件的句柄。我们使用GW_HWNDNEXT标志来告诉GetWindow函数获取的窗口句柄是和当前hEdit句柄链上的下一个句柄。如果这个函数返回值为NULL,说明在当前窗口句柄链中没有其它的句柄了,所以当前的hEdit是这个链上的最后一个句柄。我们在调用GetWindow函数时设置GW_HWNDFIRST来绕回到第一个控件上。类似于tab键,shift键处理过程正好相反。