[ contenidos | #winprog ]

Menúes e Iconos

Ejemplo: menu_one

Esta es sólo una pequeña sección para mostrar como agregar menúes básicos en nuestra ventana. Generalmente utilizaremos un recurso menú pre-definido que estará en un archivo .rc y que luego será compilado y enlazado con el archivo .exe. Esto suele depender de cada compilador, los compiladores comerciales cuentan con un editor de recursos que puedes utilizar para crear tus menúes, pero en este ejemplo voy a mostrar el texto del archivo .rc para que puedas agregar menúes manualmente. Generalmente tengo un archivo .h que incluyo dentro de mis archivos .c y .rc .Este archivo contiene los identificadores para controles, items de un menú, etc...

En este ejemplo puedes comenzar con el código de la ventana que vimos en el ejemplo simple_window, e ir agregando el código de esta sección.

Primero el archivo .h, generalmente llamado "resource.h"

#define IDR_MYMENU 101
#define IDI_MYICON 201

#define ID_FILE_EXIT 9001
#define ID_STUFF_GO 9002

No hay demasiado aquí, pero nuestro menú será muy simple. Los nombres y valores puedes escogerlos arbitrariamente. Ahora escribamos nuesto archivo .rc

#include "resource.h"

IDR_MYMENU MENU
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "E&xit", ID_FILE_EXIT
    END

    POPUP "&Stuff"
    BEGIN
        MENUITEM "&Go", ID_STUFF_GO
        MENUITEM "G&o somewhere else", 0, GRAYED
    END
END

IDI_MYICON ICON "menu_one.ico"

Deberás agregar el archivo .rc a tu project o makefile dependiendo de que herramienta estás usando.

También debes agregar la linea #include "resource.h" en tu archivo fuente (.c) para que los identificadores de los comandos del menú y los del recurso menú estén definidos.

La forma mas fácil de asignar un menú y un ícono a nuestra ventana es especificar ambos cundo registramos la clase ventana, como esto:

    wc.lpszMenuName  = MAKEINTRESOURCE(IDR_MYMENU);
    wc.hIcon  = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON));
    wc.hIconSm  = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_MYICON), IMAGE_ICON, 16, 16, 0);

Cambia esto y observa que sucede. Tu ventana ahora debería tener un menú File y un menú Stuff, con los respectivos items dentro. Esto es asumiendo que nuestro archivo .rc fué apropiadamente compilado y enlazado en nuestro programa. (nuevamente mira las notas sobre compiladores)

He usado LoadIcon( ) para cargar el ícono grande debido a que es mas simple, sin embargo solo cargará los íconos en la resolución por default de 32x32, por lo tanto para cargar la imagen pequeña necesitamos usar LoadImage( ) .Observa que los archivos de íconos y de recursos pueden contener múltiples imágenes y en este caso, la única imagen que hay, contiene los dos tamaños que estamos cargando.

Ejemplo: menu_two

Una alternativa cuando usamos el recurso menú es crear uno en tiempo de ejecución. Esto lleva un poco mas de trabajo de programación, pero agrega mas flexibilidad y a veces es necesario.

También podemos usar íconos que no están almacenados como recursos, podríamos elegir almacenar nuestros íconos en un archivo separado y cargarlos en tiempo de ejecución. Esto también podría darnos la opción de permitir al usuario seleccionar un ícono con los dialogos comunes (common dialogs) que veremos luego, o alguna otra opción que tenga el mismo efecto.

Comenzaremos nuevamente con el ejemplo simple_window sin agregar el archivo .h y el archivo .rc. Ahora manejaremos el mensaje WM_CREATE y agregaremos un menú a nuestra ventana.

#define ID_FILE_EXIT 9001
#define ID_STUFF_GO 9002

Por esta vez pon estos dos ID's en el comienzo de tu archivo .c, debajo de los #include's. A continuación agregaremos el siguiente código dentro de nuestro manejador del mensaje WM_CREATE.

    case WM_CREATE:
    {
        HMENU hMenu, hSubMenu;
        HICON hIcon, hIconSm;

        hMenu = CreateMenu();

        hSubMenu = CreatePopupMenu();
        AppendMenu(hSubMenu, MF_STRING, ID_FILE_EXIT, "E&xit");
        AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&File");

        hSubMenu = CreatePopupMenu();
        AppendMenu(hSubMenu, MF_STRING, ID_STUFF_GO, "&Go");
        AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&Stuff");

        SetMenu(hwnd, hMenu);


        hIcon = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 32, 32, LR_LOADFROMFILE);
        if(hIcon)
            SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
        else
            MessageBox(hwnd, "Could not load large icon!", "Error", MB_OK | MB_ICONERROR);

        hIconSm = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
        if(hIconSm)
            SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm);
        else
            MessageBox(hwnd, "Could not load small icon!", "Error", MB_OK | MB_ICONERROR);
    }
    break;

Este crea un menú igual al que hemos definido en nuestro archivo de recursos y se lo asigna a nuestra ventana. Un menú que es asignado a una ventana es automáticamente removido cuando el programa termina, por lo tanto no necesitamos preocuparnos por librarnos de él cuando el programa finalice.

El código para los íconos es muy simple, llamamos a LoadImage( ) dos veces: primero para cargar el ícono de 16x16 y luego el ícono de 32x32. No podemos usar LoadIcon( ) en todo momento debido a que éste sólo carga recursos, no archivos. En el parámetro del handle a la instancia, especificamos el valor NULL debido a que no estamos cargando un recurso de nuestro módulo y en vez de pasar el ID de un recurso, pasamos el nombre del archivo donde se encuentra el ícono. Por último, pasamos el flag LR_LOADFROMFILE para indicar que la función trate el string que le pasamos como un nombre de archivo y no como el nombre de un recurso.

Si esto tiene éxito, asignamos el handle al icono utilizando WM_SETICON y si falla mostramos un mensaje diciéndonos que algo ha ocurrido.

NOTA: La llamada a LoadImage( ) fallará si el archivo con el ícono no se encuentra en el directorio de trabajo del programa. Si estas usando VC++ y ejecutas el programa desde el IDE, el directorio de trabajo será aquel donde se encuentre almacenado el proyecto. Sin embargo si ejecutas desde el Debug, desde el explorer o la linea de comando, entonces necesitarás copiar el archivo con los iconos dentro de dicho directorio para que el programa pueda encontrarlo. Si todo esto falla, especifica la ruta completa.

Ok, ahora que tenemos un menú necesitamos que haga algo. Esto es muy simple, todo lo que necesitmaos hacer es manejar el mensaje WM_COMMAND. Además necesitaremos chequear que comando estamos recibiendo para poder actuar acordemente. Ahora nuestro WinProc( ) debería lucir como este:

LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
    switch(Message)
    {
        case WM_CREATE:
        {
            HMENU hMenu, hSubMenu;

            hMenu = CreateMenu();

            hSubMenu = CreatePopupMenu();
            AppendMenu(hSubMenu, MF_STRING, ID_FILE_EXIT, "E&xit");
            AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&File");

            hSubMenu = CreatePopupMenu();
            AppendMenu(hSubMenu, MF_STRING, ID_STUFF_GO, "&Go");
            AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu, "&Stuff");

            SetMenu(hwnd, hMenu);


            hIcon = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 32, 32, LR_LOADFROMFILE);
            if(hIcon)
                SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
            else
                MessageBox(hwnd, "Could not load large icon!", "Error", MB_OK | MB_ICONERROR);

            hIconSm = LoadImage(NULL, "menu_two.ico", IMAGE_ICON, 16, 16, LR_LOADFROMFILE);
            if(hIconSm)
                SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm);
            else
                MessageBox(hwnd, "Could not load small icon!", "Error", MB_OK | MB_ICONERROR);
        }
        break;
        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case ID_FILE_EXIT:

                break;
                case ID_STUFF_GO:

                break;
            }
        break;
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, Message, wParam, lParam);
    }
    return 0;
}

Como podemos ver, hemos procesado el mensaje WM_COMMAND y éste tiene adentro otro switch( ). Este switch( ) distingue usando el valor de la palabra inferior de wparam: si es el caso de un mensaje WM_COMMAND, entonces contiene el ID del control o menú que envió el mensaje.

Obviamente queremos que el item Exit cierre el programa. Por lo tanto en el procesador del mensaje WM_COMMAND, ID_FILE_EXIT usaremos el siguiente código:

PostMessage(hwnd, WM_CLOSE, 0, 0);

El procesador del mensaje WM_COMMAND debería lucir como este:

    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
            case ID_FILE_EXIT:
                PostMessage(hwnd, WM_CLOSE, 0, 0);
            break;
            case ID_STUFF_GO:

            break;
        }
    break;

Te dejo como tarea realizar el otro comando ID_STUFF_GO para que haga algo.

El ícono del Programa

Habrás notado que ahora el archivo menu_one.exe muestra como ícono el ícono que agregamos como recurso, mientras que menu_two.exe no lo hace debido a que estamos cargando un archivo externo. El Windows Explorer simplemente muestra el primer icono (ordenados numericamente por ID) y debido a que sólo tenemos un ícono, este es el que será mostrado. Si quieres asegurarte de que un cierto ícono sea mostrado por el Windows Explorer simplemente agrega este como recurso y asígnale un numero bajo de ID... como 1. De aquí en más, en tu programa, ya no necesitas referirte al archivo que contiene al ícono y puedes asignarle a cada ventana un ícono diferente si lo deseas.
Copyright © 1998-2003, Brook Miles (theForger). All rights reserved.

Versión en español: Federico Pizarro - 2003.