Messages de fenêtre (Prise en main de 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 une personne peut interagir avec votre programme : clics de souris, coups de touche, mouvements d’écran tactile, etc.
  • Les événements du système d’exploitation incluent tout ce qui est « extérieur » au programme qui peut affecter le comportement du programme. Par exemple, l’utilisateur peut brancher un nouveau périphérique matériel, ou Windows peut passer à un état de faible consommation (veille ou mise en veille prolongée).

Ces événements peuvent se produire à tout moment pendant l’exécution du programme, dans presque n’importe quel ordre. Comment structurez-vous un programme dont le flux d’exécution ne peut pas être prédit à l’avance ?

Pour résoudre ce problème, Windows utilise un modèle de transmission de messages. Le système d’exploitation communique avec la fenêtre de votre application en lui transmettant des messages. Un message est simplement un code numérique qui désigne un événement particulier. Par exemple, si l’utilisateur appuie sur le bouton gauche de la souris, la fenêtre reçoit un message contenant le code de message suivant.

#define WM_LBUTTONDOWN    0x0201

Des données sont associées à certains messages. Par exemple, le message WM_LBUTTONDOWN inclut la coordonnée x et la coordonnée 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 inscrite pour cette fenêtre. (Et maintenant, vous savez à quoi sert la procédure de fenêtre.)

Boucle de message

Une application reçoit des milliers de messages pendant son exécution. (Considérez que chaque séquence de touches et chaque clic de bouton de la souris génèrent un message.) En outre, 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 remet-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 fenêtres appropriées.

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 masquée de votre programme. Vous ne pouvez pas manipuler directement la file d’attente. Toutefois, vous pouvez extraire un message de la file d’attente en appelant la fonction GetMessage .

MSG msg;
GetMessage(&msg, NULL, 0, 0);

Cette fonction supprime le premier message du début de la file d’attente. Si la file d’attente est vide, la fonction se bloque jusqu’à ce qu’un autre message soit mis en file d’attente. Le fait que les blocs GetMessage ne rendent pas votre programme qui ne répond pas. S’il n’y a aucun message, le programme n’a rien à faire. Si vous devez effectuer un traitement en arrière-plan, vous pouvez créer des threads supplémentaires qui continuent à s’exécuter pendant que GetMessage attend un autre message. (Voir É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 du message. Les trois autres paramètres vous permettent de filtrer les messages que vous recevez de la file d’attente. Dans presque tous les cas, vous définissez ces paramètres sur 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 le passerez directement à deux autres fonctions.

TranslateMessage(&msg); 
DispatchMessage(&msg);

La fonction TranslateMessage est liée à l’entrée au clavier. Il traduit les séquences de touches (touche vers le bas, touche vers le haut) en caractères. Vous n’avez pas vraiment besoin de savoir comment cette fonction fonctionne; n’oubliez pas de l’appeler avant DispatchMessage. Le lien vers la documentation MSDN vous donnera plus d’informations, si vous êtes curieux.

La fonction DispatchMessage indique au système d’exploitation d’appeler la procédure de fenêtre de la fenêtre qui est la cible du message. En d’autres termes, le système d’exploitation recherche le handle de fenêtre dans sa table de fenêtres, recherche 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 provoque une chaîne d’événements :

  1. Le système d’exploitation place un message WM_LBUTTONDOWN dans la file d’attente des messages.
  2. Votre programme appelle la fonction GetMessage .
  3. GetMessage extrait le message WM_LBUTTONDOWN de la file d’attente et remplit la structure MSG .
  4. Votre programme appelle les fonctions TranslateMessage et DispatchMessage .
  5. Dans DispatchMessage, le système d’exploitation appelle votre procédure de fenêtre.
  6. Votre procédure de fenêtre peut répondre au message ou l’ignorer.

Lorsque la procédure de fenêtre est retournée, elle retourne à DispatchMessage. Cette opération retourne à la boucle de message 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 disposer d’une boucle qui extrait continuellement les messages de la file d’attente et les distribue. Vous pouvez considérer la boucle comme procédant comme suit :

// WARNING: Don't actually write your loop this way.

while (1)      
{
    GetMessage(&msg, NULL, 0,  0);
    TranslateMessage(&msg); 
    DispatchMessage(&msg);
}

Comme écrit, bien sûr, cette boucle ne se terminerait jamais. C’est là qu’intervient la valeur de retour de la fonction GetMessage . Normalement, GetMessage renvoie une valeur différente de zéro. Lorsque vous souhaitez quitter l’application et sortir de la boucle de message, appelez la fonction PostQuitMessage .

        PostQuitMessage(0);

La fonction PostQuitMessage place un message WM_QUIT dans la file d’attente des messages. WM_QUIT est un message spécial : GetMessage retourne zéro, signalant la fin de la boucle de message. Voici la boucle de message révisée.

// Correct.

MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

Tant que GetMessage renvoie une valeur différente de zéro, l’expression dans la boucle while prend la valeur true. Après avoir appelé PostQuitMessage, l’expression devient false 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’avoir une instruction case pour ce message dans votre procédure de fenêtre.)

La prochaine question évidente est quand appeler PostQuitMessage. Nous revenons à cette question dans la rubrique Fermeture de la fenêtre, mais nous devons d’abord écrire notre procédure de fenêtre.

Messages publiés et messages envoyés

La section précédente a parlé des messages envoyés dans une file d’attente. Parfois, le système d’exploitation appelle directement une procédure de fenêtre, contournant la file d’attente.

La terminologie de cette distinction peut prêter à confusion :

  • La publication d’un message signifie que le message passe dans la file d’attente des messages et qu’il est distribué via la boucle de message (GetMessage et DispatchMessage).
  • L’envoi d’un message signifie que le message ignore 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. Toutefois, certains messages contournent la file d’attente et accèdent directement à votre procédure de fenêtre. Toutefois, cela peut faire une différence si votre application communique entre des fenêtres. Vous trouverez une description plus approfondie de ce problème dans la rubrique À propos des messages et des files d’attente de messages.

Suivant

Écriture de la procédure de fenêtre