全部博文(290)
分类:
2008-01-10 00:58:30
Tutorial 20: Window Subclassing
第二十课:窗口子类化
In this tutorial, we will learn about window subclassing, what it is and how to use it to your advantage.
在这一课中,我们将学习什么是窗口子类化,以及如何使用它才能对你有利。
Download the example here.
Theory:
原理:
If you program in Windows for some time, you will find some cases where a window has nearly the attributes you need in your program but not quite. Have you encountered a situation where you want some special kind of edit control that can filter out some unwanted text? The straightforward thing to do is to code your own window. But it's really hard work and time-consuming. Window subclassing to the rescue.
如果你在windows编程已经有一段时间的话,你将发现一些案例中的窗口几乎有你程序所需要的属性但又不是完全相同。你遭遇过这样的情景吗?当你想要一个能过滤那些不必要的文本的特殊编辑控件时。最简单的方法就是你自己为这个窗口编码。但它真是一件辛苦的工作并且耗费时间。窗口子类化挽救了这一切。
In a nutshell, window subclassing allows you to "take over" the subclassed window. You will have absolute control over it. Let's take an example to make this clearer. Suppose you need a text box that accepts only hex numbers. If you use a simple edit control, you have no say whatsoever when your user types something other than hex numbers into your text box, ie. if the user types "zb+q*" into your text box, you can't do anything with it except rejecting the whole text string. This is unprofessional at least. In essence, you need the ability to examine each character the user typed into the text box right at the moment he typed it.
简单的说,窗口子类化允许你“接管”已经被子类化的窗口。你将完全的控制它。让我们举例来澄清它。假如你需要你个仅能接收16进制的文本框。如果你用一个简单的编辑控件,无论什么时候当你的用户输入一些不是16进制的数字等等在你的文本框中时,你什么都不能说。 如果用户在你的文本框中输入”zb+q”字串,你除了拒接整个字符串之外,你无能为力。这至少是不专业的。在本质上,你需要用户输入的时候检查每一个字符的能力,以便保证他敲进文本框中的每一个字符都是正确的 。
We will examine how to do that now. When the user types something into a text box, Windows sends WM_CHAR message to the edit control's window procedure. This window procedure resides inside Windows itself so we can't modify it. But we can redirect the message flow to our own window procedure. So that our window procedure will get first shot at any message Windows sends to the edit control. If our window procedure chooses to act on the message, it can do so. But if it doesn't want to handle the message, it can pass it to the original window procedure. This way, our window procedure inserts itself between Windows and the edit control. Look at the flow below:
现在,我们将要分析如何做这些工作。当用户输入一些东西在一个文本框的时候,windows发送WM_CHAR消息给编辑控件的窗口过程。这个窗口过程驻留在windows内部所以我们不能修改它。但是我们能重定向这条消息让它流向我们自己的窗口过程。这样我们的窗口过程将首先获得windows发给编辑控件的任何消息。如果我们的窗口过程愿意对这条消息起作用,它就能这样做。但是如果它不想处理这条消息,它能把它传给原来的窗口过程。这样,我们窗口过程将它自己插入在windows和edit 控件之间。看起来像下面这样:
Before Subclassing
在窗口子类化之前
Windows ==> edit control's window procedure
Windows ==>Edit 控件的窗口处理函数。
After Subclassing
在子类化之后
Windows ==> our window procedure -----> edit control's window procedure
Windows ==>自定义的窗口处理函数==> Edit 控件的窗口处理函数
Now we put our attention on how to subclass a window. Note that subclassing is not limited to controls, it can be used with any window.
现在我们把注意力放在如何子类化一个窗口上。注意,子类化并不是仅限制于控件,它能被用于任何窗口。
Let's think about how Windows knows where the edit control's window procedure resides. A guess?......lpfnWndProc member of WNDCLASSEX structure. If we can replace this member with the address of our own window procedure, Windows will send messages to our window proc instead.
We can do that by calling SetWindowLong.
让我们相像一下windows是如何知道编辑控件的窗口过程在那儿的?猜一下?。。是WNDCLASSEX结构的lpfnWndProc 成员。如果我们能用自己的窗口过程地址替代这个成员,windows也就把消息发送到我们的窗口过程中。
我们通过调用SetWindowLong函数做到这点。
SetWindowLong PROTO hWnd:DWORD, nIndex:DWORD, dwNewLong:DWORD
hWnd = handle of the window to change the value in the WNDCLASSEX structure
窗口的句柄,将改变后的值放在WNDCLASSEX结构中。
nIndex == value to change.
改变的值。
GWL_EXSTYLE Sets a new extended window style.
设置一个新的扩张窗口样式。
GWL_STYLE Sets a new window style.
设置一个新的窗口样式
GWL_WNDPROC Sets a new address for the window procedure.
为窗口过程设置一个新的地址
GWL_HINSTANCE Sets a new application instance handle.
设置一个新的应用程序实例句柄
GWL_ID Sets a new identifier of the window.
设置一个新的窗口标识符
GWL_USERDATA Sets the 32-bit value associated with the window. Each window has a corresponding 32-bit value intended for use by the application that created the window.
设置一个和窗口相关联的32位值。每一个窗口都有一个32位的值当算在应用程序创建窗口的时候使用。
dwNewLong = the replacement value. 替换值
So our job is easy: We code a window proc that will handle the messages for the edit control and then call SetWindowLong with GWL_WNDPROC flag, passing along the address of our window proc as the third parameter. If the function succeeds, the return value is the previous value of the specified 32-bit integer, in our case, the address of the original window procedure. We need to store this value for use within our window procedure.
Remember that there will be some messages we don't want to handle, we will pass them to the original window procedure. We can do that by calling CallWindowProc function.
所以我们的工作是简单的:我们编写一个窗口过程,它将处理这个编辑控件的消息,然后调用把我们窗口过程的地址传递给SetWindowLong函数作为它的第三个参数,并设置第二个参数的值为GWL_WNDPROC标记。如果这个函数调用成功,它返回的是先前指定的32位整数值,在我们的例子中,是原来的窗口过程的地址。为了在我们的窗口过程中使用它我们需要储存这个值。记住,这里有一些消息我们并不想处理,我们将它传递给原始的窗口过程。我们能通过调用CallWindowProc这个函数来做到这一点。
CallWindowProc PROTO lpPrevWndFunc:DWORD, \
hWnd:DWORD,\
Msg:DWORD,\
wParam:DWORD,\
lParam:DWORD
lpPrevWndFunc = the address of the original window procedure.
原来的窗口过程地址
The remaining four parameters are the ones passed to our window procedure. We just pass them along to CallWindowProc.
剩下的四个参数是传递给我们的窗口过程的,我们直接把它们传递给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
Analysis:
分析:
invoke SetWindowLong,hwndEdit,GWL_WNDPROC,addr EditWndProc
mov OldWndProc,eax
After the edit control is created, we subclass it by calling SetWindowLong, replacing the address of the original window procedure with our own window procedure. Note that we store the address of the original window procedure for use with CallWindowProc. Note the EditWndProc is an ordinary window procedure.
在编辑控件被创建后,我们通过调用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
Within EditWndProc, we filter WM_CHAR messages. If the character is between 0-9 or a-f, we accept it by passing along the message to the original window procedure. If it is a lower case character, we convert it to upper case by adding it with 20h. Note that, if the character is not the one we expect, we discard it. We don't pass it to the original window proc. So when the user types something other than 0-9 or a-f, the character just doesn't appear in the edit control.
在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
I want to demonstrate the power of subclassing further by trapping Enter key. EditWndProc checks WM_KEYDOWN message if it's VK_RETURN (the Enter key). If it is, it displays a message box saying "You pressed the Enter key in the text box!". If it's not an Enter key, it passes the message to the original window procedure.
通过陷入回车键,我想进一步的示范窗口子类化的能力。EditWndProc检查WM_KEYDOWN消息看它是否是VK_RETURN(回车键)。如果它是,它将弹出一个对话框并显示文本“you pressed the enter key in the box!”如果它不是回车键,它将这个消息传递给原来的窗口程序。
You can use window subclassing to take control over other windows. It's one of the powerful techniques you should have in your arsenal.
你能用窗口子类化来控制其它的窗口。它是种有力的技术,你应该将它存入你的兵工厂。
This article come from Iczelion's asm page
风向改变翻译于2008-1-10