Tutorial 9: Controles de Ventanas Hijas

En este tutorial exploraremos los controles de ventana hija que son unos dispositivos de entrada y salida muy importantes dentro de nuestros programas.

Baja el ejemplo aquí.

Teoría:

Windows provee algunas clases de ventana predefinidas que podemos usar satisfactoriamente dentro de nuestros programas. Muchas veces las usamos como componentes de una caja de diálogo por lo que ellas usualmente son llamadas controles de ventanas hijas. Los controles de ventanas hijas procesan sus propios mensajes de teclado y de ratón y notifican a las ventanas padres cuando sus estados han cambiado. Ellos liberan al programador de enormes cargas, así que deberías usarlas cada vez que sea posible. En este tutorial las pongo sobre una ventana normal para demostrar cómo puedes crearlas y usarlas, pero en realidad deberías ponerlas en una caja de diálogo.

Ejemplos de clases de ventanas predefinidas son el botón, la caja de lista [listbox], la caja de chequeo [checkbox], botón de radio [radio button], edición [edit] etc.

Con el fin de usar el control de vemtana hija, debes crearla con CreateWindow o CreateWindowEx. Nota que no tienes que registrar la clase de ventana puesto que Windows lo hace por tí. El parámetro nombre de la clase DEBE ser el nombre de la clase predefinida. Es decir, si quieres crear un botón, debes especificar "button" como nombre de la clase en CreateWindowEx. Los otros parámetros que debes llenar son la agarradera o manejador [handle] de la ventana padre y el ID del control. El ID del control debe ser único entre los controles. El ID del control es el ID de ese control. Lo usas para diferenciar entre controles.

Después de que el control fue creado, enviará mensajes de notificación a la ventana padre cuando su estado cambie. Normalmente, creas las ventanas hijas durante el mensaje WM_CREATE de la ventana padre. La ventana hija envía mensajes WM_COMMAND a la ventana padre con su ID de control en la palabra baja de wParam, el código de notificación en la palabra alta de wParam, y su manejador de ventana en lParam. Cada conrtol de ventana hija tiene su propio código de notificación, así que debes revisar la referencia de la API de Win32 para más información.

También la ventana padre puede enviar órdenes o comandos a la ventana hija, llamando a la función SendMessage. Esta función envía el mensaje especificado acompañado de otrops valores en wParam y lParam a la ventana especificada por el manejador de ventana. Es una función extremadamente útil, ya que puede enviar mensajes a cualquier ventana que conozcas su manejador.

Así que después de crear ventanas hijas, la ventana padre debe procesar los mensajes WM_COMMAND para poder recibir códigos de notificación desde las ventanas hijas.

Ejemplo:

Crearemos una ventana que contenga un control de edición y un "pushbutton". Cuando pulses el botón, un cuadro de mensaje aparecerá mostrando un texto que hayas escrito en el cuadro de diálogo. Hay también un menú con 4 elementos:

  1. Say Hello  -- Pone una cadena de texto en la caja de edición
  2. Clear Edit Box -- Limpia el contenido de la caja de edición
  3. Get Text -- despliega una caja de mensajes con el texto en la caja de edición
  4. Exit -- Cierra el programa.

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

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

.data
ClassName db "SimpleWinClass",0
AppName  db "Our First Window",0
MenuName db "FirstMenu",0
ButtonClassName db "button",0
ButtonText db "My First Button",0
EditClassName db "edit",0
TestString db "Wow! I'm in an edit box now",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndButton HWND ?
hwndEdit HWND ?
buffer db 512 dup(?)   ; buffer para almacenar el texto recuperado desde la ventana de edición

.const
ButtonID equ 1                                ; El ID del control botón [button]
EditID equ 2                                    ; El ID del control de edición [edit]
IDM_HELLO equ 1
IDM_CLEAR equ 2
IDM_GETTEXT equ 3
IDM_EXIT equ 4

.code
start:
    invoke GetModuleHandle, NULL
    mov    hInstance,eax
    invoke GetCommandLine
    invoke WinMain, hInstance,NULL,CommandLine, 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_BTNFACE+1
    mov   wc.lpszMenuName,OFFSET MenuName
    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_OVERLAPPEDWINDOW,\
                        CW_USEDEFAULT, CW_USEDEFAULT,\
                        300,200,NULL,NULL, hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    .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_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_CREATE
        invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,\
                        WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\
                        ES_AUTOHSCROLL,\
                        50,35,200,25,hWnd,8,hInstance,NULL
        mov  hwndEdit,eax
        invoke SetFocus, hwndEdit
        invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR ButtonText,\
                        WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
                        75,70,140,25,hWnd,ButtonID,hInstance,NULL
        mov  hwndButton,eax
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF lParam==0
            .IF ax==IDM_HELLO
                invoke SetWindowText,hwndEdit,ADDR TestString
            .ELSEIF ax==IDM_CLEAR
                invoke SetWindowText,hwndEdit,NULL
            .ELSEIF  ax==IDM_GETTEXT
                invoke GetWindowText,hwndEdit,ADDR buffer,512
                invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
            .ELSE
                invoke DestroyWindow,hWnd
            .ENDIF
        .ELSE
            .IF ax==ButtonID
                shr eax,16
                .IF ax==BN_CLICKED
                    invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
                .ENDIF
            .ENDIF
        .ENDIF
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
     xor    eax,eax
    ret
WndProc endp
end start

Analysis:

Analicemos el programa .

Creamos los controles cuando procesamos el mensaje WM_CREATE. Llamamos a CreateWindowEx con el estilo de ventana extra, WS_EX_CLIENTEDGE, el cual hace que la ventana cliente se vea con un borde sobreado. El nombre de cada control es un nombre predefinido, "edit" para el control de edición, "button" para el control botón. Luego especificamos los estilos de ventanas hijas. Cada control tiene estilos extras además de los estilos de ventana normal. Por ejemplo, los estilos de botón llevan el prefijo "BS_" para "button style", los estilos del control de edición llevan "ES_" para "edit style". Tienes que revisar estos estilos en la referenicia de la API de Win32. Nota que pones un ID de control en lugar del manejador del menú. Esto no causa problemas ya que el control de una ventana hija no puede tener menú.

Después de crear cada control, guardamos su manejador en una variable para su futuro uso.

Se llama a SetFocus para dar fooco de entrada a la caja de edición de manera que el usuario pueda tipear texto dentro de ella immediatamente.

Ahora la parte realmente exitante. Todo control de ventana hija envía una notificación a su ventana padre con WM_COMMAND.

    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .IF lParam==0

Recuerda que también un menu envía mensajes WM_COMMAND para notificar a su ventana sobre su estado. ¿Cómo puedes diferenciar si los mensajes WM_COMMAND son originados desde un menú o desde un control? He aquí la respuesta:
 

  Palabra baja de wParam Palabra alta de wParam lParam
Menu Menu ID 0 0
Control Control ID Código de notificación Manjeador de ventana hija

 

Como puedes ver, hay que chequear lParam. Si es cero, el mensaje WM_COMMAND actual es de un menú.No puedes usar wParam para diferenciar entre un menú y un control porque el ID del menú y el ID del control pueden ser idénticos y el código de notificación puede ser cero.

            .IF ax==IDM_HELLO
                invoke SetWindowText,hwndEdit,ADDR TestString
            .ELSEIF ax==IDM_CLEAR
                invoke SetWindowText,hwndEdit,NULL
            .ELSEIF  ax==IDM_GETTEXT
                invoke GetWindowText,hwndEdit,ADDR buffer,512
                invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK

Puedes poner una cadena de texto dentro de una caja de edición llamando a SetWindowText. Limpias el contenido de la caja de edición llamando a SetWindowText con NULL. SetWindowText es una función de la API de propósito general. Puedes usar SetWindowText para cambiar el encabezamiento o título [caption] de una ventana o el texto sobre un botón.

Para obtener el texto en una caja de edición, usas GetWindowText.

            .IF ax==ButtonID
                shr eax,16
                .IF ax==BN_CLICKED
                    invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
                .ENDIF
            .ENDIF

El fragmento de código de arriba tiene que ver con la condición de si el usuario presiona el botón. Primero, chequea la palabra baja de wParam para ver si el ID del control coincide con el del botón. Si es así, chequea la palabra alta de wParam para ver si es el código de notificación BN_CLICKED que se envía cuando el botón es pulsado.

La parte interesante es después que el código de notificación es BN_CLICKED. Queremos obtener el texto de la caja de edición y desplegarlo en la caja de edición. Podemos duplicar el código en la sección IDM_GETTEXT de arriba pero no tiene sentido. Si de alguna manera podemos enviar un mensaje WM_COMMAND con el valor IDM_GETTEXT en la palabra baja de wParam a nuestro procedimiento de ventana, podemos evitar duplicación de código y simplificar nuestro programa. La función SendMessage es la respuesta. Esta función envía cualquier mensaje a cualquier ventana con cualquieras wParam y lParam que desiemos. Así que e nvez de duplicar código, llamamos a SendMessage con el manejador de ventana padre, WM_COMMAND, IDM_GETTEXT, y 0. Esto tiene un efecto idéntico que seleccionar el elemento "Get Text" de nuestro menú. El procedimiento de ventana no percibirá ninguna diferencia entre los dos.

Deberías usar estas técnicas en la medida de lo posible para que tu código sea más organizado.

Por último, no olvides poner la función TranslateMessage en el bucle de mensajes, puesto que, como debes tipear algún texto en la caja de edición, tu programa debe traducir la entrada cruda del teclado a texto que pueda ser leído. Si omites esta función, no serás capáz de editar nada en la caja de edición.


Í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