[ contenidos | #winprog ]

Controles estándar: Botones, Edición, Cajas de lista, Estáticos

Ejemplo: ctl_one

[images/ctl_one.gif]

Ya hemos usado botones en ejemplos anteriores, por lo tanto deberías estar mas o menos familiarizado con ellos, sin embargo debido a que he usado botones en este ejemplo los he agregado como parte del título solo con el propósito de completitud.

Controles

Una cosa para recordar acerca de los controles es que son solo ventanas. Al igual que cualquier otra ventana tienen un window procedure, una clase ventana etc... que es registrada por el sistema. Todo lo que puedes hacer con las ventanas normales, puedes hacerlo con los controles.

Mensajes

Como recordarás de las primeras discusiones sobre el bucle de mensajes, Windows se comunica usando mensajes, envíamos mensajes para que un control realice algo y cuando un evento ocurre sobre un control, éste nos envía un mensaje de notificación. Para controles estándar esta notificación será un mensaje WM_COMMAND, como sucede con los botones y menúes y para los Controles Comunes, de los cuales hablaré después, esta notificación será el mensaje WM_NOTIFY.

Los mensajes varían ampliamente para cada control, y cada control tiene su propio conjunto de mensajes. En algún momento el mismo mensaje será usado para mas de un tipo de control, pero en general solo funcionarán en el control para el cual están destinados. Esto es especialmente molesto con los mensajes listbox y combobox (LB* y CB* ) los cuales si bien realizan tareas casi idénticas NO SON intercambiables.

Por otro lado, los mensajes genéricos como WM_SETTEXT son soportados por casi todos los controles. Después de todo un control es una ventana.

Puedes enviar mensajes usando la API SendMessage( ) y usar GetDlgItem( ) para obtener el handle al control, o puedes usar SenDlgItemMessage( ) el cual realiza ambos pasos. En ambos métodos el resultado es el mismo.

Controles Edit

Uno de los controles mas comunmente usados en el entorno Windows, el control EDIT, es usado para permitir al usuario ingresar texto, modificar, copiar, etc... El Block de Notas de Windows no es mas que una ventana con un gran control edit dentro de ella.

Aqui está el código usado como interface para el control edit de este ejemplo:

    SetDlgItemText(hwnd, IDC_TEXT, "This is a string");

Esto es para cambiar el texto contenido dentro del control (esto puede ser usado para cualquier control que tenga un valor de texto asociado a él, STATICs, BUTTONs, etc...).

Recuperar el texto de un control también es fácil, si bien es apenas mas trabajo que cambiarlo...

    int len = GetWindowTextLength(GetDlgItem(hwnd, IDC_TEXT));
    if(len > 0)
    {
        int i;
        char* buf;

        buf = (char*)GlobalAlloc(GPTR, len + 1);
        GetDlgItemText(hwnd, IDC_TEXT, buf, len + 1);

        //Rellenamos con texto...

        GlobalFree((HANDLE)buf);
    }

Primero que todo, necesitamos reservar memoria para almacenar el string, para esto necesitamos conocer cuanta memoria reservar. No hay una función GetDlgItemTextLenght( ), pero existe GetWindowTextLenght( ), por lo tanto todo lo que necesitamos hacer es obtener el handle al control usando GetDlgItem( ).

Ahora que sabemos la longitud, podemos reservar la memoria necesaria para almacenar el string. Aquí, he agregado un chequeo para ver si existe algún texto (i.e la longitud del string es mayor que cero) para no estar trabajando sobre un string vacío. Asumiendo que existe dicho texto llamamos a GlobalAlloc( ) para reservar la memoria. Si eres usuario de DOS/UNIX GlobalAlloc( ) es equivalente a calloc( ). Esto reserva algo de memoria, inicializa su contenido a cero (0) y retorna un puntero a dicha memoria. Hay diferentes flags que puedes pasar en el primer parámetro para hacer que se comporte de manera diferente para diferentes propósitos, pero esta es la única manera en que la voy a usar durante el resto del tutorial.

Observa que le he sumado 1 a la longitud del string en dos lugares, ¿Por que? Bien, GetWindowTextLenght( ) retorna el número de caracteres que contiene el texto del control SIN INCLUIR el terminador nulo. Esto significa que si no sumamos 1 el texto podría caber pero el terminador nulo sobrepasaría los limites del bloque de memoria, causando una violación de acceso o sobreescribiendo otros datos. Debes ser cuidadoso cuando trabajas con longitudes de strings en windows, debido a que algunas APIs y mensajes esperan que las longitudes incluyan el caracter nulo y otras no lo hacen. Siempre lee la documentación cuidadosamente.

Finalmente podemos llamar a GetDlgItemText( ) para recuperar los contenidos del control en el buffer de memoria que acabamos de crear. Esta llamada espera el tamaño del buffer incluyendo el terminador nulo. El valor, el cual vamos a ignorar aquí, es el número de caracteres copiados SIN incluir el terminador nulo... divertido eh?

Hay un segundo conjunto de APIs llamadas LocalAlloc( ), LocalFree( ), etc... que son parte de las APIs de Windows 16-bits. En Win32 las funciones para memoria Local* y Free* son identicas.

Edits con Numeros

Cuando ingresamos texto está todo bien, ¿pero que pasa si queremos que el usuario ingrese un numero? Esta es una tarea muy común y afortunadamente hay una API para hacer esto mas simple. Esta API se encarga de toda la asignación de memoria, como tambien convertir el string a un valor entero.

    BOOL bSuccess;
    int nTimes = GetDlgItemInt(hwnd, IDC_NUMBER, &bSuccess, FALSE);

GetDlgItemInt( ) funciona de manera muy parecida a GetDlgItemText( ), excepto que en vez de copiar al string a un buffer, lo convierte internamente a un entero y luego retorna el valor. El tercer parámetro es opcional y toma un puntero a un BOOL. Debido a que la función retorna 0 en caso de falla, no hay forma de distinguir si la función ha fallado o el usuario ha ingresado como valor el numero 0, por lo tanto usamos este parámetro. Pero si te resulta adecuado utilizar el valor 0 en caso de error, entonces puedes ignorar este parámetro.

Otra característica muy usada para controles de edición es el estilo ES_NUMBER, el cual permite que solo sean ingresados los caracteres del 0 al 9. Esto el muy útil si solo quieres que se ingresen enteros positivos, en otro caso no tiene mucha utilidad debido a que no puedes ingresar caracteres como el menos (-), el punto decimal (.) o la coma (,).

Listas

Otro control muy usado son las listas. Este es el último control estandar que voy a cubrir por ahora, debido a que francamente no hay muchos interesantes y si aún no estás aburrido, yo si lo estoy :)

Agregado de Items

La primer cosa que quieres hacer con una lista es agregar items en ella.

    int index = SendDlgItemMessage(hwnd, IDC_LIST, LB_ADDSTRING, 0, (LPARAM)"Hi there!");

Como puedes ver, esta es una tarea muy simple. Si la lista tiene el estilo LBS_SORT, el nuevo item será agregado en orden alfabético, en otro caso simplemente será agregado al final de la lista.

Este mensaje retorna el índice del nuevo item, el cual podemos usar para realizar alguna tarea sobre el ítem, como asociar algunos datos a él. Generalmente esto es como un puntero a una estructura conteniendo mas información, o quizás un ID que usarás para identificar el ítem.

    SendDlgItemMessage(hwnd, IDC_LIST, LB_SETITEMDATA, (WPARAM)index, (LPARAM)nTimes);

Notificaciones

El proposito de las listas es permitir al usuario seleccionar cosas de una lista. Sin embargo, a veces queremos ser capaz de hacer algo en el acto, quizás mostrar información diferente o actualizada, basada en los items que fueron seleccionados. Para hacer esto necesitamos procesar los mensajes de notificación que nos envía la lista. En este caso estamos interesados en LBN_SELCHANGE, el cual nos dice que la selección ha sido modificada por el usuario. LBN_SELCHANGE es enviado via WM_COMMAND, pero a diferenecia del procesamiento de los mensajes WM_COMMAND que hacemos con los botones y menúes, que son generalmente en respuesta a un click, una lista envía el mensaje WM_COMMAND por varias razones y necesitamos un segundo chequeo para averiguar que nos está diciendo. El Código de Notificación es pasado como el HIWORD de wParam, la otra mitad del parámetro nos da el ID del control.
    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
            case IDC_LIST:
                // It's our listbox, check the notification code
                switch(HIWORD(wParam))
                {
                    case LBN_SELCHANGE:
                        // Selection changed, do stuff here.
                    break;
                }
            break;
            // ... other controls
        }
    break;

Obtener los datos de una lista

Ahora que sabemos que la selección ha cambiado, necesitamos obtener dicha selección de la lista y realizar algo útil con ella.

En este ejemplo he usado una lista multi-selección, por lo tanto obtener la lista de items seleccionados es un poco mas complicado. Si fuera una lista de selección simple, podríamos simplemente envíar LB_GETCURSEL para recuperar el índice del item.

Primero, necesitamos obtener los indices de los items seleccionados para que podamos reservar un búffer de memoria en donde guardar los índices.

    HWND hList = GetDlgItem(hwnd, IDC_LIST);
    int count = SendMessage(hList, LB_GETSELCOUNT, 0, 0);

Luego, reservamos un búffer basado en el número de items, y enviamos el mensaje LB_GETSELITEM para llenar el arreglo.

    int *buf = GlobalAlloc(GPTR, sizeof(int) * count);
    SendMessage(hList, LB_GETSELITEMS, (WPARAM)count, (LPARAM)buf);

    // ... Do stuff with indexes

    GlobalFree(buf);

En este ejemplo, buf[0] es el primer índice, buf[1] el segundo, etc.. siendo el último indice buf[count-1].

Una de las cosas que podrias querer realizar con la lista de índices, es recuperar los datos asociados con cada item y luego hacer algún proceso con éste. Esto se hace simplemente enviando otro mensaje.

    int data = SendMessage(hList, LB_GETITEMDATA, (WPARAM)index, 0);

Si los datos tienen algún otro tipo de valor (cualquier cosa distinta de 32-bits) simplemente debemos convertirlo al tipo apropiado. Por ejemplo si almacenamos HBITMAPs en vez de ints...

    HBITMAP hData = (HBITMAP)SendMessage(hList, LB_GETITEMDATA, (WPARAM)index, 0);

Estáticos

Al igual que los botones, los controles estáticos son ampliamente triviales, pero una vez mas por el propósito de completitud, voy a incluirlos aquí. Los controles estáticos son generalmente aquello que es, estático, significando que no cambian o no hacen nada en especial. Son muy usados para mostrar texto al usuario, sin embargo puedes hacer de ellos algo un poco mas util asignándoles un único ID (VC++ asigna un ID por default, IDC_STATIC, que tiene un valor de -1 y significa "Sin ID") y luego asignarles el texto en tiempo de ejecución para presentar texto dinámico al usuario.

En el ejemplo, he usado uno para mostrar los datos de los items seleccionados en la caja de lista, asumiendo que se puede seleccionar solo un item.

    SetDlgItemInt(hwnd, IDC_SHOWCOUNT, data, FALSE);

Copyright © 1998-2003, Brook Miles (theForger). All rights reserved.

Versión en Español: Federico Pizarro - 2003