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

2013年(6)

2012年(15)

2011年(25)

2010年(86)

2009年(52)

2008年(66)

2007年(40)

分类:

2008-01-12 01:19:52

Tutorial 22: Superclassing

第二十二课:超类化


In this tutorial, we will learn about superclassing, what it is and what it is for. You will also learn how to provide Tab key navigation to the controls in your own window.

在这一课中,我们将学习什么是超类化以及它能用来做什么。你也将学习到如何在你自己的窗口中为控件提供Tab键导航。(切换窗口)
Download the example here

Theory:

In your programming career, you will surely encounter a situation where you need several controls with *slightly* different behavior. For example, you may need 10 edit controls which accept only number. There are several ways to achieve that goal:

在你的程序员生涯中,你必定遭遇过这种情形,这里有你需要的一系列控件,但它们的行为又稍微有些不同。例如,你可能需要10仅接收数字的编辑控件。这里有几种方法能完成目标:

  • Create your own class and instantiate the controls

创建你自己的类并将控件实例化。

  • Create those edit control and then subclass all of them

创建这些编辑控件然后将它们全部子类化。

  • Superclass the edit control

超类化这个编辑控件。

The first method is too tedious. You have to implement every functionality of the edit control yourself. Hardly a task to be taken lightly. The second method is better than the first one but still too much work. It is ok if you subclass only a few controls but it's going to be a nightmare to subclass a dozen or so controls. Superclassing is the technique you should use for this occasion.

第一种方法是非常乏味的。你必须自己实现编辑控件的每一个功能。艰难的任务不是能被轻松完成的。第二种方法比第一种方法好很多但是仍要做很多工作。如果你子类化的控件仅有几个,那么这种方法仍可行。但是当你子类化一打(十二个)左右的控件时它将是一个恶梦。在这种场合,超类化才是你应该使用的技术。

 


Subclassing is the method you use to *take control* of a particular window class. By *taking control*, I mean you can modify the properties of the window class to suit your purpose then then create the bunch of controls.
The steps in superclassing is outlined below:

超类化用于操纵特别窗口类控件的方法。用操纵控件,我的意思是你能修改这个窗口类的性质来让它于你的目的想匹配,然后你能用它创建一捆的控件。

超类化的步骤 概要如下:

  • call GetClassInfoEx to obtain the information about the window class you want to superclass. GetClassInfoEx requires a pointer to a WNDCLASSEX structure which will be filled with the information if the call returns successfully.

调用GetClassInfoEx函数来获得你想超类化的那个窗口的信息。GetClassInfoEx函数需要一个指向WNDCLADDEX结构体的指针,如果调用成功,这个结构体将被成功返回的信息填满。

  • Modify the WNDCLASSEX members that you want. However, there are two members which you MUST modify:

修改你想改变的WNDCLASSEX结构的成员。无论如何,这里有两个成员是你必须修改。

    • hInstance  You must put the instance handle of your program into this member.

hInstance 你必须把你们的应用程序实例句柄放到这个成员中。

    • lpszClassName  You must provide it with a pointer to a new class name.

LpszClassName  你必须为它提供一个包含新类名的字符指针。


You need not modify lpfnWndProc member but most of the time, you need to do it. Just remember to save the original lpfnWndProc member if you want to call it with CallWindowProc.

你并不需要修改lpfnWndProc成员,但是在大多数情况下,你还是修改一下。仅需要记住的是如果你要调用的函数是CallWindowProc函数,你要保存lpfnWndProc的初始值。

  • Register the modifed WNDCLASSEX structure. You'll have a new window class which has several characteristics of the old window class.

注册已修改过的WNDCLASSEX结构。你将有一个新的窗口类, 这个新窗口类将有几个特征和老窗口类的相同。

  • Create windows from the new class

从这个窗口类中创建窗口。

Superclassing is better than subclassing if you want to create many controls with the same characteristics.

如果你想创建具有相同特征值的多个控件,超类化比子类化好得多。

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
 

Analysis:

分析:

The program will create a simple window with 6 "modified" edit controls in its client area. The edit controls will accept only hex digits. Actually, I modified the subclassing example to do superclassing. The program starts normally and the interesting part is when the main window is created:

程序将创建一个简单的窗口,在窗口的客户区有6个修改过的编辑控件。这些编辑控件仅接收16进制数。实际上,我是修改了子类化的例子做到超类化的。程序正常开始,有趣的部分是在主窗口被创建的时候:

    .if uMsg==WM_CREATE
         mov wc.cbSize,sizeof WNDCLASSEX
        invoke GetClassInfoEx,NULL,addr EditClass,addr wc

We must first fill the WNDCLASSEX structure with the data from the class which we want to superclass, in this case, it's EDIT class. Remember that you must set the cbSize member of the WNDCLASSEX structure before you call GetClassInfoEx else the WNDCLASSEX structure will not be filled properly. After GetClassInfoEx returns, wc is filled with all information we need to create a new window class.

首先,我们必须用我们想超类化的那个类的数据成员来填充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

Now we must modify some members of wc. The first one is the pointer to the window procedure. Since we need to chain our own window procedure with the original one, we have to save it into a variable so we can call it with CallWindowProc. This technique is identical to subclassing except that you modify the WNDCLASSEX structure directly without having to call SetWindowLong. The next two members must be changed else you will not be able to register your new window class, hInstance and lpsClassName. You must replace original hInstance value with hInstance of your own program. And you must choose a new name for the new class.

现在我们修改wc的一些成员。第一个成员是指向窗口过程的指针。因为在我们自己的窗口过程中要用到原来的窗口过程,我们必须把它保存在一个变量中,那么我们在调用CallWindowProc时就能够使用它。除了没有直接调用SetWindowLong函数来修改WNDCLADSDEX结构外,这个技术和子类化是一样的。接下来的两个成员必须被改变,否则你就不能注册你的新窗口类,它们是hInstancelpsClassName。你必须用你拥有的程序实例替代原来的实例值并且你必须为这个新类选择一个新类名。

        invoke RegisterClassEx, addr wc

When all is ready, register the new class. You will get a new class with some characteristics of the old class.

当所有的这些都准备好时,注册这个新的类。这个新窗口类将有几个特征和老窗口类的相同。

        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

Now that we registered the class, we can create windows based on it. In the above snippet, I use ebx as the counter of the number of windows created. edi is used as the y coordinate of the left upper corner of the window. When a window is created, its handle is stored in the array of dwords. When all windows are created, set input focus to the first window.
At this point, you got 6 edit controls which accept only hex digits. The substituted window proc handles the filter. Actually, it's identical to the window proc in subclassing example. As you can see, you don't have to do extra work of subclassing them.

现在,我们注册了这个窗口类,我们就能基于它创建窗口了。在上面的代码片段中,我用ebx作为创建了多少个窗口的计数器。Edi保存窗口左上角的y坐标值。当一个窗口被创建时,它的句柄就被储存在一个DWORD的数组中。当所有的窗口都被创建时,设置输入焦点给第一个窗口。到这里,你得到了6个仅接收16进制数的编辑控件。取代的窗口过程处理了字符过滤。实际上,它和在子类化例子中的窗口过程一致。如你所见,你不必要去做窗口子类化的那些额外的工作了。

I throw in a code snippet to handle control navigation with tabs to make this example more juicy. Normally, if you put controls on a dialog box, the dialog box manager handles the navigation keys for you so you can tab to go to the next control or shift-tab to go back to the previous control. Alas, such feature is not available if you put your controls on a simple window. You have to subclass them so you can handle the Tab keys yourself. In our example, we need not subclass the controls one by one because we already superclassed them, so we can provide a "central control navigation manager" for them.

我插入用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

The above code snippet is from EditWndClass procedure. It checks if the user press Tab key, if so, it call GetKeyState to check if  the SHIFT key is also pressed. GetKeyState returns a value in eax that determines whether the specified key is pressed or not. If the key is pressed, the high bit of eax is set. If not, the high bit is clear. So we test the return value against 80000000h. If the high bit is set, it means the user pressed shift+tab which we must handle separately.

 

上面的是摘自于 EditWndClass 过程的程序片断,它检查用户是否按下了TAB键,如果是,它调用GetKeyState来检查SHIFT键是否也被按下。GetKeyStateeax中返回的值用于确定指定的键是否被按下。如果这个键被按下,eax的高字节被设置,如果没有按下,高字节被清理。所以我们用80000000来测试返回值。若最高位是1则说明用户按下了 SHIFT-Tabs,这需要单独处理


If the user press Tab key alone, we call GetWindow to retrieve the handle of the next control. We use GW_HWNDNEXT flag to tell GetWindow to obtain the handle to the window that is next in line to the current hEdit. If this function returns NULL, we interpret it as no more handle to obtain so the current hEdit is the last control in the line. We will "wrap around" to the first control by calling GetWindow with GW_HWNDFIRST flag. Similar to the Tab case, shift-tab just works in reverse.

如果用户仅按下TAB键,我们调用GetWindow来获取下一个控件的句柄。我们使用GW_HWNDNEXT标志来告诉GetWindow函数获取的窗口句柄是和当前hEdit句柄链上的下一个句柄。如果这个函数返回值为NULL,说明在当前窗口句柄链中没有其它的句柄了,所以当前的hEdit是这个链上的最后一个句柄。我们在调用GetWindow函数时设置GW_HWNDFIRST来绕回到第一个控件上。类似于tab键,shift键处理过程正好相反。


This article come from Iczelion's asm page

风向改变翻译于2008-1-12

 

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