Messaggi finestra (Introduzione a Win32 e C++)
Un'applicazione GUI deve rispondere agli eventi dell'utente e dal sistema operativo.
- Gli eventi dell'utente includono tutti i modi in cui un utente può interagire con il programma: clic del mouse, tratti di tasti, movimenti touch-screen e così via.
- Gli eventi del sistema operativo includono qualsiasi elemento "esterno" del programma che può influire sul comportamento del programma. Ad esempio, l'utente potrebbe collegare un nuovo dispositivo hardware o Windows potrebbe entrare in uno stato di alimentazione inferiore (sospensione o ibernazione).
Questi eventi possono verificarsi in qualsiasi momento mentre il programma è in esecuzione, in quasi qualsiasi ordine. Come si struttura un programma il cui flusso di esecuzione non può essere stimato in anticipo?
Per risolvere questo problema, Windows usa un modello di passaggio di messaggi. Il sistema operativo comunica con la finestra dell'applicazione passando messaggi. Un messaggio è semplicemente un codice numerico che definisce un determinato evento. Ad esempio, se l'utente preme il pulsante sinistro del mouse, la finestra riceve un messaggio con il codice del messaggio seguente.
#define WM_LBUTTONDOWN 0x0201
Alcuni messaggi contengono dati associati. Ad esempio, il messaggio WM_LBUTTONDOWN include la coordinata x e la coordinata y del cursore del mouse.
Per passare un messaggio a una finestra, il sistema operativo chiama la routine della finestra registrata per tale finestra. (E ora si sa cosa è la procedura finestra per.)
Ciclo del messaggio
Un'applicazione riceverà migliaia di messaggi durante l'esecuzione. Si consideri che ogni sequenza di tasti e clic del pulsante del mouse genera un messaggio. Inoltre, un'applicazione può avere diverse finestre, ognuna con una propria routine finestra. In che modo il programma riceve tutti questi messaggi e li recapita alla procedura di finestra corretta? L'applicazione richiede un ciclo per recuperare i messaggi e inviarli alle finestre corrette.
Per ogni thread che crea una finestra, il sistema operativo crea una coda per i messaggi della finestra. Questa coda contiene messaggi per tutte le finestre create in tale thread. La coda stessa è nascosta dal programma. Non è possibile modificare direttamente la coda. Tuttavia, è possibile eseguire il pull di un messaggio dalla coda chiamando la funzione GetMessage.
MSG msg;
GetMessage(&msg, NULL, 0, 0);
Questa funzione rimuove il primo messaggio dall'inizio della coda. Se la coda è vuota, la funzione si blocca fino a quando non viene accodato un altro messaggio. Il fatto che i blocchi GetMessage non rispondano al programma. Se non sono presenti messaggi, non c'è nulla che il programma faccia. Se è necessario eseguire l'elaborazione in background, è possibile creare thread aggiuntivi che continuano a essere eseguiti mentre GetMessage attende un altro messaggio. (Vedere Evitare colli di bottiglia nella procedura finestra.
Il primo parametro di GetMessage è l'indirizzo di una struttura MSG. Se la funzione ha esito positivo, compila la struttura MSG con informazioni sul messaggio. Sono incluse la finestra di destinazione e il codice del messaggio. Gli altri tre parametri consentono di filtrare i messaggi ricevuti dalla coda. In quasi tutti i casi, questi parametri verranno impostati su zero.
Anche se la struttura MSG contiene informazioni sul messaggio, quasi mai si esaminerà direttamente questa struttura. Verrà invece passato direttamente a due altre funzioni.
TranslateMessage(&msg);
DispatchMessage(&msg);
La funzione TranslateMessage è correlata all'input da tastiera. Converte le sequenze di tasti (tasto giù, tasto su) in caratteri. Non è necessario sapere come funziona questa funzione; ricordarsi di chiamarlo prima di DispatchMessage.
La funzione DispatchMessage indica al sistema operativo di chiamare la routine della finestra della finestra che rappresenta la destinazione del messaggio. In altre parole, il sistema operativo cerca l'handle di finestra nella relativa tabella di finestre, trova il puntatore di funzione associato alla finestra e richiama la funzione.
Si supponga, ad esempio, che l'utente preme il pulsante sinistro del mouse. In questo modo si verifica una catena di eventi:
- Il sistema operativo inserisce un messaggio WM_LBUTTONDOWN nella coda dei messaggi.
- Il programma chiama la funzione GetMessage.
- GetMessage esegue il pull del messaggio WM_LBUTTONDOWN dalla coda e compila la struttura MSG.
- Il programma chiama le funzioni TranslateMessage e DispatchMessage.
- All'interno di DispatchMessage, il sistema operativo chiama la procedura della finestra.
- La procedura della finestra può rispondere al messaggio o ignorarla.
Quando la routine della finestra viene restituita, torna a DispatchMessage. Viene restituito al ciclo di messaggi per il messaggio successivo. Se il programma è in esecuzione, i messaggi continueranno ad arrivare nella coda. Pertanto, è necessario disporre di un ciclo che estrae continuamente i messaggi dalla coda e li invia. È possibile considerare il ciclo come segue:
// WARNING: Don't actually write your loop this way.
while (1)
{
GetMessage(&msg, NULL, 0, 0);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Come scritto, naturalmente, questo ciclo non finirebbe mai. È qui che entra in gioco il valore restituito per la funzione GetMessage . In genere, GetMessage restituisce un valore diverso da zero. Quando si vuole uscire dall'applicazione e uscire dal ciclo di messaggi, chiamare la funzione PostQuitMessage.
PostQuitMessage(0);
La funzione PostQuitMessage inserisce un messaggio di WM_QUIT nella coda dei messaggi. WM_QUIT è un messaggio speciale: fa sì che GetMessage restituisca zero, segnalando la fine del ciclo di messaggi. Di seguito è riportato il ciclo di messaggi modificato.
// Correct.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Se GetMessage restituisce un valore diverso da zero, l'espressione nel ciclo while restituisce true. Dopo aver chiamato PostQuitMessage, l'espressione diventa false e il programma si interrompe dal ciclo. (Un risultato interessante di questo comportamento è che la procedura della finestra non riceve mai un WM_QUIT messaggio. Pertanto, non è necessario disporre di un'istruzione case per questo messaggio nella procedura della finestra.
La domanda ovvia successiva è quando chiamare PostQuitMessage. Si tornerà a questa domanda nell'argomento Chiusura della finestra, ma prima di tutto è necessario scrivere la procedura della finestra.
Messaggi pubblicati e messaggi inviati
La sezione precedente ha parlato dei messaggi che passano in una coda. In alcuni casi, il sistema operativo chiamerà direttamente una routine di finestra, ignorando la coda.
La terminologia di questa distinzione può generare confusione:
- La pubblicazione di un messaggio indica che il messaggio passa alla coda dei messaggi e viene inviato tramite il ciclo di messaggi (GetMessage e DispatchMessage).
- L'invio di un messaggio indica che il messaggio ignora la coda e il sistema operativo chiama direttamente la routine della finestra.
Per il momento, la differenza non è molto importante. La procedura della finestra gestisce tutti i messaggi. Tuttavia, alcuni messaggi ignorano la coda e passano direttamente alla procedura della finestra. Tuttavia, può fare la differenza se l'applicazione comunica tra finestre. È possibile trovare una discussione più approfondita di questo problema nell'argomento Informazioni su messaggi e code di messaggi.