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