Esempio: window_click

Bene, abbiamo creato una finestra, che però non fa niente eccetto quello che DefWindowProc() permette di fare, cioè ridimensionare, ingrandire, etc… Niente di veramente eccitante.

Nella prossima sezione vi mostrerò come modificare quello che abbiamo appena creato per fare qualcosa di nuovo. Ovvero vi posso dire: in questo modo potete “Processare questo messaggio, e fare questo con esso…“ e voi saprete esattamente cosa intendo senza vedere l’intero esempio. Per lo meno spero sia così, quindi fate attenzione :P

Ok, per i newbye, bisogna prendere il codice di esempio della finestra appena creata, assicurarsi che compila e gira come ci si aspetta. Dopodichè si può aggiustare con il codice seguente o copiare il tutto in un nuovo progetto e modificarlo.

Stiamo per aggiungere la funzionalità di mostrare all’utente qual’è il nome del nostro programma quando fa click sulla nostra finestra. Non esattamente eccitante, ma serve fondamentalmente per comprendere meglio il metodo per processare i messaggi. Vediamo cosa abbiamo nella nostra WndProc():

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

Se vogliamo processare il click del mouse, ci tocca aggiungere l’opzione WM_LBUTTONDOWN (oppure WM_RBUTTONDOWN, WM_MBUTTONDOWN, rispettivamente per il click con il tasto destro o centrale).

Se io o qualcun’altro fa riferimento a Processare un messaggio si riferisce all’aggiungere codice dentro alla WndProc() della vostra finestra come segue:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   switch(msg)
   {
      case WM_LBUTTONDOWN:    // <-
                              // <-     codice aggiunto
      break;                  // <-
      case WM_CLOSE:
         DestroyWindow(hwnd);
      break;
      case WM_DESTROY:
         PostQuitMessage(0);
      break;
      default:
         return DefWindowProc(hwnd, msg, wParam, lParam);
   }
   return 0;
}

l’ordine con cui vengono processati i messaggi generalmente non è rilevante. L’unica cosa importante è assicurarsi di aver posizionato il break; dopo ogni messaggio. Come potete vedere abbiamo semplicemente aggiunto un altro case nel nostro switch(). Ora vogliamo che accada qualcosa quando questa parte dello switch viene processata nel nostro programma.

Innanzitutto vi presenterò il codice da aggiungere (che mostrerà all’utente il nome del file del nostro programma) in modo da integrarlo nel nostro programma. In seguito probabilmente vi mostrerò solo il codice lasciare che siate voi ad integrarlo all’interno del programma. Questo ovviamente è la strada più comoda da seguire per me perchè non devo scrivere così tanto e la migliore per voi perchè sarete portati ad esercitarvi ed essere pronti ad aggiungere il codice in QUALSIASI programma e non solamente in quello presentato in questo tutorial. Se non siete sicuri su come fare tutto ciò date uno sguardo al file .zip allegato a questa sezione del tutorial.

GetModuleFileName(hInstance, szFileName, MAX_PATH);
MessageBox(hwnd, szFileName, "Questo programma è:",
                 MB_OK | MB_ICONINFORMATION);

Queste istruzioni non girano da sole, dobbiamo inserirle nel nostro codice. In particolare vogliamo che vengano eseguite quando l’utente fa click con il mouse sulla nostra finestra. Otteniamo l’effetto in questo modo:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_LBUTTONDOWN:
// BEGIN NEW CODE
        {
            char szFileName[MAX_PATH];
            HINSTANCE hInstance = GetModuleHandle(NULL);

            GetModuleFileName(hInstance, szFileName, MAX_PATH);
            MessageBox(hwnd, szFileName, "Questo programma è:",
                              MB_OK | MB_ICONINFORMATION);
        }
// END NEW CODE
        break;
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

Notate la nuova coppia di parentesi graffe {}. Sono obbligarie quando dichiariamo nuove variabili all’interno del blocco all’interno dell’istruzione switch(). Questa dovrebbe essere conoscenza base del C che penso dobbiate avere, ma è meglio ricordarlo per evitare di rendere le cose ancora più complicate.

Se avete aggiunto il codice, ora potete compilarlo. Se tutto va bene, cliccate sulla finestra e dovreste ottenere un messaggio contenente il nome del file .exe che state facendo girare.

Avrete notato che abbiamo aggiunto due variabili, hInstance e szFileName. Guardate GetModuleFileName() e vi renderete conto che il primo parametro è un HINSTANCE che fa riferimento al modulo dell’eseguibile (il nostro programma, il file .exe). Come otteniamo una cosa simile? GetModuleHandle() è la risposta. Una rapida occhiata alle referenze (guida) di GetModuleHandle() ci indica che passando NULL ci verrà ritornato “un handle al file usato per creare il processo chiamante”, che è esattamente quello che ci serve, ossia l’ HINSTANCE appena mensionato. Unendo le informazioni otteniamo la seguente dichiarazione:

HINSTANCE hInstance = GetModuleHandle(NULL);

Il secondo parametro, prendendo sempre il nostro manuale delle referenze preferito, vediamo che è “_ un puntatore ad un buffer che riceve il percorso ed il nome del file del modulo specificato_” e il tipo di dati è LPTSTR (oppure LPSTR se le vostre referenze sono datate). Siccome LPSTR è equivalente a char* possiamo dichiarare un array di char come questo:

char szFileName[MAX_PATH];

MAX_PATH è una macro inclusa in windows.h che è definita come la massima lunghezza necessaria del buffer per conservare il nome di un file sotto Win32. Passiamo anche MAX_PATH a GetModuleFileName() in modo che la funzione conosca la grandezza del buffer.

Dopo la chiamata a GetModuleFileName(), il buffer szFileName sarà riempito con una stringa, terminata con un carattere NULL, contenente il nome del nostro file .exe. Passiamo questo valore a MessageBox() in modo da visualizzare in modo semplice il risultato.

Bene, se avete compilato il codice, testate il risultato e dovrebbe fare esattamente cosa ci aspettiamo. Se non ha funzionato, questo è il codice completo del programma. Confrontatelo con il vostro e rendetevi conto degli errori fatti.

#include <windows.h>

const char g_szClassName[] = "myWindowClass";

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_LBUTTONDOWN:
        {
            char szFileName[MAX_PATH];
            HINSTANCE hInstance = GetModuleHandle(NULL);

            GetModuleFileName(hInstance, szFileName, MAX_PATH);
            MessageBox(hwnd, szFileName, "This program is:",
                              MB_OK | MB_ICONINFORMATION);
        }
        break;
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Registrazione della Finestra Fallita!", "Errore!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "Titolo della mia finestra",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Creazione della Finestra Fallita!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}