Capítulo 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
D Ejemplo 42 win042.zip 2004-07-22 3691 bytes 372

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
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
D Ejemplo 43 win043.zip 2004-07-22 3650 bytes 333