Capítulo 5 Menús 1

Ahora que ya sabemos hacer el esqueleto de una aplicación Windows, veamos el primer medio para comunicarnos con ella.

Supongo que todos sabemos lo que es un menú: se trata de una ventana un tanto especial, del tipo pop-up, que contiene una lista de comandos u opciones entre las cuales el usuario puede elegir.

Cuando se usan en una aplicación, normalmente se agrupan varios menús bajo una barra horizontal, (que no es otra cosa que un menú), dividida en varias zonas o ítems.

Cada ítem de un menú, (salvo los separadores y aquellos que despliegan nuevos menús), tiene asociado un identificador. El valor de ese identificador se usará por la aplicación para saber qué opción se activó por el usuario, y decidir las acciones a tomar en consecuencia.

Existen varias formas de añadir un menú a una ventana, veremos cada una de ellas por separado.

También es posible desactivar o inhibir algunas opciones para que no estén disponibles para el usuario.

Usando las funciones para inserción ítem a ítem

Este es el sistema más rudimentario, pero como ya veremos en el futuro, en ocasiones puede ser muy útil. Empezaremos viendo este sistema porque ilustra mucho mejor la estructura de los menús.

Tomemos el ejemplo del capítulo anterior y definamos algunas constantes:

#define CM_PRUEBA 100
#define CM_SALIR  101

Y añadamos la declaración de una función en la zona de prototipos:

void InsertarMenu(HWND);

Al final del programa añadimos la definición de esta función:

void InsertarMenu(HWND hWnd)
{
   HMENU hMenu1, hMenu2;

   hMenu1 = CreateMenu(); /* Manipulador de la barra de menú */
   hMenu2 = CreateMenu(); /* Manipulador para el primer menú pop-up */
   AppendMenu(hMenu2, MF_STRING, CM_PRUEBA, "&Prueba"); /* 1º ítem */
   AppendMenu(hMenu2, MF_SEPARATOR, 0, NULL);           /* 2º ítem (separador) */
   AppendMenu(hMenu2, MF_STRING, CM_SALIR, "&Salir");   /* 3º ítem */
   /* Inserción del menú pop-up */
   AppendMenu(hMenu1, MF_STRING | MF_POPUP, (UINT)hMenu2, "&Principal");
   SetMenu (hWnd, hMenu1);  /* Asigna el menú a la ventana hWnd */
}

Y por último, sólo nos queda llamar a nuestra función, insertaremos ésta llamada justo antes de visualizar la ventana.

...
   InsertarMenu(hWnd);
   ShowWindow(hWnd, SW_SHOWDEFAULT);
...

Veamos cómo funciona "InsertarMenu".

La primera novedad son las variables del tipo HMENU. HMENU es un tipo de manipulador especial para menús. Necesitamos dos variables de este tipo, una para manipular la barra de menú, hMenu1. La otra para manipular cada uno de los menús pop-up, en este caso sólo uno, hMenu2.

De momento haremos una barra de menú con un único elemento que será un menú pop-up. Después veremos como implementar menús más complejos.

Para crear un menú usaremos la función CreateMenu, que crea un menú vacío.

Para ir añadiendo ítems a cada menú usaremos la función AppendMenu. Esta función tiene varios argumentos:

El primero es el menú donde queremos insertar el nuevo ítem.

El segundo son las opciones o atributos del nuevo ítem, por ejemplo MF_STRING, indica que se trata de un ítem de tipo texto, MF_SEPARATOR, es un ítem separador y MF_POPUP, indica que se trata de un menú que desplegará un nuevo menú pop-up.

El siguiente parámetro puede tener distintos significados:

  • Puede ser un identificador de comando, este identificador se usará para comunicar a la aplicación si el usuario selecionó un determinado ítem.
  • Un manipulador de menú, si el ítem tiene el flag MF_POPUP, en este caso hay que hacer un casting a (UINT).
  • O también puede ser cero, si se trata de un separador.

El último parámetro es el texto del ítem, cuando se ha especificado el flag MF_STRING, más adelante veremos que los ítems pueden ser también bitmaps. Normalmente se trata de una cadena de texto. Pero hay una peculiaridad interesante, para indicar la tecla que activa un determinado ítem de un menú se muestra la letra correspondiente subrayada. Esto se consigue insertando un '&' justo antes de la letra que se quiere usar como atajo, por ejemplo, en el ítem "&Prueba" esta letra será la 'P'.

Por último SetMenu, asigna un menú a una ventana determinada. El primer parámetro es el manipulador de la ventana, y el segundo el del menú.

Prueba estas funciones y juega un rato con ellas. A continuación veremos cómo hacer que nuestra aplicación responda a los mensajes del menú.

Uso básico de MessageBox

Antes de aprender a visualizar texto en la ventana, usaremos un mecanismo más simple para informar al usuario de cualquier cosa que pase en nuestra aplicación. Este mecanismo no es otro que el cuadro de mensaje (message box), que consiste en una pequeña ventana con un mensaje para el usuario y uno o varios botones, según el tipo de cuadro de mensaje que usemos. En nuestros primeros ejemplos, el cuadro de mensaje sólo incluirá el botón de "Aceptar".

Para visualizar un cuadro de mensaje simple, usaremos la función MessageBox. En nuestros ejemplos bastará con la siguiente forma:

MessageBox(hWnd, "Texto de mensaje", "Texto de título", MB_OK);

Esto mostrará un pequeño cuadro de diálogo con el texto y el título especificados y un botón de "Aceptar". El cuadro se cerrará al pulsar el botón o al pulsar la tecla de Retorno.

Respondiendo a los mensajes del menú

Las activaciones de los menús se reciben mediante un mensaje WM_COMMAND.

Para procesar estos mensajes, si sólo podemos recibir mensajes desde un menú, únicamente nos interesa la palabra de menor peso del parámetro wParam del mensaje.

Modifiquemos el procedimiento de ventana para procesar los mensajes de nuestro menú:

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)                  /* manipulador del mensaje */
    {
        case WM_COMMAND:
           switch(LOWORD(wParam)) {
              case CM_PRUEBA:
                 MessageBox(hwnd, "Comando: Prueba", "Mensaje de menú", MB_OK);
                 break;
              case CM_SALIR:
                 MessageBox(hwnd, "Comando: Salir", "Mensaje de menú", MB_OK);
                 /* envía un mensaje WM_QUIT a la cola de mensajes */
                 PostQuitMessage(0);
                 break;
           }
           break;
        case WM_DESTROY:
           /* envía un mensaje WM_QUIT a la cola de mensajes */
           PostQuitMessage(0);
           break;
        default: /* para los mensajes de los que no nos ocupamos */
           return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

Sencillo, ¿no?.

Observa que hemos usado la macro LOWORD para extraer el identificador del ítem del parámetro wParam. Después de eso, todo es más fácil.

También se puede ver que hemos usado la misma función para salir de la aplicación que para el mensaje WM_DESTROY: la función PostQuitMessage.

Ejemplo 2

Este ejemplo contiene todo lo que hemos visto sobre los menús hasta ahora.

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 2 win002.zip 2004-01-18 2247 bytes 52

Ficheros de recursos

Veamos ahora una forma más sencilla y más frecuente de implementar menús.

Lo normal es implementar los menús desde un fichero de recursos, el sistema que hemos visto sólo se usa en algunas ocasiones, para crear o modificar menús durante la ejecución de la aplicación.

Es importante adquirir algunas buenas costumbres cuando se trabaja con ficheros de recursos.

  1. Usaremos siempre etiquetas como identificadores para los ítems de los menús, y nunca valores numéricos literales.
  2. Crearemos un fichero de cabecera con las definiciones de los identificadores, en nuestro ejemplo se llamará "win003.h".
  3. Incluiremos este fichero de cabecera tanto en el fichero de recursos y como en el del código fuente de nuestra aplicación.

Partimos de un proyecto nuevo: win003. Pero usaremos el código modificado del ejemplo1.

Para ello creamos un nuevo proyecto de tipo GUI, al que llamaremos Win003, y copiamos el contenido de "ejemplo1.c" en un fichero vacío al que nombraremos como "win003.c".

A continuación crearemos el fichero de identificadores.

Añadimos el fichero de cabecera a nuestro proyecto. Desde Code::Blocks ésto se hace pulsando con el botón derecho del ratón "Archivo nuevo" o el menú de "Archivo->nuevo", nos preguntará si queremos añadir el archivo al proyecto, a lo que contestaremos que sí, y le pondremos un nombre, en este caso "win003.h".

Introducimos en é los identificadores:

#define CM_PRUEBA 100
#define CM_SALIR 101

En el fichero "win003.c" añadimos la línea:

#include "win003.h"

Justo después de la línea "#include <windows.h>".

Ahora añadiremos el fichero de recursos. Para ello haremos lo mismo que hemos hecho con el fichero "ids.h", pero usaremos el nombre "win003.rc".

En la primera línea introducimos la siguiente línea:

#include "win003.h"

Y a continuación escribimos:

Menu MENU
BEGIN
   POPUP "&Principal"
      BEGIN
         MENUITEM "&Prueba", CM_PRUEBA
         MENUITEM SEPARATOR
         MENUITEM "&Salir", CM_SALIR
      END
END

En un fichero de recursos podemos crear toda la estructura de un menú fácilmente. Este ejemplo crea una barra de menú con una columna "Principal", con dos opciones: "Prueba" y "Salir", y con un separador entre ellas.

La sintaxis es sencilla, definimos el menú mediante una cadena identificadora, sin comillas, seguida de la palabra MENU. Entre las palabras BEGIN y END podemos incluir items, separadores u otras columnas. Para incluir columas usamos una sentencia del tipo POPUP seguida de la cadena que se mostrará como texto en el menú. Cada POPUP se comporta del mismo modo que un MENU.

Los ítems se crean usado la palabra MENUITEM seguida de la cadena que se mostrará en el menú, una coma, y el comando asignado a ese ítem, que puede ser un número entero, o, como en este caso, una macro definida.

Los separadores se crean usando MENUITEM seguido de la palabra SEPARATOR.

Observarás que las cadenas que se muestran en el menú contienen un símbolo & en su interior, por ejemplo "&Prueba". Este símbolo indica que la siguiente letra puede usarse para activar la opción del menú desde el teclado, usando la tecla [ALT] más la letra que sigue al símbolo &. Para indicar eso, en pantalla, esa letra se muestra subrayada, en este ejemplo "Prueba".

Ya podemos cerrar el cuadro de edición del fichero de recursos.

Para ver más detalles sobre el uso de este recurso puedes consultar las claves: MENU, POPUP y MENUITEM.

Cómo usar los recursos de menú

Ahora tenemos varias opciones para usar el menú que acabamos de crear.

Primero veremos cómo cargarlo y asignarlo a nuestra ventana, ésta es la forma que más se parece a la del ejemplo del capítulo anterior. Para ello basta con insertar este código antes de llamar a la función ShowWindow:

   HMENU hMenu;
   ...
   hMenu = LoadMenu(hInstance, "Menu");
   SetMenu(hWnd, hMenu);

O simplemente:

   SetMenu(hWnd, LoadMenu(hInstance, "Menu"));

La función LoadMenu se encarga de cargar el recurso de menú, para ello hay que proporcionarle un manipulador de la instancia a la que pertenece el recurso y el nombre del menú.

Otro sistema, más sencillo todavía, es asignarlo como menú por defecto de la clase. Para esto basta con la siguiente asignación:

   WNDCLASSEX wincl;
   ...
   wincl.lpszMenuName = "Menu";

Y por último, también podemos asignar un menú cuando creamos la ventana, especificándolo en la llamada a CreateWindowEx:

    hwnd = CreateWindowEx(
           0,
           "NUESTRA_CLASE",
           "Ejemplo 003",
           WS_OVERLAPPEDWINDOW,
           CW_USEDEFAULT,
           CW_USEDEFAULT,
           544,
           375,
           HWND_DESKTOP,
           LoadMenu(hInstance, "Menu"), /* Carga y asignación de menú */
           hInstance,
           NULL
    );

El tratamiento de los comandos procedentes del menú es igual que en el apartado anterior.

Ejemplo 3

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 3 win003.zip 2004-01-18 2433 bytes 41