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.

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: 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: Superclassing is better than subclassing if you want to create many controls with the same characteristics.

Example:

____________________________________________________________________________________________

[hwndEdit: 0 0 0 0 0 0
 OldWndProc: 0
 ClassName: 'SuperclassWinClass' 0
 AppName:   'Superclassing Demo' 0
 EditClass: 'EDIT' 0
 OurClass:  'SUPEREDITCLASS' 0
 EnterMessage:   'You pressed the Enter key in the text box!' 0]

[WindowClassEx:  cbSize: len
 style: 3  lpfnWndProc: MainWindowProc    cbClsExtra: 0  cbWndExtra: 0
 hInstance: 0  hIcon: 0  hCursor: 0  hbrBackground: &COLOR_APPWORKSPACE
 lpszMenuName: 0  lpszClassName: ClassName  hIconSm: 0]

[WindowHandle: 0]

[FirstMessage: 0 #7]

_________________________________________________________________________________________

Main:

    call 'Kernel32.GetModuleHandleA' 0 | mov D§hInstance eax

    call 'User32.LoadIconA'  0  &IDI_APPLICATION | mov D§hIcon eax, D§hIconSm eax
 
    call 'User32.LoadCursorA' 0  &IDC_ARROW  | mov D§hCursor eax

    call 'User32.RegisterClassExA'  WindowClassEx

    call 'User32.CreateWindowExA' &WS_EX_CLIENTEDGE+&WS_EX_CONTROLPARENT ClassName AppName,
                                 &WS_OVERLAPPEDWINDOW+&WS_VISIBLE,
                                 &CW_USEDEFAULT &CW_USEDEFAULT 350 220,
                                 0  0  D§hInstance  0
      mov D§WindowHandle eax
 
    call 'User32.ShowWindow'  D§WindowHandle &SW_SHOW
    call 'User32.UpdateWindow'  D§WindowHandle

 
L1: call 'User32.GetMessageA' FirstMessage 0 0 0 | cmp eax 0 | je L9>
        call 'User32.TranslateMessage'  FirstMessage
        call 'User32.DispatchMessageA'  FirstMessage
    jmp L1<

L9: call 'Kernel32.ExitProcess' 0

_____________________________________________________________________________________
 

[OurWindowClass:  OWC_len: len
 OWC_style: 3  OWC_lpfnWndProc: 0  OWC_scbClsExtra: 0  OWC_scbWndExtra: 0
 OWC_shInstance: 0  OWC_hIcon: 0  OWC_hCursor: 0  OWC_shbrBackground: &COLOR_APPWORKSPACE
 OWC_slpszMenuName: 0  OWC_slpszClassName: OurClass  OWC_hIconSm: 0]
 

Proc MainWindowProc:
    Arguments @Adressee, @Message, @wPAram, @lParam

    pushad
    .If D@Message = &WM_CREATE
        call 'USER32.GetClassInfoExA' &NULL EditClass  OurWindowClass
        move D§OldWndProc D§OWC_lpfnWndProc
        mov D§OWC_lpfnWndProc  EditWndProc
        move D§OWC_shInstance D§hInstance
        mov D§OWC_slpszClassName OurClass
 
        call 'User32.RegisterClassExA' OurWindowClass
 
        mov ebx 0,  edi 20
        While ebx < 6
            push ebx, edi

                call 'User32.CreateWindowExA' &WS_EX_CLIENTEDGE  OurClass &NULL,
                                              &WS_CHILD+&WS_VISIBLE+&WS_BORDER,
                                              20  edi 300 25 D@Adressee ebx,
                                              D§hInstance &NULL
            pop edi, ebx
            mov D§hwndEdit+4*ebx eax
            add edi 25 | inc ebx
        End_While
        call 'User32.SetFocus' D§hwndEdit
    .Else_If D@Message = &WM_DESTROY
        call 'User32.PostQuitMessage' &NULL
    .Else
        popad
        call 'USER32.DefWindowProcA' D@Adressee  D@Message  D@wParam  D@lParam
        Exit
    .End_If
 
    popad | mov eax &FALSE
EndP

_________________________________________________________________________________________

Proc EditWndProc:
    Arguments @Adressee, @Message, @wPAram, @lParam
 
    pushad
 
    .If D@Message = &WM_CHAR
        mov eax D@wParam
        On al = &VK_BACK, jmp L8>>
            On al < '0', jmp L9>>
                On al <= '9', jmp L8>>
                    and al 0FF-32
                    On al < 'A', jmp L9>>
                        On al > 'F', jmp L9>>
                            mov D@wParam eax | jmp L8>>
 
    .Else_If D@Message = &WM_KEYDOWN
        mov eax D@wParam
        ..If al = &VK_RETURN
            call 'USER32.MessageBoxA' D@Adressee EnterMessage AppName &MB_ICONINFORMATION
            call 'USER32.SetFocus' D@Adressee
        ..Else_If al = &VK_TAB
            call 'USER32.GetKeyState' &VK_SHIFT
            test eax 080000000 | jnz L1>
                call 'USER32.GetWindow' D@Adressee  &GW_HWNDNEXT
                On eax = 0, call 'USER32.GetWindow' D@Adressee &GW_HWNDFIRST
                jmp L2>
 
L1:             call 'USER32.GetWindow' D@Adressee &GW_HWNDPREV
                On eax = 0, call 'USER32.GetWindow' D@Adressee &GW_HWNDLAST
 
L2:        call 'USER32.SetFocus' eax

        ..Else
            jmp L8>
 
        ..End_If
    .Else           ; Valid_Char:
L8:     popad
        call 'USER32.CallWindowProcA' D§OldWndProc D@Adressee D@Message D@wParam D@lParam
        Exit
 
    .End_If

                    ; UnValid_Char:

L9: popad | mov eax &FALSE

EndP
 

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:
 

    .If D@Message = &WM_CREATE
        call 'USER32.GetClassInfoExA' &NULL EditClass  OurWindowClass
 

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.

 
        move D§OldWndProc D§OWC_lpfnWndProc
        mov D§OWC_lpfnWndProc  EditWndProc
        move D§OWC_shInstance D§hInstance
        mov D§OWC_slpszClassName 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.

         call 'User32.RegisterClassExA' OurWindowClass

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

        mov ebx 0,  edi 20
        While ebx < 6
            push ebx, edi

                call 'User32.CreateWindowExA' &WS_EX_CLIENTEDGE  OurClass &NULL,
                                              &WS_CHILD+&WS_VISIBLE+&WS_BORDER,
                                              20  edi 300 25 D@Adressee ebx,
                                              D§hInstance &NULL
            pop edi, ebx
            mov D§hwndEdit+4*ebx eax
            add edi 25 | inc ebx
        End_While
        call 'User32.SetFocus' D§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.

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.
 
 

        ..Else_If al = &VK_TAB
            call 'USER32.GetKeyState' &VK_SHIFT
            test eax 080000000 | jnz L1>
                call 'USER32.GetWindow' D@Adressee  &GW_HWNDNEXT
                On eax = 0, call 'USER32.GetWindow' D@Adressee &GW_HWNDFIRST
                jmp L2>
 
L1:             call 'USER32.GetWindow' D@Adressee &GW_HWNDPREV
                On eax = 0, call 'USER32.GetWindow' D@Adressee &GW_HWNDLAST
 
L2:        call 'USER32.SetFocus' eax

        ..Else
            jmp L8>
 
        ..End_If

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.
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.


[Iczelion's Win32 Assembly Homepage]