# DirectX/Message loop

DirectX
Copertina Tutti i moduli · Sviluppo
    CopertinaDirectX/Copertina
* Ripasso di matematicaDirectX/Ripasso di matematica
** Calcolo vettorialeDirectX/Calcolo vettoriale
** Calcolo matricialeDirectX/Calcolo matriciale
** TrasformazioniDirectX/Trasformazioni
* Creare il progetto con VS2010DirectX/Creare il progetto con VS2010
* InizializzazioneDirectX/Inizializzazione
** Win32
*** Creare la finestraDirectX/Creare la finestra
*** Terminale di debugDirectX/Terminale di debug
*** Message loopDirectX/Message loop
*** Altre funzioni Win32DirectX/Altre funzioni Win32
Modifica il sommario
Nel modulo precedente abbiamo visto come creare una finestra. Il problema è che quella finestra è praticamente inutilizzabile. In questo modulo scopriremo come rendere questa finestra viva e come farla interagire con il resto del mondo.

## Messaggi

Ogni volta che un evento deve essere segnalato ad una finestra, Windows crea un messaggio per la finestra e lo inserisce in un buffer. L'applicazione ha il compito di prelevare i messaggi l'uno dopo l'altro e di processarli, eseguendo ogni compito che ritiene necessario. Se questi messaggi non vengono interpretati, la finestra non subisce eventi di alcun tipo (creazione, distruzione, mouse, tastiera, ...) e resta un bel rettangolo grigio. Normalmente il codice da eseguire per la gestione dei messaggi è il seguente:
Questo codice andrebbe eseguito subito dopo lo ShowWindow del modulo precedente. GetMessage è una API che legge l'ultimo messaggio dal buffer e lo inserisce nella variabile puntata come primo argomento. Il secondo argomento è l'HANDLE della finestra da cui vogliamo ricevere i messaggi. Mettendolo a zero riceveremo anche i messaggi di altre finestre (create sempre dallo stesso processo ovviamente), nel caso in cui pensaste di crearne più di una. Gli ultimi due argomenti settati a zero, sono il codice minimo e massimo dei messaggi da ricevere. Per esempio ci potrebbe piacere leggere solo i messaggi con il tipo da 0x200 a 0x20E, ossia solo quelli legati strettamente alla gestione del mouse. Tutti gli altri verrebbero ignorati. Quando impostati a zero, non viene imposto alcun limite. Tutti i messaggi vengono interpretati. GetMessage ritorna 0 se il messaggio in arrivo è WM_QUIT, ossia il messaggio che chiede la chiusura del programma, e ritorna non-zero per ogni altro messaggio.
GetMessage, nonostante funzioni a dovere in normali applicazioni, purtroppo presenta una grave lacuna per un videogioco. Infatti nel caso in cui non ci fossero messaggi nel buffer, il processo viene messo in pausa fino all'arrivo di un nuovo messaggio. Questo per un videogame è inaccettabile, infatti le scene verrebbero mostrate solo in presenza di un messaggio. Per superare questo impedimento in applicazioni grafiche viene utilizzato PeekMessage. Questa funzione cerca un messaggio nel buffer e ritorna non-zero se un messaggio viene trovato e deve essere interpretato, e zero se non ci sono messaggi da leggere. In questo caso noi mostreremo una scena disegnata in DirectX.
Se PeekMessage ritorna un valore diverso da zero, dobbiamo a questo punto gestire il messaggio. Questo normalmente si traduce con la chiamata di due funzioni:
TranslateMessage è una comoda funzione che trasforma alcuni codici in altri a noi più comprensibili. Ad esempio può tradurre lo Scancode di un tasto, nel suo corrispondente nella tabella ASCII. DispatchMessage invece chiamerà a sua volta una certa funzione responsabile di gestire i singoli messaggi. Quale funzione? Una creata da noi così:
Questa funzione il cui nome non è rilevante, prende quattro argomenti:
* L'HANDLE della finestra a cui era destinato il messaggio.
* Un codice che identifica il messaggio stesso.
* Due argomenti che possono anche non essere usati (ad esempio per il movimento del mouse potrebbero contenere le nuove coordinate x ed y del cursore)
La funzione viene chiamata da DispatchMessage per ogni messaggio ricevuto. Il valore di ritorno dipende dal tipo di messaggio. Generalmente ritornare zero vuol dire che il messaggio è stato correttamente elaborato (non vale per il 100% dei messaggi).

## Associare l'handler alla finestra

Come chiediamo però a Windows di chiamare proprio questa funzione con DispatchMessage? Il puntatore a questa funzione deve essere inserito nel campo lpfnWndProc di WNDCLASSEX al momento della creazione. Questo era l'ultimo elemento della struttura che rimasto in attesa di spiegazioni ed ora WNDCLASSEX non ha più segreti per voi.

## Facciamo chiudere la finestra

A questo punto la finestra dovrebbe essere ben visibile, ma non avrete alcuna speranza di riuscire a chiuderla. Infatti il click sulla X rossa, o Alt-F4 inviano un messaggio di tipo WM_DESTROY alla finestra, che fino ad ora noi avevamo completamente ignorato. Vediamo come chiudere la finestra:
Ci sono qui due nuove funzioni. PostQuitMessage inserirà nel buffer dei messaggi un messaggio di tipo WM_QUIT. Quando il nostro Message Loop riceverà un WM_QUIT, il loop si interromperà facendo terminare il programma. L'argomento è il parametro da ritornare da WinMain.
DefWindowProc è invece una richiesta che facciamo a Windows di gestire il messaggio. In altre parole, non avendo alcun interesse nel gestire tutti i messaggi esistenti, chiediamo a Windows di gestire in modo standard tutti quelli che noi scartiamo.

## Altri messaggi da conoscere

Molti di questi messaggi non li useremo nel nostro libro, ma sono sicuramente utili per un buon videogioco. Evidenzierò in grassetto quelli su cui dovremo lavorare più approfonditamente. Per gli altri si può vedere la libreria MSDN.
| WM_ACTIVATE | Questo messaggio è inviato alla finestra quando viene selezionata (attivata). Per attivare una finestra è sufficiente cliccarvici su, o selezionarla dalla taskbar dopo averla ridotta ad icona ad esempio. Lo stesso messaggio viene inviato anche quando la finestra viene disattivata. E' possibile riconoscere le due situazioni interpretando il valore in wParam. |
| WM_KEYDOWN | Viene inviato quando viene schiacciato un tasto sulla tastiera. Viene inviato più volte se il tasto resta premuto. Il codice del tasto lo si può trovare in wParam, mentre in lParam si trovano alcune flags (conteggio di ripetizione, scancode OEM, ...) |
| WM_KEYUP | Viene inviato quando viene rilasciato un tasto dalla tastiera. Il formato è uguale a WM_KEYDOWN |
| WM_CHAR | Questo messaggio viene creato da TranslateMessage. Viene generato immediatamente dopo un WM_KEYDOWN. Questa volta in wParam sarà contenuto il codice ASCII del carattere da rappresentare. Generalmente si sceglierà se gestire WM_KEYDOWN o WM_CHAR, ignorando l'altro. |
| WM_LBUTTONDOWN | Segnala lo schiacciamento del tasto sinistro del mouse. In wParam sono contenute delle flag che indicano lo stato di altri tasti, e dei tasti SHIFT, ALT, CTRL della tastiera. In lParam invece troviamo le coordinate del mouse. |
| WM_LBUTTONUP | Pulsante sinistro rilasciato. Stesso formato di WM_LBUTTONDOWN |
| WM_MBUTTONDOWN, WM_RBUTTONDOWN | Schiacciamento del pulsante centrale e destro del mouse. |
| WM_MBUTTONUP, WM_RBUTTONUP | Rilascio del pulsante centrale e destro del mouse. |
| WM_MOUSEWHEEL | Indica l'uso della rotellina di scroll. Nella high word di wParam troviamo un valore (delta) che indica una quantità di spostamento. Nella low word altre flags, e in lParam le coordinate del mouse. |
| WM_MOUSEHWHEEL | Usato per segnalare l'uso della rotellina orizzontale. Stesso formato di WM_MOUSEWHEEL. |
| WM_MOUSEMOVE | Viene inviato ogni volta che il mouse compie un movimento sulla finestra. Il formato è lo stesso di WM_LBUTTONDOWN. |
| WM_MOUSEHOVER | Il mouse entra nella finestra. |
| WM_MOUSELEAVE | Il mouse esce dalla finestra. lParam e wParam non sono usati. |
| WM_CREATE | Viene inviato subito dopo la chiamata a CreateWindow, prima che la finestra sia visibile. |
| WM_SIZE | Viene inviato ogni volta che la finestra subisce un ridimensionamento. Se trascinate le ancore ai lati della finestra, verrà inviato un WM_SIZE per ogni pixel di spostamento. |
| WM_MOVE | Per ogni pixel di spostamento della finestra nello schermo, viene inviato un messaggio WM_MOVE. |
| WM_ENTERSIZEMOVE | Inviato quando l'utente clicca sulle ancore di ridimensionamento ai lati. |
| WM_EXITSIZEMOVE | L'utente ha rilasciato le ancore di ridimensionamento. |

## Esempi

Proviamo a gestire l'utilizzo della tastiera. Utilizzando il messaggio WM_CHAR, possiamo sapere quando l'utente avrà premuto un tasto. WM_CHAR invia in wParam il codice UTF-16 del carattere Aggiungiamo quindi allo switch:
Ogni volta che la finestra sarà in primo piano ed un tasto verrà premuto, sarà visualizzato sul terminale. Se non avete ancora aperto il terminale seguite le istruzioni nel modulo Terminale di debug.

## Sorgente per questo modulo

A questo punto il codice del nostro programma dovrebbe assomigliare a qualcosa di simile: