37 Menús (2)

En el capítulo 5 tratamos el tema de los menús, pero de una manera superficial. La intención era dar unas nociones básicas para poder usar menús en nuestros primeros ejemplos. Ahora los veremos con más detalle, y estudiaremos muchas características que hasta ahora habíamos pasado por alto.

Los menús pueden tener, por ejemplo, mapas de bits a la izquierda del texto. También pueden comportarse como checkboxes o radiobuttons. Se pueden inhibir ítems. Podemos crear menús flotantes contextuales al pulsar con el ratón sobre determinadas zonas de la aplicación, etc.

Marcas en menús

Seguro que estás familiarizado con las marcas de chequeo que aparecen en algunos ítems de menú en casi todas las aplicaciones Windows. Es frecuente que se puedan activar o desactivar opciones, y que se pueda ver el valor actual de cada opción consultando menú. El funcionamiento es exactamente el mismo que el de los checkboxes y radiobuttons.

Hemos visto que los ítems de menú se comportan exactamente igual que los botones, pero hasta ahora sólo hemos usado los menús como un conjunto de "Pushbuttons", veremos qué otras opciones tenemos.

Menús como checkboxes

Recordemos que los checkboxes son uno o un conjunto de botones, cada uno de los cuales puede tener dos estados. Cada uno de los botones dentro de un conjunto de chekboxes puede estar marcado o no. De hecho, cada checkbox se comporta de un modo independiente, y sólo se agrupan conceptualmente, es decir, los grupos no son controlados por Windows.

Con menús podemos crear este efecto para cada ítem, añadiendo un mapa de bits a la izquierda que indique si la opción está marcada o no. Generalmente, cuando no lo esté, se eliminará la marca.

Checkboxes
Checkboxes

Disponemos de dos funciones para marcar ítems de menú. La más sencilla de usar es CheckMenuItem. Esta función necesita tres parámetros: el primero es el manipulador del menú, el segundo el identificador o la posición que ocupa el ítem, y el tercero dos banderas que indican si el segundo parámetro es un identificador o una posición y si el ítem se va a marcar o no.

Entonces, lo primero que necesitamos, es una forma de obtener un manipulador del menú de la ventana. Esto es sencillo: usaremos la función GetMenu, que nos devuelve el manipulador del menú asociado a una ventana.

Lo segundo será decidir si accedemos al ítem mediante un identificador o mendiante su posición. La primera opción es la mejor, ya que usando el identificador no necesitamos un manipulador al submenú concreto que tiene nuestro ítem. Eso siempre que los identificadores no estén duplicados, en ese caso necesitamos usar la segunda opción.

Otra cosa que necesitamos es averiguar si un ítem está o no marcado. Para ello podemos usar la función GetMenuState, que usa prácticamente los mismos parámetros que CheckMenuItem, salvo que el tercero sólo indica si el segundo es un identificador o una posición.

Por ejemplo, este sencillo código averigua si un ítem está o no marcado, y cambia el estado de la marca:

   if(GetMenuState(GetMenu(hwnd), CM_OPCION1, MF_BYCOMMAND) & MF_CHECKED)
      CheckMenuItem(GetMenu(hwnd), CM_OPCION1, MF_BYCOMMAND | MF_UNCHECKED);
   else
      CheckMenuItem(GetMenu(hwnd), CM_OPCION1, MF_BYCOMMAND | MF_CHECKED);

Sin embargo, la documentación del API de Windows dice que la función CheckMenuItem es obsoleta, aunque se puede seguir usando, y recomienda usar en su lugar la función SetMenuItemInfo. Esta función permite modificar otros valores, como veremos a lo largo de este capítulo.

Al tener más opciones, esta función es más complicada de usar. Necesita cuatro parámetros: el manipulador de menú, el identificador o posición del ítem, un tercer parámetro que indica si el segundo es un identificador o una posición y un puntero a una estructura MENUITEMINFO.

Esta estructura contiene campos que indican qué valores queremos modificar, y otros campos para indicar los nuevos valores. En nuestro caso, queremos modificar el valor de chequeo, por lo tanto, asignaremos el valor MIIM_STATE al campo fMask y al campo fState el valor apropiado: MFS_CHECKED o MFS_UNCHECKED.

La misma estructura se usa para recuperar valores de un ítem de menú, pero con la función GetMenuItemInfo, el valor del campo fState nos dirá si el ítem está o no marcado.

   MENUITEMINFO infoMenu;
...
   infoMenu.cbSize = sizeof(MENUITEMINFO);
   infoMenu.fMask = MIIM_STATE;
   GetMenuItemInfo(GetMenu(hwnd), CM_OPCIONA, FALSE, &infoMenu);
   if(infoMenu.fState & MFS_CHECKED)
      infoMenu.fState = MFS_UNCHECKED;
   else
      infoMenu.fState = MFS_CHECKED;
   SetMenuItemInfo(GetMenu(hwnd), CM_OPCIONA, FALSE, &infoMenu);

Por supuesto, a lo largo del programa siempre podremos consultar el estado de estos ítems, de modo que sabremos qué opción ha activado el usuario cuando lo necesitemos. Además, los items sigen generando mensajes WM_COMMAND, así que podemos procesarlos cuando sean modificados, en lugar se usarlos como simples editores de opciones.

Menús como radiobuttons

Cuando tenemos un grupo de opciones de las que sólo una de ellas puede estar activa, estamos ante un conjunto de radiobuttons. Estos botones sí deben estar agrupados, y se necesitan al menos dos de ellos en un grupo. En este caso, Windows sí puede gestionar el grupo automáticamente, para asegurarse que al marcar una opción, la que estaba activa anteriormente se desactive.

Radio buttons
Radio buttons

En este caso, procesar estos ítems es más simple, una única función bastará para gestionar todo un grupo de radioitems: CheckMenuRadioItem. Como primer parámetro tenemos el manipulador de menú, el segundo es el identificador o posición del primer ítem del grupo, el tercero el del último ítem del grupo, el cuarto el del ítem a marcar y el quinto indica si los parámetros segundo a cuarto son identificadores o posiciones.

Este ejemplo marca la opción 3 dentro de un grupo de 1 a 6. Automáticamente elimina la marca del ítem que la tuviese previamente:

   CheckMenuRadioItem(GetMenu(hwnd),CM_RADIO1, CM_RADIO6,
      CM_RADIO3, MF_BYCOMMAND);

Al usar esta función, la marca normal (V) se sustituye por la del círculo negro.

Podemos seguir usando las funciones GetMenuState o mejor, GetMenuItemInfo, para averiguar qué ítem es el activo en un momento dado.

Ejemplo 42

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 42 win042.zip 2004-07-22 3691 bytes 540

Inhibir y oscurecer ítems

También frecuente que en determinadas circunstancias queramos que algunas opciones no estén disponibles para el usuario, ya sea porque no tienen sentido, o por otra razón. Por ejemplo, esto es lo que pasa con la opción de "Maximizar" del menú de sistema cuando la ventana está maximizada.

Inhibir menús
Inhibir menús

En ese sentido, los ítems pueden tener tres estados distintos: activo, inhibido y oscurecido. Hasta ahora sólo hemos trabajado con ítems activos. Los inhibidos tienen el mismo aspecto para el usuario, pero no se pueden seleccionar. Los oscurecidos además de no poderse seleccionar, aparecen en gris o difuminados, para indicar que están inactivos.

Podemos cambiar el estado de acceso de un ítem usando la función EnableMenuItem, o mejor, con la función SetMenuItemInfo. Aunque la documentación del API dice que la primera está obsoleta, se puede seguir usando si no se necesitan otras características de la segunda.

La función EnableMenuItem necesita tres parámetros, el primero es el manipulador de menú, el segundo su identificador o posición, y el tercero indica si el segundo es un identificador o una posición y el estado que se va a asignar al ítem.

En este ejemplo se usa la función EnableMenuItem para inhibir y oscurecer un ítem, y la función SetMenuItemInfo para activarlo, de modo que se ilustran los dos modos de realizar esta tarea.

   MENUITEMINFO infoMenu;
...
   switch(LOWORD(wParam)) {
      case CM_INHIBIR:
         EnableMenuItem(GetMenu(hwnd),CM_OPCION, MF_DISABLED | MF_BYCOMMAND);
         CheckMenuRadioItem(GetMenu(hwnd),CM_INHIBIR, CM_ACTIVAR, CM_INHIBIR, MF_BYCOMMAND);
         break;
      case CM_OSCURECER:
         EnableMenuItem(GetMenu(hwnd),CM_OPCION, MF_GRAYED | MF_BYCOMMAND);
         CheckMenuRadioItem(GetMenu(hwnd),CM_INHIBIR, CM_ACTIVAR, CM_OSCURECER, MF_BYCOMMAND);
         break;
      case CM_ACTIVAR:
         infoMenu.cbSize = sizeof(MENUITEMINFO);
         infoMenu.fMask = MIIM_STATE;
         infoMenu.fState = MFS_ENABLED;
         SetMenuItemInfo(GetMenu(hwnd), CM_OPCION, FALSE, &infoMenu);
         CheckMenuRadioItem(GetMenu(hwnd),CM_INHIBIR, CM_ACTIVAR, CM_ACTIVAR, MF_BYCOMMAND);
         break;

Lo mismo se puede hacer con ModifyMenu, aunque esta función es obsoleta y se desaconseja su uso.

Ejemplo 43

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 43 win043.zip 2004-07-22 3650 bytes 484

Más sobre ficheros de recursos

También en lo que respecta a los ficheros de recursos hay más cosas que contar. Para empezar, además de la sentencia MENU existe otra sentencia para crear recursos de menús extendidos, que incorporan características introducidas en Windows 95. Pero de todos modos, aún no hemos visto todo sobre la sentencia MENUITEM.

La sintaxis de MENUITEM es:

MENUITEM texto, resultado, [lista_de_opciones]

Y la de POPUP es:

POPUP texto, [lista_de_optiones]
BEGIN
    definiciones_de_item
    ...
END

Hasta ahora nunca hemos usado las opciones (que evidentemente son opcionales). Ahora que hemos visto las marcas y los estados de los ítems de menú, veremos que esas opciones pueden ser interesantes:

Ficheros de recursos
Ficheros de recursos
  • GRAYED: el nombre del ítem esta inicialmente inactivo y aparece en el menú en gris o con el texto del menú ligeramente degradado. En el ejemplo, el ítem "Gris".
  • HELP identifica un ítem de ayuda. Esto hace que se muestre alineado a la derecha. En el ejemplo, todo el POPUP "Pruebas" ha sido definido con esta opción.
  • INACTIVE el nombre del ítem se muestra, pero no puede ser seleccionado. En el ejemplo, el ítem "Inactivo".
  • MENUBREAK coloca el ítem de menú en una nueva línea en ítems de menú. En menús pop-up, coloca el ítem de menú en una nueva columna sin división entre las columnas. En el ejemplo es el caso del ítem "Break".
  • MENUBARBREAK lo mismo que MENUBREAK excepto que para menús pop-up, separa la nueva columna de la anterior con una línea vertical. En el ejemplo, "Bar break".
  • CHECKED: el ítem tiene una marca de verificación junto a él. En el ejemplo, el ítem "Prueba 2" está definido con esta opción. Esta opción sólo se aplica a ítems de menú, no a popups.
menu MENU
BEGIN
 POPUP "&Pruebas"
 BEGIN
  MENUITEM "&Ayuda", 500
  MENUITEM "&Gris",  501, GRAYED
  MENUITEM "&Inactivo", 502, INACTIVE
  MENUITEM "Ba&r Break", 503, MENUBARBREAK
  MENUITEM "Prueba &2", 504, CHECKED
  MENUITEM "Prueba &3", 505
  MENUITEM "&Break", 506, MENUBREAK
  MENUITEM "Prueba &4", 507
  MENUITEM "Prueba &5", 508
 END
END

Detalles sobre cadenas de ítems

En cuanto a las cadenas de caracteres que se usan en los ítems, hay varias secuencias de caracteres que nos permiten cierto control sobre el aspecto de las cadenas.

Ya hemos visto que se puede seleccionar una de las letras para que se pueda activar el ítem mediante el teclado, basta como poner el carácter & delante de la letra elegida.

Si queremos que el carácter & aparezca como parte del texto del ítem, habrá que duplicar el carácter &&, por ejemplo "&Carácter &&" aparecerá como Carácter &.

Del mismo modo, para incluir comillas dobles dentro de la cadena, se deben escapar mediante la secuencia \", por ejemplo "Cadena \"prueba\"" aparecerá como Cadena "prueba".

Por último, podemos usar la secuencia \a para justificar el texto a la derecha, y la secuencia \t para introducir un tabulador. Estas secuencias se suelen usar para añadir la información sobre las teclas aceleradoras de menú, como vimos en el capítulo anterior.

Como habrás visto, en las sentencias anteriores no es posible crear menús con todas las características que hemos explicado. Por ejemplo, no se pueden crear ítems con la marca de "radio" en lugar de la de "check". Los menús evolucionaron a partir de Windows 95, y pora adaptarse a ello, se creó una nueva sentencia: MENUEX.

La sintaxis es la siguiente:

menuID MENUEX
BEGIN
    [{[MENUITEM itemText [, [id] [, [type [| state]]]] |
    [POPUP itemText [, [id] [, [type [| state] [, helpID]]]]
    BEGIN
        popupBody
    END]} ...]
END

En realidad, la sintaxis de MENUEX es la misma que la de MENU, lo que cambia es que las sentencias MENUITEM y POPUP tienen más posibilidades dentro de una sentencia MENUEX.

Como vemos, en el caso de MENUITEM, tanto el identificador, como el tipo, como es estado son opcionales.

Además, tanto para el tipo como para el estado, existen más posibilidades que en el MENUITEM anterior, en concreto podemos usar las constantes de tipo definidas en el fichero "winuser.h" para la estructura MENUITEMINFO, que empiezan con MFT_. Para eso hay que incluir ese fichero de cabecera en el fichero de recursos.

Estas constantes son:

  • MFT_BITMAP: muestra el ítem de menú usando un mapa de bits. En realidad, esta constante no se puede usar en ficheros de recursos, y se debe modificar en el fichero ejecutable. Veremos esto más abajo.
  • MFT_MENUBARBREAK: coloca el ítem de menú en una línea nueva (para una barra de menú) o en una columna nueva (para un menú desplegable, un submenú o un menú de atajo). Para este último caso, una línea vertical separa la nueva columna de la antigua. Equivale a MENUBARBREAK de MENU.
  • MFT_MENUBREAK: coloca el ítem de menú en una línea nueva (para una barra de menú) o en una columna nueva (para un menú desplegable, un submenú o un menú de atajo). Para este último caso, la columna no se separa con una línea vertical. Equivale a MENUBREAK.
  • MFT_OWNERDRAW: asigna la responsabilidad del trazado del ítem de menú a la ventana a la que pertenece el menú. Esta bandera tampoco se puede usar en un fichero de recursos.
  • MFT_RADIOCHECK: muestra los ítems marcados usando la marca del radio-button en lugar una marca de chequeo si el miembro hbmpChecked es NULL. Esta bandera tampoco se puede usar en un fichero de recursos.
  • MFT_RIGHTJUSTIFY: justifica a la derecha el ítem de menú y los ítems siguientes. Este valor sólo es válido si el ítem de menú está en una barra de menú. Equivale a HELP.
  • MFT_SEPARATOR: especifica que el ítem de menú es un separador. Un ítem de menú separador aparece como una línea horizontal divisora. No es válido en barras de menú. Equivale al SEPARATOR de MENU. Tampoco se puede usar en ficheros de recursos, para poner separadores basta con usar una cadena nula, ("").
  • MFT_STRING: muestra el ítem de menú usando una cadena de texto. Es el valor por defecto, no es necesario especificarlo.

Para el estado podemos usar las constantes declaradas en "winuser.h" con el prefijo MFS_.

  • MFS_CHECKED: marca el ítem de menú. Equivale a CHECKED.
  • MFS_DEFAULT: especifica que el ítem de menú es el ítem por defecto. Esta bandera no se puede usar en un archivo de recursos. Veremos esto más abajo.
  • MFS_DISABLED: inhibe el ítem de menú de modo que no puede ser seleccionado, pero no lo oscurece. Equivale a INACTIVE.
  • MFS_ENABLED: desinhibe el ítem de menú de modo que pueda ser seleccionado. Este es el estado por defecto, y no es necesario especificarlo.
  • MFS_GRAYED: inhibe el ítem de menú y lo oscurece de modo que no puede ser seleccionado. Equivale a GRAYED.
  • MFS_HILITE: resalta el ítem de menú. Esta bandera tampoco se puede usar en ficheros de recursos.
  • MFS_UNCHECKED: quita la marca del ítem de menú. Este es el estado por defecto, tampoco es necesario especificarlo.
  • MFS_UNHILITE: elimina el resaltado del ítem de menú. Este es el estado por defecto, tampoco es necesario especificarlo.
menu MENUEX
BEGIN
 POPUP "&Principal"
 BEGIN
  MENUITEM "&Inhibir",   CM_INHIBIR, MFT_STRING
  MENUITEM "&Oscurecer", CM_OSCURECER, MFT_STRING
  MENUITEM "&Activar",   CM_ACTIVAR, MFT_STRING
  MENUITEM "" // MFT_SEPARATOR
  MENUITEM "O&pción",    CM_OPCION, MFT_STRING
 END
 POPUP "&Pruebas",0,MFT_STRING | MFT_RIGHTJUSTIFY
 BEGIN
  MENUITEM "&Ayuda", 500, MFT_STRING
  MENUITEM "&Gris",  501, MFT_STRING, MFS_GRAYED
  MENUITEM "&Inactivo", 502, MFT_STRING, MFS_DISABLED
  MENUITEM "Ba&r Break", 503, MFT_STRING | MFT_MENUBARBREAK
  MENUITEM "Prueba &2", 504, MFT_STRING, MFS_CHECKED
  MENUITEM "Prueba &3", 505, MFT_STRING, MFS_UNCHECKED
  MENUITEM "&Break", 506, MFT_STRING | MFT_MENUBREAK
  MENUITEM "Prueba &4", 507, MFT_STRING
  MENUITEM "Prueba &5", 508, MFT_STRING
 END
END
Nota:

He encontrado tres errores en la documentación de Windows.

1. Las definiciones de las constantes MFS_DISABLED y MFS_GRAYED, dentro del fichero "winuser.h" actualmente están modificadas. La primera macro debe valer 2, y la segunda 1; actualmente ambas valen 3.

2. En la documentación original de MENUEX dice que los valores de tipo y estado deben estar separados con una coma. He comprobado que en algunas versiones antiguas del compilador de recursos, para que todo funcione adecuadamente, estos valores se deben combinar usando el operador de bits OR (|) o el de suma (+).

3. Los separadores dentro de menús POPUP no se consiguen con la bandera de tipo MFS_SEPARATOR, sino usando una cadena nula ("").

Resumamos un poco:

Items marcados y no marcados

Bueno, ya hemos visto que cuando definimos recursos de menú podemos elegir el estado incial de las marcas de chequeo. En el caso de MENU mediante los modificadores CHECKED para marcado, y nada para no marcados. En el caso de MENUEX mediante las banderas MFS_CHECKED y MFS_UNCHECKED.

Ya hemos visto que no podemos hacer nada para que los ítems sean de tipo radio en el fichero de recursos. Esta característica se modifica durante la ejecución.

Items activos, inactivos u oscurecidos

Por defecto, los ítems de menú estarán activos, pero si queremos crearlos inactivos u oscurecidos, en el caso del recurso MENU usaremos los modificadores INACTIVE o GRAYED respectivamente. En el caso del recurso MENUEX las banderas MFS_DISABLED para inhibido, o MFS_GRAYED para oscurecido.

Separadores y líneas de ruptura

Existen varias opciones para separar ítems, ya sea horizontalmente o verticalmente, y en barras de menú o en menús desplegables.

En barras de menú, y con recursos MENU podemos usar los modificadores MENUBREAK o MENUBARBREAK para cambiar de línea un ítem o un popup. También podemos usar el modificar HELP para trasladar un ítem o popup a la derecha de la barra de menú.

En el caso del recurso MENUEX, el mismo efecto se consigue con las banderas MFT_MENUBREAK, MFT_MENUBARBREAK o MFT_RIGHTJUSTIFY, respectivamente.

Dentro de los menús popup, los modificadores del recurso MENU tienen efectos diferentes. El modificador MENUBREAK hace que el siguiente ítem se sitúe en una nueva columna, y el modificador MENUBARBREAK lo mismo, pero se añade una línea vertical separadora.

Los separadores horizontales, en el caso del recurso MENU se consiguen con una línea MENUITEM SEPARATOR, estos separadores son útiles para agrupar ítems, o grupos de opciones del tipo checkitems o radioitems.

En el caso de recursos MENUEX los separadores verticales se consiguen con las banderas MFT_MENUBREAK y MFT_MENUBARBREAK, y los horizontales con una cadena vacía MENUITEM "".

Nota:

SEPARATOR es probablemente una etiqueta para la cadena "", y son intercambiables.

Cargar recursos

Para cargar un recurso de menú tenemos varias opciones, como ya vimos en el capítulo 5. Lo más simple es usar un menú de clase, asignando la cadena con el nombre del menú al miembro lpszMenuName de la estructura WNDCLASSEX o WNDCLASS, por ejemplo:

wincl.lpszMenuName = "menu";

También podemos cargar recursos de menú y usar un manipulador para referirnos a ellos durante la ejecución, mediante la función LoadMenu:

    HMENU hMenu;
...
    hMenu = LoadMenu(hThisInstance, "menu");

Este menú se puede asignar a una ventana mediante la función SetMenu o al crear la ventana con las funciones CreateWindow o CreateWindowEx, pero más abajo veremos que existen otras posibilidades.

Ítems por defecto

Para cada popup se puede marcar un ítem para que sea el ítem por defecto. Cuando hagamos doble clic sobre el popup, será como si hubiésemos seleccionado el ítem por defecto. Para indicar al usuario cual es ese ítem, aparecerá marcado en negrita.

Ítems por defecto
Ítems por defecto

El ítem por defecto hay que marcarlo durante la ejecución, ya hemos visto que no se puede hacer en el fichero de recursos. Para hacerlo disponemos de la función SetMenuDefaultItem.

Pero esta función hay que manejarla con cuidado, ya que en cada menú desplegable sólo puede haber un ítem por defecto, como primer parámetro deberemos pasar un manipulador del submenú donde se encuentre el ítem. Como además, no tiene mucho sentido marcar un ítem por defecto en una barra de menú (ya que siempre será accesible mediante un clic, y un doble clic sobre una barra de menú no hace nada), generalmente deberemos usar la función GetSubMenu para obtener un manipulador al submenú que contiene el ítem a marcar por defecto. Esto se debe hacer aunque el identificador del ítem a marcar sea único:

   SetMenuDefaultItem(GetSubMenu(GetMenu(hwnd),0), CM_OPCION, FALSE);

Este ejemplo marcará el ítem con el identificador CM_OPCION en el primer submenú del menú de la ventana hwnd.

Para obtener el ítem por defecto de un submenú podemos usar la función GetMenuDefaultItem, con las mismas precauciones:

   id = GetMenuDefaultItem(GetSubMenu(GetMenu(hwnd),0), FALSE, GMDI_GOINTOPOPUPS);

El primer parámetro es un manipulador de submenú, el segundo indica el tipo de valor devuelto: FALSE para que devuelva un identificador, y TRUE para que devuelva una posición. El tercero indica el modo de realizar la búsqueda, el valor usado en el ejemplo busca recursivamente.

También podemos usar las funciones SetMenuItemInfo y GetMenuItemInfo para hacer las mismas tareas.

En concreto, usar la función SetMenuItemInfo es más práctico, ya que no necesitamos obtener un manipulador de submenú, y marcará el ítem por defecto independientemente de cual sea el submenú donde esté:

   infoMenu.cbSize = sizeof(MENUITEMINFO);
   infoMenu.fMask = MIIM_STATE
   infoMenu.fState = MFS_DEFAULT;
   SetMenuItemInfo(GetMenu(hwnd), CM_OPCION, FALSE, &infoMenu);

Ejemplo 44

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 44 win044.zip 2004-07-22 3925 bytes 544

Menús flotantes o contextuales

Menús flotantes
Menús flotantes

Otra posibilidad de los menús es crear menús flotantes, también llamados menús de atajo o menús contextuales. Estos menús se suelen mostrar cuando el usuario hace clic con el ratón sobre distintas zonas de la ventana, y normalmente se muestran distintos menús dependiendo de la zona, o mejor dicho, del contexto.

Todo esto es responsabilidad del programador: procesar las pulsaciones de botones de ratón, decidir qué menú mostrar, y finalmente mostrarlo en pantalla.

La aplicación recibe el mensaje WM_CONTEXTMENU cuando el usuario hace clic sobre la ventana, aunque también podemos procesar los mensajes de ratón comunes.

Para mostrar el menú flotante en pantalla se usa la función TrackPopupMenuEx:

   HMENU hmenu;
...
   case WM_CREATE:
      hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
      hmenu = LoadMenu(hInstance, "menu2");
...
   case WM_CONTEXTMENU:
      TrackPopupMenuEx(GetSubMenu(hmenu, 0),
         TPM_CENTERALIGN | TPM_HORIZONTAL | TPM_RIGHTBUTTON,
         LOWORD(lParam), HIWORD(lParam),
         hwnd, NULL);
      break;
...
   case WM_DESTROY:
      DestroyMenu(hmenu);
...

El primer parámetro es un manipulador del menú popup que queremos visualizar, debe ser un manipulador de menú creado mediante la función CreatePopupMenu o, como en este ejemplo, extraído de un menú existente, que a su vez puede ser uno asignado a una ventana, o cargado de un recurso.

Hay que recordar que hay que destruir los menús que no estén asignados a una ventana mediante la función DestroyMenu.

En el ejemplo 45, que ilustra el uso de los menús flotantes, usamos un menú de recurso para crear todos los posibles menús flotantes, y elegimos el que nos interesa en cada caso mediante la función GetSubMenu.

El segundo parámetro son banderas que nos ayudan a situar el menú con relación a las coordenadas que indicamos en los parámetros tercero y cuarto.

El quinto parámetro identifica la ventana que recibirá los mensajes generados por el menú, y el sexto, nos permite definir un área de exclusión, donde no se mostrará el menú, si es posible.

Ejemplo 45

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 45 win045.zip 2004-07-23 4345 bytes 539

Acceso por teclado

Ya comentamos en el capítulo dedicado al ratón que ese dispositivo no es imprescindible en Windows, de modo que la comunicación entre la aplicación y el usuario se puede hacer exclusivamente desde el teclado, y los menús no son una excepción.

Mnemónicos

Una de las formas de agilizar el acceso por teclado son los mnemónicos, que se asignan añadiendo el símbolo & delante de la letra que activa el menú popup o el ítem de menú.

Aceceso de teclado estándar

Además existen teclas y combinaciones de teclas especialmente dedicadas al acceso a los menús:

  • Mnemónico: el carácter correspondiente selecciona el primer ítem con ese mnemónico. Si el ítem abre un menú, éste se muestra y se resalta su primer ítem. En caso contrario, se selecciona ese ítem. Para activar un menú mediante el mnemónico en necesario pulsar la tecla ALT.
  • ALT: enciende y apaga la barra de menú.
  • ALT+SPACEBAR: muestra el menú de ventana (o menú de sistema).
  • ENTER: activa un menú y selecciona su primer ítem de menú, si es que ese ítem abre un submenú. En caso contrario, se selecciona el ítem como si el usuario hubiese soltado el botón del ratón mientras el ítem estaba seleccionado.
  • ESC: sale del modo de menú.
  • Flecha izquieda: regresa al ítem de menú previo. Los ítems de mayor nivel incluyen los nombres y el menú de sistema. Si el ítem seleccionado está en una barra de menú, se selecciona la columna anterior de la columna o el menú de mayor nivel previo.
  • Flecha derecha: funciona como la tecla de flecha izquierda, pero en dirección contraria. En barras de menú, esta tecla mueve hacia la derecha una columna; cuando el ítem seleccionado actualmente es la última de la derecha abre, se selecciona el siguiente menú.
  • Flechas arriba y abajo: activa un menú cuando se presiona en un nombre de menú. Cuando se presiona en un menú, la flecha arriba selecciona el ítem previo y la flecha abajo el siguiente.

Aceleradores

Otro modo de acceder a opciones de menú mediante el teclado son los aceleradores, aunque en realidad estos son independientes de los menú, es costumbre añadir a los menús las combinaciones de teclas que activan la misma opción mediante un acelerador. De este modo, el usuario puede memorizar más fácilmente aquellas opciones de menú que usa más frecuentemente, y acceder a ellas mediante aceleradores en lugar de hacerlo mediante el menú.

Modificar menús

Siempre es posible modficar un menú durante la ejecución de la aplicación, tan sólo necesitamos su manipulador y aplicar las funciones que necesitemos entre las siguientes:

Función Descripción
AppendMenu Añade un nuevo ítem de menú al final del menú especificado. Esta función ha sido sustituida por InsertMenuItem. Sin embargo, se puede seguir usando, si no se necesitan las características extendidas de la función InsertMenuItem.
InsertMenu Inserta un nuevo ítem de menú dentro de un menú, moviendo los otros ítems hacia abajo. Esta función también ha sido sustituida por la función InsertMenuItem. De todos modos, se puede seguir usando, si no se necesitan las características extendidas de InsertMenuItem.
InsertMenuItem Inserta un nuevo ítem de menú en la posición especificada de un menú. Usa una estructura MENUITEMINFO para crear el ítem.
ModifyMenu Modifica un ítem de menú existente. Esta función ha sido sustituida por SetMenuItemInfo. De todos modos, se puede seguir usando si no se necesitan las características extendidas de SetMenuItemInfo.
SetMenuItemInfo Modifica la información sobre un ítem de menú. Usa una estructura MENUITEMINFO para modificar el ítem.
DeleteMenu Borra un ítem del menú especificado. Si el ítem de menú abre un menú o submenú, esta función destruye el manipulador del menú o submenú y libera la memoria usada por él.

Si se modifica un menú que actualmente se está visualizando, se debe llamar a la función DrawMenuBar para actualizar la ventana y reflejar los cambios.

El menú de sistema

El menú de sistema, también llamado menú de ventana o menú de control, es el que se muestra cuando se hace clic sobre el icono de la aplicación, o cuando se pulsa Alt+espacio.

Cuando se crea una aplicación, Windows asigna siempre el menú de sistema por defecto, siempre que se especifique el estilo WS_SYSMENU al crear la ventana.

A diferencia de los menús que hemos usado hasta ahora, el menú de sistema genera mensajes WM_SYSCOMMAND, en lugar de mensajes WM_COMMAND, y generalmente, salvo que modifiquemos el menú de sistema, dejaremos que el proceso por defecto se encargue de esos mensajes, mediante la función DefWindowProc.

Modificar el menú de sistema

Modificar el menú de sistema es relativamente simple. Para empezar, obtendremos un manipulador de menú del menú de sistema mediante la función GetSystemMenu, usaremos como parámetros el manipulador de la ventana, y el valor FALSE, de modo que obtenemos una copia modificable del menú de sistema actual.

Una vez disponemos de un manipulador de menú para una copia del menú de sistema, podemos modificarlo usando las funciones que ya conocemos: AppendMenu, InsertMenu, InsertMenuItem, SetMenuItemInfo, ModifyMenu o DeleteMenu.

Los identificadores de los ítems que insertemos deben ser menores que 0xf000.

   static HMENU hmenuSistema;
...
   switch(msg) {  /* manipulador del mensaje */
      case WM_CREATE:
        hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
        hmenuSistema = GetSystemMenu(hwnd, FALSE);
        InsertMenu(hmenuSistema, 0, MF_BYPOSITION | MF_STRING | MF_POPUP,
           (UINT)GetSubMenu(GetMenu(hwnd), 1), "&Opciones");
        break;
...

Los ítems incluidos en un menú de sistema generan mensajes WM_SYSCOMMAND, por lo tanto, deberemos procesar este mensaje, teniendo cuidado de remitir a la función DefWindowProc aquellos que no procesemos nosotros.

      case WM_SYSCOMMAND:
        switch(LOWORD(wParam)) {
          case CM_OPCION1:
             break;
          case CM_OPCION2:
             break;
          case CM_OPCION3:
             break;
          case CM_OPCION4:
             break;
          default:
             return DefWindowProc(hwnd, msg, wParam, lParam);
        }
        break;

Ejemplo 46

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 46 win046.zip 2004-07-23 3575 bytes 582

Destrucción de menús

Los menús asociados a ventanas se destruyen automáticamente cuando se destruyen las ventanas, de modo que sólo es necesario destruir los menús que no estén asociados a ninguna ventana, en general, serán los que usemos como menús flotantes, o como menús alternativos, por ejemplo, si disponemos de diferentes versiones de menús para nuestras ventanas, y los usamos aternativamente, en función de las circunstancias.

Para destruir uno de esos menús se usa la función DestroyMenu.

Mensajes de menú

Ya hemos visto algunos de los mensajes que generan los menús, en concreto los que generan cuando se seleccionan ítems:

Función Descripción
WM_COMMAND Es enviado cuando el usuario selecciona un comando de un ítem de un menú.
WM_SYSCOMMAND Cuando el usuario elige un comando desde el menú de ventana.

Pero hay más, algunos de los más interesantes pueden ser:

Función Descripción
WM_INITMENU Se envía cuando un menú se va a activar. Eso ocurre cuando el usuario hace clic sobre un ítem de la barra de menú o cuando presiona la tecla de menú. Nos permite modificar el menú antes de que se muestre.
WM_INITMENUPOPUP Se envía cuando un menú emergente o un submenú va a ser activado. Esto permita a una aplicación modificar el menú antes de que sea mostrado, sin modificar el menú completo.
WM_MENUSELECT Se envía a la ventana cuando el usuario selecciona un ítem del menú.
WM_CONTEXTMENU Notifica a una ventana que el usuario ha hecho clic con el botón derecho del ratón en la ventana.

Mapas de bits en ítems de menú

hay varios modos de incluir mapas de bits en ítems de menú, en este capítulo veremos dos de ellos, dejaremos el métodp owner-draw para capítulos posteriores.

Modificar mapas de bits de check

Una de las formas de insertar mapas de bits, sin modificar el resto del ítem, es cambiar las marcas de chequeo por defecto por las diseñadas por nosotros. Esto se consigue con la función SetMenuItemBitmaps o con SetMenuItemInfo.

Mapas de bits
Mapas de bits
   static HBITMAP hSi;
   static HBITMAP hNo;
...
      hSi = LoadBitmap(hInstance, "si");
      hNo = LoadBitmap(hInstance, "no");

      infoMenu.cbSize = sizeof(MENUITEMINFO);
      infoMenu.fMask = MIIM_CHECKMARKS;
      infoMenu.hbmpChecked = hSi;
      infoMenu.hbmpUnchecked = hNo;
      for(i = 0; i < 3; i++)
         SetMenuItemInfo(GetSubMenu(GetMenu(hwnd),0),
            i, TRUE, &infoMenu);
...
      DeleteObject(hNo);
      DeleteObject(hSi);

Lo mismo, usando la función SetMenuItemBitmaps:

...
      hSi = LoadBitmap(hInstance, "si");
      hNo = LoadBitmap(hInstance, "no");
      for(i = 0; i < 3; i++)
         SetMenuItemBitmaps(GetSubMenu(GetMenu(hwnd),0),
            i, MF_BYPOSITION, hSi, hNo);
...

Primero deberemos diseñar los mapas de bits del tamaño adecuado para ambas marcas, o al menos para la marca de check, ya que la correspondiente al ítem no marcado puede dejarse en blanco. Para conseguir el tamaño de los mapas de bits se puede usar la función GetMenuCheckMarkDimensions.

      wsprintf(mensaje, "%d %d",
         HIWORD(GetMenuCheckMarkDimensions()),
         LOWORD(GetMenuCheckMarkDimensions()));
      MessageBox(hwnd, mensaje, "Medidas de mapas de bits", MB_OK);

Items de mapas de bits

También se pueden usar mapas de bits en lugar de texto en los ítems de menú. Para hacerlo hay que crear o modificar los ítems en la ejecución del programa, ya que no es posible hacerlo al crear el fichero de recursos.

Items de mapas de bits
Items de mapas de bits

Como siempre, disponemos de dos formas de hacerlo, mediante las funciones AppendMenu, InsertMenu y ModifyMenu, o mediante InsertMenuItem y SetMenuItemInfo, y la estructura MENUITEMINFO.

Recordemos el proceso para crear un mapa de bits durante la ejecución:

  1. Usar la función CreateCompatibleDC para crear un contexto de dispositivo compatible con el usado por la ventana principal de la aplicación.
  2. Usar la función CreateCompatibleBitmap para crear un mapa de bits compatible con la ventana principal de la aplicación o user la función CreateBitmap para crear un mapa de bits monocromo.
  3. Usar la función SelectObject para seleccionar el mapa de bits en el contexto de dispositivo compatible.
  4. Usar las funciones de trazado del GDI, como Ellipse, LineTo o BitBlt, para generar una imagen dentro del mapa de bits.

En el primer caso, insertaremos o modificaremos los ítems indicando que son de tipo MF_BITMAP, e indicando como nuevo ítem un manipulador de mapa de bits.

   InsertMenu(hmenu, 0, MF_BYPOSITION | MF_BITMAP,
      CM_OPCION, (LPCTSTR)(hBitmap));

En el segundo caso, insertaremos o modificaremos los ítems de tipo MFT_BITMAP, e indicando un manipulador de mapa de bits.

   infoMenu.cbSize = sizeof(MENUITEMINFO);
   infoMenu.fMask = MIIM_TYPE | MIIM_ID;
   infoMenu.fType = MFT_BITMAP;
   infoMenu.wID = CM_OPCION;
   infoMenu.dwTypeData = (LPSTR) (hBitmap);
   InsertMenuItem(hmenu, 0, TRUE, &infoMenu);

Ejemplo 47

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 47 win047.zip 2004-07-23 28715 bytes 526