?? lion-tut-c22.htm
字號(hào):
<HTML>
<head>
<link rel="stylesheet" href="../../asm.css">
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Iczelion's win32 asm tutorial</title>
</head>
<body bgcolor="#FFFFFF" background="../../images/back01.jpg">
<P align=center><font color="#0000FF" size="3"><b>第22課 超類化</b></font></P>
<HR SIZE=1>
<p><br>
在這一講我們將學(xué)習(xí)什么是超類化以及它有什么作用;同時(shí)你還會(huì)學(xué)到怎樣在自己的窗口中用Tab鍵在控件中切換這一技巧。</p>
<p><font color="#FF0000">理論:</font></p>
<p>在你的程序生涯中你肯定遇到過(guò)這樣的情況,你需要一系列的控件,但它們之間卻只有一點(diǎn)點(diǎn)的不同。例如,你可能需要10個(gè)只接受數(shù)字的 Edit 控件,當(dāng)然你可以通過(guò)多種方法來(lái)達(dá)到這個(gè)目的。</p>
<ol>
<li>創(chuàng)建自己的類并用它實(shí)例化為那些控件</li>
<li>創(chuàng)建那些 Edit 控件并把它們?nèi)孔宇惢?lt;/li>
<li>超類化Edit 控件</li>
</ol>
<p>第一種方法太乏味了,因?yàn)槟惚仨氉约簩?shí)現(xiàn)Edit 控件的每個(gè)功能,但這項(xiàng)工作不是輕松就能完成的。第二種方法好于第一種,但仍然要做許多工作,子類化幾個(gè)Edit
控件還可以接受,但若要子類化十幾二十個(gè),這項(xiàng)工作簡(jiǎn)直就是一場(chǎng)惡夢(mèng)。在這種情況下就應(yīng)該使用超類化這個(gè)技巧,它是用于控制某一個(gè)特定窗口類的特殊方法。通過(guò)這種控制就可以修改窗口類的特性使之符合你的要求,然后再創(chuàng)建那一堆控件就可以了。</p>
<p>超類化有如下幾個(gè)步驟:</p>
<ol>
<li>通過(guò)調(diào)用 GetClassInfoEx 來(lái)獲得想要進(jìn)行超類化操作的窗口類的信息。函數(shù)GetClassInfoEx 需要一個(gè)指向 WNDCLASSEX
結(jié)構(gòu)的指針,用于當(dāng)成功返回時(shí)填入窗口類的信息。</li>
<li>按需要修改 WNDCLASSEX 結(jié)構(gòu)的成員,其中有兩個(gè)成員必須修改:<br>
hInstance 存放程序的實(shí)例句柄<br>
lpszClassName 指向一個(gè)新類名的指針<br>
不必修改成員 lpfnWndProc,但大多數(shù)情況下還是需要的。但要記住如果要使用函數(shù) CallWindowProc 調(diào)用老窗口的過(guò)程,那就必須保存成員
lpfnWndProc 的原值。</li>
<li>注冊(cè)修改完的 WNDCLASSEX 結(jié)構(gòu),得到一個(gè)具有舊窗口類某些特性的新窗口類。</li>
<li>用新窗口類創(chuàng)建窗口</li>
</ol>
<p>如果要?jiǎng)?chuàng)建具有相同特性的多個(gè)控件,超類化就比子類化要好。</p>
<p><font color="#FF0000">舉例:</font></p>
<p>.386 <br>
.model flat,stdcall <br>
option casemap:none <br>
include \masm32\include\windows.inc <br>
include \masm32\include\user32.inc <br>
include \masm32\include\kernel32.inc <br>
includelib \masm32\lib\user32.lib <br>
includelib \masm32\lib\kernel32.lib <br>
WM_SUPERCLASS equ WM_USER+5 <br>
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD <br>
EditWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD </p>
<p>.data <br>
ClassName db "SuperclassWinClass",0 <br>
AppName db "Superclassing Demo",0 <br>
EditClass db "EDIT",0 <br>
OurClass db "SUPEREDITCLASS",0 <br>
Message db "You pressed the Enter key in the text box!",0 </p>
<p>.data? <br>
hInstance dd ? <br>
hwndEdit dd 6 dup(?) ;存放6個(gè)窗口句柄的數(shù)組<br>
OldWndProc dd ? ;原來(lái)的窗口過(guò)程</p>
<p>.code <br>
start: <br>
invoke GetModuleHandle, NULL <br>
mov hInstance,eax <br>
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT <br>
invoke ExitProcess,eax </p>
<p>WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
<br>
LOCAL wc:WNDCLASSEX <br>
LOCAL msg:MSG <br>
LOCAL hwnd:HWND </p>
<p> mov wc.cbSize,SIZEOF WNDCLASSEX <br>
mov wc.style, CS_HREDRAW or CS_VREDRAW <br>
mov wc.lpfnWndProc, OFFSET WndProc <br>
mov wc.cbClsExtra,NULL <br>
mov wc.cbWndExtra,NULL <br>
push hInst <br>
pop wc.hInstance <br>
mov wc.hbrBackground,COLOR_APPWORKSPACE <br>
mov wc.lpszMenuName,NULL <br>
mov wc.lpszClassName,OFFSET ClassName <br>
invoke LoadIcon,NULL,IDI_APPLICATION <br>
mov wc.hIcon,eax <br>
mov wc.hIconSm,eax <br>
invoke LoadCursor,NULL,IDC_ARROW <br>
mov wc.hCursor,eax <br>
invoke RegisterClassEx, addr wc <br>
invoke CreateWindowEx,WS_EX_CLIENTEDGE+WS_EX_CONTROLPARENT,ADDR ClassName,ADDR
AppName,\ <br>
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT,\
<br>
CW_USEDEFAULT,350,220,NULL,NULL,\ <br>
hInst,NULL <br>
mov hwnd,eax </p>
<p> .while TRUE <br>
invoke GetMessage, ADDR msg,NULL,0,0 <br>
.BREAK .IF (!eax) <br>
invoke TranslateMessage, ADDR msg <br>
invoke DispatchMessage, ADDR msg <br>
.endw <br>
mov eax,msg.wParam <br>
ret <br>
WinMain endp </p>
<p>WndProc proc uses ebx edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
<br>
LOCAL wc:WNDCLASSEX <br>
.if uMsg==WM_CREATE <br>
mov wc.cbSize,sizeof WNDCLASSEX <br>
invoke GetClassInfoEx,NULL,addr EditClass,addr wc <br>
push wc.lpfnWndProc <br>
pop OldWndProc <br>
mov wc.lpfnWndProc, OFFSET EditWndProc <br>
push hInstance <br>
pop wc.hInstance <br>
mov wc.lpszClassName,OFFSET OurClass <br>
invoke RegisterClassEx, addr wc <br>
xor ebx,ebx <br>
mov edi,20 <br>
.while ebx<6 <br>
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\ <br>
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\ <br>
edi,300,25,hWnd,ebx,\ <br>
hInstance,NULL <br>
mov dword ptr [hwndEdit+4*ebx],eax <br>
add edi,25 <br>
inc ebx <br>
.endw <br>
invoke SetFocus,hwndEdit <br>
.elseif uMsg==WM_DESTROY <br>
invoke PostQuitMessage,NULL <br>
.else <br>
invoke DefWindowProc,hWnd,uMsg,wParam,lParam <br>
ret <br>
.endif <br>
xor eax,eax <br>
ret <br>
WndProc endp </p>
<p>EditWndProc PROC hEdit:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD <br>
.if uMsg==WM_CHAR <br>
mov eax,wParam <br>
.if (al>="0" && al<="9") || (al>="A"
&& al<="F") || (al>="a" && al<="f")
|| al==VK_BACK <br>
;處理字符0~9,A~F,a~f,這幾個(gè)十六進(jìn)制數(shù)<br>
.if al>="a" && al<="f" <br>
sub al,20h <br>
如果是字符a~f,則把它們變?yōu)榇髮?lt;br>
.endif <br>
invoke CallWindowProc,OldWndProc,hEdit,uMsg,eax,lParam <br>
ret <br>
.endif <br>
.elseif uMsg==WM_KEYDOWN <br>
mov eax,wParam <br>
.if al==VK_RETURN <br>
invoke MessageBox,hEdit,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION <br>
invoke SetFocus,hEdit <br>
.elseif al==VK_TAB <br>
invoke GetKeyState,VK_SHIFT <br>
test eax,80000000 <br>
.if ZERO? <br>
invoke GetWindow,hEdit,GW_HWNDNEXT <br>
.if eax==NULL <br>
invoke GetWindow,hEdit,GW_HWNDFIRST <br>
.endif <br>
.else <br>
invoke GetWindow,hEdit,GW_HWNDPREV <br>
.if eax==NULL <br>
invoke GetWindow,hEdit,GW_HWNDLAST <br>
.endif <br>
.endif <br>
invoke SetFocus,eax <br>
xor eax,eax <br>
ret <br>
.else <br>
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam <br>
ret <br>
.endif <br>
.else <br>
invoke CallWindowProc,OldWndProc,hEdit,uMsg,wParam,lParam <br>
ret <br>
.endif <br>
xor eax,eax <br>
ret <br>
EditWndProc endp <br>
end start <br>
</p>
<p><font color="#FF0000">分析</font> </p>
<p>這個(gè)程序創(chuàng)建了一個(gè)在其客戶區(qū)有六個(gè)被修改的 Edit 控件的簡(jiǎn)單窗口,這些 Edit控件只接受十六進(jìn)制的數(shù)字。實(shí)際上,這個(gè)例子是通過(guò)修改窗口了類化的例子得來(lái)的。這個(gè)程序開始和其它程序一樣,有趣的部分出現(xiàn)在主窗口被創(chuàng)建的時(shí)候:</p>
<p>.if uMsg==WM_CREATE <br>
mov wc.cbSize,sizeof WNDCLASSEX <br>
invoke GetClassInfoEx,NULL,addr EditClass,addr wc </p>
<p> 必須用想進(jìn)行超類化操作的類數(shù)據(jù)填充 WNDCLASSEX 結(jié)構(gòu),在我們的例子中就是類 Edit ,記住在調(diào)用函數(shù) GetClassInfoEx 之前必須填寫成員
cbSize,否則函數(shù)調(diào)用 GetClassInfoEx不會(huì)在 WNDCLASSEX 結(jié)構(gòu)中填入正確的返回值。成功返回后,變量 wc中保存的就是想要?jiǎng)?chuàng)建一個(gè)新類所需要的所有信息。</p>
<p> push wc.lpfnWndProc <br>
pop OldWndProc <br>
mov wc.lpfnWndProc, OFFSET EditWndProc <br>
push hInstance <br>
pop wc.hInstance <br>
mov wc.lpszClassName,OFFSET OurClass </p>
<p> 現(xiàn)在必須修改變量 wc 的一些屬性:第一個(gè)要修改的就是指向窗口過(guò)程的指針。因?yàn)樵谛麓翱谶^(guò)程中函數(shù) CallWindowProx 要用到老窗口過(guò)程,因此得把它保存到一個(gè)變量中以便使用。這個(gè)技巧和在子類化中用到的一樣,只不過(guò)不是調(diào)用
SetWindowLong 而是直接修改 WNDCLASSEX 結(jié)構(gòu)罷了。接下來(lái)必須得為這個(gè)新類取個(gè)名字。</p>
<p> invoke RegisterClassEx, addr wc </p>
<p>當(dāng)所有這些都完成時(shí),注冊(cè)這個(gè)新類就會(huì)得到一個(gè)具有舊類某些特征的新類了。</p>
<p> xor ebx,ebx <br>
mov edi,20 <br>
.while ebx<6 <br>
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR OurClass,NULL,\ <br>
WS_CHILD+WS_VISIBLE+WS_BORDER,20,\ <br>
edi,300,25,hWnd,ebx,\ <br>
hInstance,NULL <br>
mov dword ptr [hwndEdit+4*ebx],eax <br>
add edi,25 <br>
inc ebx <br>
.endw <br>
invoke SetFocus,hwndEdit </p>
<p> 注冊(cè)完新類就可以創(chuàng)建基于它的窗口了:<br>
在上面的程序片斷中,用寄存器 ebx 來(lái)保存已創(chuàng)建的窗口數(shù)目,用寄存器 edi 來(lái)保存窗口左上角的 y 坐標(biāo)。創(chuàng)建一個(gè)新窗口時(shí),把它的句柄保存在一個(gè)雙字的數(shù)組中,當(dāng)創(chuàng)建完所有的窗口后,設(shè)定輸入焦點(diǎn)為所創(chuàng)建的第一個(gè)窗口。</p>
<p>這時(shí)已經(jīng)有6個(gè)只能接受十六進(jìn)制數(shù)字的 edit 窗口控件了,替換的窗口過(guò)程處理了字符過(guò)濾,這實(shí)際上和在子類化中的例子是一樣的。但不必做子類化那些窗口的額外工作了。
</p>
<p>在此程序中,通過(guò)使用 Tabs 鍵來(lái)在各個(gè) Edit 控件中切換來(lái)使得這個(gè)程序更加有趣。一般來(lái)說(shuō),如果使用對(duì)話框,對(duì)話框管理器會(huì)處理好所有這些問(wèn)題,即:<br>
按下 Tabs 輸入焦點(diǎn)切換到下一個(gè)控件窗口中,按下 Shift-Tabs 輸入焦點(diǎn)切換到上一個(gè)控件窗口中;但一個(gè)簡(jiǎn)單的窗口不具有這個(gè)功能,必須子類化它們以處理
Tabs 鍵。在這個(gè)例子中,不必一個(gè)一個(gè)去子類化已經(jīng)進(jìn)行過(guò)超類化操作的這些控件,可以使用一種集中控制切換策略。</p>
<p> .elseif al==VK_TAB <br>
invoke GetKeyState,VK_SHIFT <br>
test eax,80000000 <br>
.if ZERO? <br>
invoke GetWindow,hEdit,GW_HWNDNEXT <br>
.if eax==NULL <br>
invoke GetWindow,hEdit,GW_HWNDFIRST <br>
.endif <br>
.else <br>
invoke GetWindow,hEdit,GW_HWNDPREV <br>
.if eax==NULL <br>
invoke GetWindow,hEdit,GW_HWNDLAST <br>
.endif <br>
.endif <br>
invoke SetFocus,eax <br>
xor eax,eax <br>
ret </p>
<p> 上面是摘自于 EditWndClass 過(guò)程的程序片斷,它檢查用戶是否按下了 Tabs 鍵,若是就調(diào)用函數(shù) GetKeyState 來(lái)檢查 SHIFT
鍵是否也被同時(shí)按下了。函數(shù) GetKeyState 在寄存器 eax 中設(shè)立一個(gè)返回值,用于判斷某個(gè)特定的鍵是否被按下了,若按下了,則把 eax 的的最高位置1,否則把最高位清0。所以只要用
80000000h 來(lái)測(cè)試返回值就行了,若最高位是1則說(shuō)明用戶按下了 SHIFT-Tabs,這需要單獨(dú)處理;否則說(shuō)明只按下 Tabs 鍵,調(diào)用函數(shù) GetWindow
來(lái)獲得 hEdit 所指向窗口的下一個(gè)窗口句柄,若該函數(shù)返回 NULL ,說(shuō)明這是當(dāng)前窗口是窗口鏈中最后一個(gè)窗口了,應(yīng)該通過(guò)以參數(shù) GW_HWNDFIRST
調(diào)用函數(shù) GetWindow 來(lái)卷回到窗口鏈中的第一個(gè)窗口控件。SHIFT-Tabs 的處理過(guò)程和這正好相反。<br>
</p>
<HR SIZE=1>
<DIV align=center> <font face="宋體">
<SCRIPT language=JavaScript1.1 src="../lion-tut-c13.files/textclick"></SCRIPT>
<BR>
</font></DIV>
<font face="宋體"><!-- 10:1 文本廣告交換 --> </font>
<DIV align=center> <font face="宋體">
<SCRIPT language=JavaScript1.1 src="../lion-tut-c13.files/c21.htm"></SCRIPT>
<!-- 10:1 文本廣告交換 --></font></DIV>
<HR SIZE=1>
<DIV align=center><font face="宋體">翻譯:ZhangJun,校對(duì):LuoYunBin's Win32 ASM Page, <A
href="http://asm.yeah.net/">http://asm.yeah.net</A></font></DIV>
</BODY></HTML>
?? 快捷鍵說(shuō)明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -