Vensterberichten (Aan de slag met Win32 en C++)
Een GUI-toepassing moet reageren op gebeurtenissen van de gebruiker en van het besturingssysteem.
- Gebeurtenissen van de gebruiker omvatten alle manieren waarop iemand met uw programma kan communiceren: muisklikken, toetsaanslagen, aanraakbewegingen en dergelijke.
- Gebeurtenissen van het besturingssysteem omvatten alles wat 'buiten' het programma ligt en van invloed kan zijn op hoe het programma zich gedraagt. De gebruiker kan bijvoorbeeld een nieuw hardwareapparaat aansluiten of Windows kan een lagere energiestatus (slaapstand of sluimerstand) invoeren.
Deze gebeurtenissen kunnen zich op elk gewenst moment voordoen terwijl het programma wordt uitgevoerd, in vrijwel elke volgorde. Hoe structureert u een programma waarvan de uitvoeringsstroom niet vooraf kan worden voorspeld?
Windows gebruikt een berichtdoorgiftemodel om dit probleem op te lossen. Het besturingssysteem communiceert met uw toepassingsvenster door er berichten aan door te geven. Een bericht is gewoon een numerieke code die een bepaalde gebeurtenis aanwijst. Als de gebruiker bijvoorbeeld op de linkermuisknop drukt, ontvangt het venster een bericht met de volgende berichtcode.
#define WM_LBUTTONDOWN 0x0201
Aan sommige berichten zijn gegevens gekoppeld. Het WM_LBUTTONDOWN bericht bevat bijvoorbeeld de x-coördinaat en de y-coördinaat van de muiscursor.
Als u een bericht wilt doorgeven aan een venster, roept het besturingssysteem de vensterprocedure aan die is geregistreerd voor dat venster. (En nu weet u waar de vensterprocedure voor dient.)
De berichtenlus
Een toepassing ontvangt duizenden berichten terwijl deze wordt uitgevoerd. (Houd er rekening mee dat elke toetsaanslag en muisknop op een bericht wordt gegenereerd.) Daarnaast kan een toepassing meerdere vensters hebben, elk met een eigen vensterprocedure. Hoe ontvangt het programma al deze berichten en levert deze aan de juiste vensterprocedure? De toepassing heeft een lus nodig om de berichten op te halen en naar de juiste vensters te verzenden.
Voor elke thread die een venster maakt, maakt het besturingssysteem een wachtrij voor vensterberichten. Deze wachtrij bevat berichten voor alle vensters die op die thread zijn gemaakt. De wachtrij zelf is verborgen voor uw programma. U kunt de wachtrij niet rechtstreeks bewerken. U kunt echter een bericht uit de wachtrij halen door de functie GetMessage aan te roepen.
MSG msg;
GetMessage(&msg, NULL, 0, 0);
Met deze functie wordt het eerste bericht uit de kop van de wachtrij verwijderd. Als de wachtrij leeg is, wordt de functie geblokkeerd totdat een ander bericht in de wachtrij staat. Het feit dat GetMessage blokken uw programma niet onresponsief maken. Als er geen berichten zijn, hoeft het programma niets te doen. Als u achtergrondverwerking moet uitvoeren, kunt u extra threads maken die blijven worden uitgevoerd terwijl GetMessage- wacht op een ander bericht. (Zie Het vermijden van knelpunten in uw vensterprocedure.)
De eerste parameter van GetMessage- is het adres van een MSG- structuur. Als de functie slaagt, wordt de MSG structuur ingevuld met informatie over het bericht. Dit omvat het doelvenster en de berichtcode. Met de andere drie parameters kunt u filteren welke berichten u uit de wachtrij krijgt. In bijna alle gevallen stelt u deze parameters in op nul.
Hoewel de MSG structuur informatie over het bericht bevat, zult u deze structuur bijna nooit rechtstreeks onderzoeken. In plaats daarvan geeft u deze rechtstreeks door aan twee andere functies.
TranslateMessage(&msg);
DispatchMessage(&msg);
De functie TranslateMessage is gerelateerd aan toetsenbordinvoer. Hiermee worden toetsaanslagen (toets omlaag, toets omhoog) omgezet in tekens. U hoeft niet echt te weten hoe deze functie werkt; onthoud om deze aan te roepen voordat DispatchMessage.
De functie DispatchMessage laat het besturingssysteem de vensterprocedure aanroepen van het venster dat het doel van het bericht is. Met andere woorden, het besturingssysteem zoekt de venstergreep in de tabel met vensters, zoekt de functie aanwijzer die aan het venster is gekoppeld en roept de functie aan.
Stel dat de gebruiker op de linkermuisknop drukt. Dit veroorzaakt een keten van gebeurtenissen:
- Het besturingssysteem plaatst een WM_LBUTTONDOWN bericht in de berichtenwachtrij.
- Uw programma roept de functie GetMessage aan.
- GetMessage haalt het WM_LBUTTONDOWN bericht op uit de wachtrij en vult de MSG structuur in.
- Uw programma roept de functies TranslateMessage en DispatchMessage- aan.
- Binnen DispatchMessageroept het besturingssysteem uw vensterprocedure aan.
- Uw vensterprocedure kan op het bericht reageren of negeren.
Wanneer de vensterprocedure wordt geretourneerd, keert deze terug naar DispatchMessage-. Dit keert terug naar de berichtenlus voor het volgende bericht. Zolang uw programma wordt uitgevoerd, blijven berichten binnenkomen in de wachtrij. Daarom moet u een lus hebben die voortdurend berichten uit de wachtrij haalt en verzendt. U kunt de loop als volgt beschouwen:
// WARNING: Don't actually write your loop this way.
while (1)
{
GetMessage(&msg, NULL, 0, 0);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Zoals geschreven, zou deze lus natuurlijk nooit eindigen. Hier komt de retourwaarde voor de GetMessage- functie binnen. Normaal gesproken retourneert GetMessage- een niet-nulwaarde. Wanneer u de toepassing wilt afsluiten en de berichtenlus wilt verlaten, roept u de functie PostQuitMessage aan.
PostQuitMessage(0);
De functie PostQuitMessage plaatst een WM_QUIT bericht in de berichtenwachtrij. WM_QUIT is een speciaal bericht: dit zorgt ervoor dat GetMessage- nul retourneert en het einde van de berichtenlus aangeeft. Dit is de herziene berichtcirkel.
// Correct.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Zolang GetMessage een niet-nulwaarde retourneert, wordt de expressie in de while-lus beoordeeld als waar. Nadat u PostQuitMessagehebt aangeroepen, wordt de expressie onwaar en breekt het programma uit de lus. (Een interessant resultaat van dit gedrag is dat uw vensterprocedure nooit een WM_QUIT bericht ontvangt. Daarom hoeft u geen case-instructie voor dit bericht te hebben in uw vensterprocedure.)
De volgende voor de hand liggende vraag is wanneer de PostQuitMessagewordt aangeroepen. We gaan terug naar deze vraag in het onderwerp Het venster sluiten, maar eerst moeten we onze vensterprocedure schrijven.
Geplaatste berichten versus verzonden berichten
In de vorige sectie is er gesproken over berichten die naar een wachtrij gaan. Soms roept het besturingssysteem rechtstreeks een vensterprocedure aan, waardoor de wachtrij wordt overgeslagen.
De terminologie voor dit onderscheid kan verwarrend zijn:
- Bericht plaatsen een bericht betekent dat het bericht in de berichtenwachtrij wordt geplaatst en wordt verzonden via de berichtenlus (GetMessage- en DispatchMessage-).
- Verzenden een bericht betekent dat het bericht de wachtrij overslaat en het besturingssysteem de vensterprocedure rechtstreeks aanroept.
Voor nu is het verschil niet erg belangrijk. De vensterprocedure verwerkt alle berichten. Sommige berichten slaan de wachtrij echter over en gaan rechtstreeks naar uw vensterprocedure. Het kan echter een verschil maken als uw toepassing communiceert tussen vensters. U vindt een uitgebreidere bespreking van dit probleem in het onderwerp Over berichten en berichtenwachtrijen.