Mandragor


Notions de base

Avant de vous balancer 150 lignes de code source en vous disant que j'expliquerai plus tard le programme ligne par ligne, je vais expliquer les grandes lignes de la programmation windows, afin que lorsque vous verrez le premier programme vous ne soyez pas trop perdu. Eh oui, un hello world dans une fenetre sous windows n'a rien à voir avec un hello world en mode console, c'est un poil plus compliqué ;p

Le principe de messages.

En C standard, le programme débute à la première ligne de la fonction main, et se déroule ensuite ligne après ligne. Votre programme gère directement les entrées de l'utilisateur: bref, quand ce dernier tape quelque chose au clavier, ces données vous sont directement accessibles via un buffer. Sous windows, c'est différent: toutes les entrées de l'utilisateur sont envoyées au système d'exploitation. Bref, souris, clavier (joystick à retour de force ;p), tout passe par Windows.

Cela se passe sous la forme de messages. Par exemple, l'utilisateur va appuyer sur une touche, windows va générer le message WM_KEYDOWN (WM c'est pour Window Message), rechercher l'application active et lui envoyer le message. Enfin presque. Car le message n'arrive pas DIRECTEMENT au programme. Windows va tout d'abord l'envoyer à la file de messages du thread correspondant au programme, qui fonctionne suivant le système FIFO (First In, First Out). C'est à dire que le premier message arrivé sera également le premier message que vous lirez ;p

Quelle est l'utilité de ce système? Principalement, la suppression de messages redondants. Prenons l'exemple d'un mouvement de la souris: chaque mouvement de la souris génère la création d'un message WM_MOUSEMOVE. Bref, un mouvement continu de la souris va envoyez un nombre inimaginable de messages, ce qui risque de bien vite engorger le système. Ce système de file de messages va permettre de supprimer une partie de ces messages.

Mais tous ces messages qui arrivent dans la file de messages du programme, ils ne sont pas par la suite envoyés au programme. En effet, il va falloir aller lire cette boucle de message. Et bien sur, comme on ne sait pas quand exactement l'utilisateur va clicker sur un bouton ou vouloir afficher un menu, on devra être en permanence en train de lire cette "file d'attente" pour voir si un nouveau message ne serait pas arrivé. En gros, on va faire un :

  tant_que(programme_pas_fini)
  {
    lire_le_message;
    traiter_le_message;
  }

C'est une boucle qui ne se terminera qu'avec l'application :). On appelle ça la "boucle de messages"; elle va lire les messages du thread et l'envoyer à la fonction de gestion des messages de la fenêtre concernée. Nous verrons par la suite quelles fonctions vont nous permettre de réaliser cela simplement.

Cependant, le système de file d'attente n'est pas toujours respecté. En effet, il y a parfois des messages "prioritaires" qui vont devoir passer en premier. Ces message ne seront donc pas placés dans la file de messages, ils seront envoyés directement à cette fonction de traitement de messages.

Les fonctions de rappel ou fonctions callback

Une fonction de rappel est associée à un fenêtre, c'est une fonction qui va recevoir et traiter tous les messages relatifs à cette fenêtre. On distinguera donc deux types de messages:

Toutefois, lorsque windows doit envoyer un message prioritaire à une fenêtre, il doit savoir ou envoyer ce message. Voilà pourquoi lorsque l'on crée la fenêtre principale du programme (ou toute nouvelle classe de fenêtre, comme nous verrons par la suite), on associe une fonction à cette classe de fenêtre.

Maintenant, concrètement, elle va ressembler à quoi cette fonction? Bah étant donné qu'elle va recevoir tous les messages associés à cette fenêtre et qu'elle doit les traiter, et bien lorsqu'elle va recevoir un nouveau message son rôle va consister à déterminer de quel type de message il s'agit, et d'agir en conséquence, soit en exécutant quelques lignes de code, soit en appelant une fonction qui va gérer le message.

Par exemple, il est courant de créer une fonction qui va traiter tous les messages de la souris, la fonction callback va donc appeler une fonction gestion_souris, sans se préoccuper si l'utilisateur a appuyé sur le bouton gauche de la souris, ou si il a simplement déplacé sa souris.

Shema 1 - 11 Ko

On peut donc voir les fonctions callback comme des "aguilleurs" qui vont recevoir TOUS les messages envoyés à notre fenêtre, et qui vont ensuite soit les traite directement, soit les envoyer à une fonction de gestion (créée par nous) plus spécifique qui va traiter un certain type de messages, et qui va renvoyer tous les messages non traités à la procédure de gestion de messages par défaut.

Etre capable en permanence de redessiner l'intégralité de la fenêtre

Eh oui, ca c'est l'inconvénient du mode multifenêtré ;p. En effet, vous avez fait votre petit programme, ce dernier va afficher quelque chose dans sa petite fenêtre. Seulement, voilà, maintenant l'utilisateur il en a marre de votre programme, il va activer une autre fenetre qu'il va déplacer par dessus la votre. Ensuite, une fois qu'il a fini ce qu'il avait à faire avec cette autre application, il va réactiver votre application pour finir ce qu'il avait commencé. Mais là, surprise, il n'y a plus rien dans la fenêtre, une fenêtre blanche.

En effet, à partir du moment ou il a masqué votre fenêtre, et bah windows a.... écrasé votre contenu en fait. Sauf cas très particulier, il ne sauvegarde pas votre fenêtre dans une bitmap pour la redessiner lorsque l'utilisateur va réactiver votre fenêtre. Et bah non, tout le boulot vous incombe. Ca sera à vous d'avoir en permanence suffisament de données sur votre fenêtre pour être capable à n'importe quel moment de la redessiner en entier, ou en partie.

Bon, en détail, comment ca marche. Lorsqu'une portion de votre fenêtre est masquée par une autre, windows va poster un message WM_PAINT à la file de messages de votre fenêtre. J'en profite pour toucher deux mots à propos du message WM_PAINT, qui est en quelque sorte une exception par rapport aux autres messages de part la façon dont il est traité. En effet, on a vu qu'il y avait des messages super importants que l'os envoie directement à la fonction callback. Et bien, WM_PAINT, c'est le contraire, c'est un message qui a une faible priorité. Cela veut dire qu'il arrivera à la tête de la file des messages uniquement... lorsqu'il n'y en aura plus d'autres. Tant que la fenêtre n'aura pas été redessinée, Windows continuera d'envoyer des messages WM_PAINT à l'application: toutefois windows ne gardera qu'un message WM_PAINT par file (c'est à dire qu'ils va supprimer tous les nouveaux message WM_PAINT s'il y en a déjà un de présent).

Bon bref, vous récupérez donc le message WM_PAINT dans votre boucle de messages de la fonction WinMain, et vous envoyez ce message à votre fonction callback de votre fenêtre principale. Cette fonction va donc déterminer qu'il s'agit d'un message WM_PAINT, et va donc entreprendre de redessiner la fenêtre, ou du moins la partie de la fenêtre qui a été masquée, si votre code est bien propret :). Je n'en rajouterait pas plus pour l'instant.