Esempio: menu_one

Presentiamo ora una piccola sezione al solo scopo di mostrare come aggiungere semplici menu alle finestre. Normalmente userete un menu “preconfezionato” in una risorsa. Ossia, il menu sarà presente all’interno del file .rc e verrà compilato all’interno dell’esegui bile .exe. La “costruzione” del menu è specifica per il compilatore in uso, i compilatori commerciali dispongono come già detto di un editor grafico per le risorse che userete per creare i vostri menu. Per questo esempio però vi sarà mostrato il codice testuale da inserire all’interno del file .rc in modo da poterlo aggiungere manualmente. Normalmente si usa un file .h incluso sia nel file .rc che nei file sorgente .c. Questo file contiene gli identificatori per i controlli, le voci di menu etc.

Per questo esempio potete iniziare con il codice preso da simple_window e aggiungere il codice seguente come da istruzioni.

Per prima cosa il file .h. Chiamato generalmente “resource.h”

#define IDR_MYMENU 101
#define IDI_MYICON 201

#define ID_FILE_EXIT 9001
#define ID_STUFF_GO 9002

Non abbiamo inserito niente di particolare ma il nostro menu sarà molto semplice. I nomi e i valori qui inseriti sono a vostra discrezione. Ora scriviamo il file .rc.

#include "resource.h"

IDR_MYMENU MENUBEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "E&sci", ID_FILE_EXIT
    END
    POPUP "&Processa"
    BEGIN
        MENUITEM "&Vai", ID_STUFF_GO
        MENUITEM "V&ai da qualche altra parte", 0, GRAYED
    END
END

IDI_MYICON ICON "menu_one.ico"

Probabilmente dovrete aggiungere il file .rc al vostro progetto a seconda dell’ambiente che usate.

Dovrete inoltre aggiungere #include "resource.h" all’interno dei files sorgente (.c) in modo da definire gli identificatori di comando e della risorsa menu.

Il modo più semplice per inserire il menu e l’icona alla finestra è specificarli quando viene registrata la window class, in questo modo:

    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);

Effettuate le modifiche e vedete cosa succede. La nostra finestra ora dovrebbe avere i menu File e Processa, con le rispettive voci.

Inoltre l’icona in alto a sinistra della finestra e la task bar di windows dovrebbero visualizzare l’icona personalizzata che abbiamo specificato all’interno del file .rc. Se premete Alt-Tab, dovrebbe venir visualizzata la versione grande dell’icona.

Questo assumendo che il vostro file .rc sia stato configurato correttamente (dinuovo: visualizzate il manuale del compilatore in uso).

Io ho usato LoadIcon() per caricare l’icona grande perchè è più semplice, in ogni caso esso caricherà solamente l’icona alla risoluzione predefinita di 32x32, quindi per caricare l’icona piccola, abbiamo bisogno di usare LoadImage(). Fate attenzione perchè un file di icona (.ico) può contenere multiple immagini, e in questo caso quelle da me specificate contengono entrambe le immagini per le due grandezze che sto caricando.

Esempio: menu_two

Un’alternativo all’uso delle risorse è di creare un menu “al volo” (ossia quando gira il programma). Questo richiede un pò di codice in più ma aggiunge parecchia flessibilità che talvolta è necessaria.

Potete anche utilizzare icone che non sono inserite all’interno delle risorse, potete scegliere di lasciare l’icona in un file separato e caricarla nel momento dell’esecuzione del programma, in questo modo potete dare la possibilità all’utente di scegliere un’icona di propria scelta.

Cominciamo dinuovo da simple_window senza il file .h o .rc aggiunti. Ora andremo a processare il messaggio WM_CREATE ed aggiungere un menu alla nostra finestra.

#define ID_FILE_EXIT 9001
#define ID_STUFF_GO 9002

Inserite questi due id all’inizio del file .c appena sotto gli #include. Dopodichè aggiungete questo codice all’interno di WM_CREATE.

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

        hMenu = CreateMenu();

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

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

        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, "Impossibile caricare l'icona grande!",
                       "Errore", 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, "Impossibile caricare l'icona piccola!",
                       "Errore", MB_OK | MB_ICONERROR);
    }
    break;

Questo codice crea esattamente lo stesso effetto ottenuto con le risorse. Un menu viene assegnato ad una finestra viene rimosso automaticamente quando il programma termina, quindi non dovete preoccuparvi più di tornare a farci delle modifiche se non è esplicitamente richiesto dalle vostre esigenze. Se proprio vogliamo occuparci di distruggere il menu possiamo usare GetMenu() e DestroyMenu().

Il codice per le icone è semplice, chiamiamo LoadImage() due volte per caricare entrambe le icone sia a grandezza 16x16 che a grandezza 32x32. Non possiamo usare assolutamente LoadIcon() perchè questo carica le risorse, non i files. Specifichiamo NULL per l’istanza perchè stiamo caricando il menu dal nostro modulo e invece di un ID per la risorsa passiamo il nome del file che vogliamo caricare. Infine passiamo il parametro LR_LOADFROMFILE per indicare alla funzione di trattare la stringa come nome di un file e non come nome di risorsa.

Se le chiamate vanno a buon fine, assegniamo l’handle per l’icona alla nostra finestra con WM_SETICON, e se questo non va a buon fine visualizziamo un messaggio che avverte l’utente che qualcosa è andata male.

NOTA: le chiamate a LoadImage() falliranno se il file dell’icona che andiamo a caricare non è presente all’interno della directory di lavoro del programma. Se state usando VC++ e state facendo girare il programma dall’IDE di sviluppo, la directory di lavoro sarà la stessa dove si trova il file progetto. Allo stesso modo se fate girare il programma da explorer all’interno delle directory Debug oppure Release avrete bisogno di copiare il file .ico all’interno di queste directory. Se questo non funziona provate a specificare il percorso completo del file sul disco es. "C:\\Path\\To\\Icon.ico".

Ok, ora che abbiamo il nostro menu, gli facciamo fare qualcosa. È molto semplice, tutto quello che dobbiamo fare è processare il messaggio WM_COMMAND. Abbiamo inoltre bisogno di controllare quale comando stiamo ricevendo e agire di conseguenza. Ora la nostra WndProc() dovrebbe somigliare a qualcosa del tipo:

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&sci");
            AppendMenu(hMenu, MF_STRING | MF_POPUP, (UINT)hSubMenu,
                       "&File");

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

            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, "Impossibile caricare l'icona grande!",
                           "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, "Impossibile caricare l'icona piccola!",
                           "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;
}

Come potete vedere abbiamo inserito e configurato il nostro WM_COMMAND; esso contiene nel suo interno un nuovo switch(). La funzione di questo nuovo switch() è di controllare la parte bassa di wParam, che nel caso di WM_COMMAND contiene l’ID del controllo o del menu che ha inviato il messaggio.

Ovviamente vogliamo che la voce di menu “Esci” ci faccia uscire dal programma.

Così all’interno di WM_COMMAND, ID_FILE_EXIT possiamo usare il codice seguente per fare proprio quello che desideriamo.

PostMessage(hwnd, WM_CLOSE, 0, 0);

Ora l’handler per WM_COMMAND dovrebbe somigliare a qualcosa del genere:

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

            break;
        }
    break;

Vi lascio come esercizio far processare l’altro comando menu ID_STUFF_GO per fare qualcosa.

L’icona del programma

Avrete notato che il file menu_one.exe ora viene visualizzato con l’icona che abbiamo aggiunto all’interno del file di risorse, mentre il file menu_two.exe no; questo perchè noi carichiamo un file esterno. Windows Explorer visualizza semplicemente la prima icona (numericamente per ID) presente all’interno delle risorse del programma, quindi finchè avremo solo un’icona essa sarà quella visualizzata, se invece inserite più icone all’interno del file risorse assicuratevi che l’ID di quella che volete mostrare come icona del programma sia il più basso di tutte. Per non sbagliare assegnate ad essa un ID molto basso, tipo 1. Non dovete far nessun riferimento al file all’interno del vostro programma e potete caricare icone completamente differenti per ognuna delle vostre finestre.