Tutorial 22: Superclasificación

En este tutorial aprenderemos sobre la superclasificación, qué es y para qué sirve. También aprenderás cómo suministrar navegación con la tecla Tab a los controloes de tu propia ventana.
Bajar el ejemplo aquí

Teoría:

En tu carrera de programación seguramente encontrarás alguna situación donde necesitarás controles con un comportamiento *levemente* distinto. Por ejemplo, puedes necesitar diez controles de edición que acepten sólo números hexadecimales. Hay varias maneras de alcanzar este objetivo:

El primer método es demasiado tedioso. Tienes que implementar todas las funcionalidades del control tú mismo. Es una tarea difícil de hacer con agilidad. El segundo método es mejor que el primero, pero todavía requiere mucho trabajo. Es bueno sólo si subclasificas unos cuantos controles pero ya casi es una pesadilla subclasificar más de doce ventanas. La superclasificación es la técnica que deberías usar para esta ocasión.

La subclasificación es la técnica que empleaas para *tomar el control* de una clase de ventana particular. Por *tomar el control*, quiero decir que puedes modificar la propiedad de la clase de la ventana para adaptarla a tus propósitos y luego crear un montón de controles.

Aquí están esbozados los pasos de la subclasificación:

La superclasificación es mejor que subclasificar s quieres crear mechos controles con las mismas características.

Ejemplo:

.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
 

Análisis:

El programa creará una ventana con seis controles de edición "modificados" en su área cliente. Los controles de edición aceptarán sólo dígitos hexadecimales. Realmente, el ejemplo de subclasificación para hacer supreclasificación. El programa comienza normalmente hasta llegar a la parte interesante, donde se crea la ventana:

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

Primero debemos llenar la estructura WNDCLASSEX con los datos de la clase que queremos subclasificar, en este caso, la clase EDIT. Recuerda que debes establecer el miembro cbSize de la estructurta WNDCLASSEX antes de llamar a GetClassInfoEx sino la estrcutura WNDCLASSEX no será llenada debidamente. Después de que regresa GetClassInfoEx, wc es llenada con toda la información que necesitamos para crear la nueva clase de ventana.

        push wc.lpfnWndProc
        pop OldWndProc
        mov wc.lpfnWndProc, OFFSET EditWndProc
        push hInstance
        pop wc.hInstance
        mov wc.lpszClassName,OFFSET OurClass

Ahora debemos modificar algunos miembros de wc. El primero es el puntero al procedimiento de ventana. Como necesitamos encadenar nuestro procedimiento de ventana con el original, debemos salvarlo con una variable para poderlo llamar con CallWindowProc. Esta técnica es idéntica a la de subclasificación, excepto porque modificas la estructura WNDCLASSEX directamente sin tener que llamar a SetWindowLong. Hay dos miembros que deberán ser cambiados, sino no padrás registrar la nueva clase de ventana:, hInstance and lpsClassName. Debes reemplazar el valor original de hInstance con hInstance del propio programa. Y debes elegir un nuevo nombre para la nueva clase.

        invoke RegisterClassEx, addr wc

Cuando todo esté listo, registras la nueva clase de ventana. Obtendrás una nueva clase de ventana con algunas características de la antigua clase.

        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

Ahora que hemos registrado la clase, podemos crear ventanas basadas en ella. En el recorte anterior, uso ebx como contador del número de ventanas creadas. edi es usado como la coordenada y correspondiente a la esquina izquierda del programa. Cuando se crea una ventana, su manejador es almacenado en un arreglo o array de variables dwords. Cuando todas las ventanas son creadas, porn el foco de la ventana a la primera ventana.

En este punto ya tienes 6 controles de edición que sólo aceptan dígitos hexadecimales. Los procedimientos de ventanas reemplazados manejan el filtro. Realmente, es idéntico al procedimiento de ventana en la subclasificación. Como puedes ver, ya no tienes que hacer el trabajo extra de la subclasificación.

Me he metido en un recorte de código para manejar controles de navegación con tab y hacer más rico este ejemplo. Normalmente, si quieres poner controles en una caja de diálogo, el propietario de la caja de diálogo maneja las teclas de navegación para tí, de manera que puedas usar la tecla tab para ir al próximo control o shift-tab para regresar al control previo. Alas, esta funcionalidad no está disponible si colocas los controles sobre una ventana. Tienes que subclasificarlos de manera que puedas manejar las teclas Tab por tí mismo. En nuestro ejemplo no necesitamos subclasificar los controles uno por uno porque los hemos superclasificado, así que podemos proveer un "propietario del control central de navegación" para ellos.
 

        .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

El recorte de código de arriba es tomado del procedimiento EditWndClass. Chequea si el usuario presionó la tecla Tab, si es así, llama a GetKeyState para chequear si la tecla SHIFT también está presionada. GetKeyState regresa un valor en eax que determina si la tecla específica ha sido presionada o no. Si ha sido presionada, el bit alto de eax está establecido, vale 1. Si no, el bit alto está en blanco, vale 0. Así que probamos el valor de retorno contra 80000000h. Si el bit alto está establecido, significa que el usuario presionó shift+tab lo cual debemos manejar por separado.

Si el usuario presiona sólo la tecla Tab, llamamos a GetWindow para recuperar el manejador del próximo control. Usamos la bendera GW_HWNDNEXT para decir a GetWindow que obtenga el manejador a la ventana próxima a la línea del actual hEdit. Si esta función regeras NULL, lo interpretamos como si no más manejadorespara obtenert así que el actual hEdit es el último control en la línea. We will "wrap around" al primer control llamando a GetWindow con la bendera GW_HWNDFIRST. Similar al caso Tab, shift-tab trabaja de manera inversa.


Índice

Siguiente

[Iczelion's Win32 Assembly Homepage]

n u M I T_o r's   Programming Page

Este tutorial, original de Iczelion, ha sido traducido por:   n u M I T_o r

www.000webhost.com