Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1759475
  • 博文数量: 290
  • 博客积分: 10653
  • 博客等级: 上将
  • 技术积分: 3178
  • 用 户 组: 普通用户
  • 注册时间: 2007-10-24 23:08
文章存档

2013年(6)

2012年(15)

2011年(25)

2010年(86)

2009年(52)

2008年(66)

2007年(40)

分类:

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发给编辑控件的任何消息。如果我们的窗口过程愿意对这条消息起作用,它就能这样做。但是如果它不想处理这条消息,它能把它传给原来的窗口过程。这样,我们窗口过程将它自己插入在windowsedit 控件之间。看起来像下面这样:

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

 

 

阅读(1672) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~