Messages de fenêtre (Commencer avec Win32 et C++)
Une application GUI doit répondre aux événements de l’utilisateur et du système d’exploitation.
- Les événements de l’utilisateur incluent toutes les façons dont quelqu’un peut interagir avec votre programme : clics de souris, frappes de touches, gestes sur écran tactile, etc.
- Les événements du système d’exploitation incluent tout ce qui est « extérieur » au programme et qui peut affecter le comportement du programme. Par exemple, l’utilisateur pourrait brancher un nouveau périphérique matériel, ou Windows pourrait entrer dans un état de faible consommation d’énergie (veille ou mise en veille prolongée).
Ces événements peuvent se produire à tout moment pendant que le programme s’exécute, dans presque n’importe quel ordre. Comment structurer un programme dont le flux d’exécution ne peut être prédit à l’avance ?
Pour résoudre ce problème, Windows utilise un modèle de passage de messages. Le système d’exploitation communique avec la fenêtre de votre application en lui passant des messages. Un message est simplement un code numérique désignant un événement particulier. Par exemple, si l’utilisateur appuie sur le bouton gauche de la souris, la fenêtre reçoit un message avec le code de message suivant.
#define WM_LBUTTONDOWN 0x0201
Certains messages ont des données associées. Par exemple, le message WM_LBUTTONDOWN inclut les coordonnées x et y du curseur de la souris.
Pour passer un message à une fenêtre, le système d’exploitation appelle la procédure de fenêtre enregistrée pour cette fenêtre. (Et maintenant vous savez à quoi sert la procédure de fenêtre).
La boucle de messages
Une application recevra des milliers de messages pendant son exécution. (Considérez que chaque frappe de touche et chaque clic de bouton de souris génèrent un message.) De plus, une application peut avoir plusieurs fenêtres, chacune avec sa propre procédure de fenêtre. Comment le programme reçoit-il tous ces messages et les envoie-t-il à la bonne procédure de fenêtre ? L’application a besoin d’une boucle pour récupérer les messages et les distribuer aux bonnes fenêtres.
Pour chaque thread qui crée une fenêtre, le système d’exploitation crée une file d’attente pour les messages de fenêtre. Cette file d’attente contient les messages pour toutes les fenêtres créées sur ce thread. La file d’attente elle-même est cachée de votre programme. Vous ne pouvez pas manipuler directement la file d’attente. Cependant, vous pouvez retirer un message de la file d’attente en appelant la fonction GetMessage.
MSG msg;
GetMessage(&msg, NULL, 0, 0);
Cette fonction retire le premier message en tête de file. Si la file d’attente est vide, la fonction bloque jusqu’à ce qu’un autre message soit en file d’attente. Le fait que GetMessage bloque ne rendra pas votre programme non réactif. S’il n’y a pas de messages, il n’y a rien à faire pour le programme. Si vous devez effectuer un traitement en arrière-plan, vous pouvez créer des threads supplémentaires qui continuent de fonctionner pendant que GetMessage attend un autre message. (Veuillez consulter la section Éviter les goulots d’étranglement dans votre procédure de fenêtre)
Le premier paramètre de GetMessage est l’adresse d’une structure MSG. Si la fonction réussit, elle remplit la structure MSG avec des informations sur le message. Cela inclut la fenêtre cible et le code de message. Les trois autres paramètres vous permettent de filtrer les messages que vous obtenez de la file d’attente. Dans presque tous les cas, vous réglerez ces paramètres à zéro.
Bien que la structure MSG contienne des informations sur le message, vous ne l’examinerez presque jamais directement. Au lieu de cela, vous la passerez directement à deux autres fonctions.
TranslateMessage(&msg);
DispatchMessage(&msg);
La fonction TranslateMessage est liée à la saisie au clavier. Elle traduit les frappes de touches (appui sur une touche, relâchement d’une touche) en caractères. Vous n’avez pas vraiment besoin de savoir comment cette fonction fonctionne ; souvenez-vous simplement de l’appeler avant DispatchMessage.
La fonction DispatchMessage indique au système d’exploitation d’appeler la procédure de fenêtre de la fenêtre cible du message. En d’autres termes, le système d’exploitation recherche le handle de la fenêtre dans sa table de fenêtres, trouve le pointeur de fonction associé à la fenêtre et appelle la fonction.
Par exemple, supposons que l’utilisateur appuie sur le bouton gauche de la souris. Cela entraîne une chaîne d’événements :
- Le système d’exploitation met un message WM_LBUTTONDOWN dans la file d’attente des messages.
- Votre programme appelle la fonction GetMessage.
- GetMessage extrait le message WM_LBUTTONDOWN de la file d’attente et remplit la structure MSG.
- Votre programme appelle les fonctions TranslateMessage et DispatchMessage.
- À l’intérieur de DispatchMessage, le système d’exploitation appelle votre procédure de fenêtre.
- Votre procédure de fenêtre peut soit répondre au message, soit l’ignorer.
Lorsque la procédure de fenêtre se termine, elle retourne à DispatchMessage. Ceci revient à la boucle de messages pour le message suivant. Tant que votre programme est en cours d’exécution, les messages continueront d’arriver dans la file d’attente. Par conséquent, vous devez avoir une boucle qui extrait continuellement les messages de la file d’attente et les distribue. Vous pouvez penser que la boucle fait ce qui suit :
// WARNING: Don't actually write your loop this way.
while (1)
{
GetMessage(&msg, NULL, 0, 0);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Bien sûr, telle qu’elle est écrite, cette boucle ne se terminerait jamais. C’est là que la valeur de retour de la fonction GetMessage entre en jeu. Normalement, GetMessage retourne une valeur non nulle. Lorsque vous souhaitez quitter l’application et sortir de la boucle de messages, appelez la fonction PostQuitMessage.
PostQuitMessage(0);
La fonction PostQuitMessage met un message WM_QUIT dans la file d’attente des messages. WM_QUIT est un message spécial : il fait que GetMessage retourne zéro, signalant la fin de la boucle de messages. Voici la boucle de messages révisée.
// Correct.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Tant que GetMessage retourne une valeur non nulle, l’expression dans la boucle while s’évalue à vrai. Après avoir appelé PostQuitMessage, l’expression devient fausse et le programme sort de la boucle. (Un résultat intéressant de ce comportement est que votre procédure de fenêtre ne reçoit jamais de message WM_QUIT. Par conséquent, vous n’avez pas besoin d’une instruction case pour ce message dans votre procédure de fenêtre.)
La prochaine question évidente est quand appeler PostQuitMessage. Nous reviendrons à cette question dans la rubrique Fermer la fenêtre, mais d’abord nous devons écrire notre procédure de fenêtre.
Messages postés contre messages envoyés
La section précédente a parlé des messages entrant dans une file d’attente. Parfois, le système d’exploitation appellera directement une procédure de fenêtre, en contournant la file d’attente.
La terminologie de cette distinction peut être déroutante :
- La publication Poster un message signifie que le message va dans la file d’attente des messages et est distribué via la boucle de messages (GetMessage et DispatchMessage).
- Envoyer un message signifie que le message contourne la file d’attente et que le système d’exploitation appelle directement la procédure de fenêtre.
Pour l’instant, la différence n’est pas très importante. La procédure de fenêtre gère tous les messages. Cependant, certains messages contournent la file d’attente et vont directement à votre procédure de fenêtre. Cependant, cela peut faire une différence si votre application communique entre fenêtres. Vous pouvez trouver une discussion plus approfondie de cette question dans la rubrique À propos des messages et des files d’attente de messages.