IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

La programmation Win32 en Virtual Pascal avec OWL


précédentsommairesuivant

V. Réponses aux messages Windows

V-A. Evénements et messages

Pour comprendre la suite, nous devons aborder (de manière extrêmement simplifiée) le fonctionnement intime de Windows.

Tous les événements qui se produisent au niveau du système sont stockés dans la file d'attente globale de Windows. Partant de là, Windows se charge d'informer les applications concernées par ces événements au moyen de messages.
Chaque application est dotée de sa propre file d'attente et le gros du travail consiste à traiter tous les messages déposés par Windows dans la file d'attente de l'application. C'est ici que la notion de programmation pilotée par événement prend tout son sens.

Pour illustrer la quantité effarante de messages qui parviennent à une application, vous pouvez faire l'expérience de tracer les messages reçus par le programme MINIMUM.EXE (le tout premier programme OWL que nous avons réalisé) à l'aide d'un utilitaire tel que WinSight (fourni avec Delphi). Le fait de démarrer le programme et de déplacer la souris pour aller le fermer génère des milliers de messages, dont voici un minuscule aperçu :

 
Sélectionnez
WM_NCMOUSEMOVE (a004X) Dispatched   wp=0000000E  lp=006F0237  (567,111) in TopRight
WM_NCHITTEST (8404X) Sent   wp=00000000  lp=006E0239  (569,110)
WM_SETCURSOR (2004X) Sent   wp=00060366  lp=0200000E  MouseMove in TopRight  hwnd 00060366
WM_NCMOUSEMOVE (a004X) Dispatched   wp=0000000E  lp=006E0239  (569,110) in TopRight
WM_NCHITTEST (8404X) Sent   wp=00000000  lp=00700239  (569,112)
WM_SETCURSOR (2004X) Sent   wp=00060366  lp=0200000E  MouseMove in TopRight  hwnd 00060366
WM_NCMOUSEMOVE (a004X) Dispatched   wp=0000000E  lp=00700239  (569,112) in TopRight
WM_NCHITTEST (8404X) Sent   wp=00000000  lp=00720239  (569,114)
WM_SETCURSOR (2004X) Sent   wp=00060366  lp=02000014  MouseMove in HT_0014h  hwnd 00060366
WM_NCMOUSEMOVE (a004X) Dispatched   wp=00000014  lp=00720239  (569,114) in HT_0014h
WM_NCHITTEST (8404X) Sent   wp=00000000  lp=00730239  (569,115)
WM_SETCURSOR (2004X) Sent   wp=00060366  lp=02000014  MouseMove in HT_0014h  hwnd 00060366
WM_NCMOUSEMOVE (a004X) Dispatched   wp=00000014  lp=00730239  (569,115) in HT_0014h
WM_NCHITTEST (8404X) Sent   wp=00000000  lp=00740239  (569,116)
WM_SETCURSOR (2004X) Sent   wp=00060366  lp=02000014  MouseMove in HT_0014h  hwnd 00060366
WM_NCMOUSEMOVE (a004X) Dispatched   wp=00000014  lp=00740239  (569,116) in HT_0014h
WM_NCHITTEST (8404X) Sent   wp=00000000  lp=00750238  (568,117)
WM_SETCURSOR (2004X) Sent   wp=00060366  lp=02000014  MouseMove in HT_0014h  hwnd 00060366
WM_NCMOUSEMOVE (a004X) Dispatched   wp=00000014  lp=00750238  (568,117) in HT_0014h
WM_NCHITTEST (8404X) Sent   wp=00000000  lp=00760237  (567,118)
WM_SETCURSOR (2004X) Sent   wp=00060366  lp=02000014  MouseMove in HT_0014h  hwnd 00060366
WM_NCMOUSEMOVE (a004X) Dispatched   wp=00000014  lp=00760237  (567,118) in HT_0014h
WM_NCHITTEST (8404X) Sent   wp=00000000  lp=00770236  (566,119)
WM_SETCURSOR (2004X) Sent   wp=00060366  lp=02000014  MouseMove in HT_0014h  hwnd 00060366
WM_NCMOUSEMOVE (a004X) Dispatched   wp=00000014  lp=00770236  (566,119) in HT_0014h
WM_NCHITTEST (8404X) Sent   wp=00000000  lp=00770235  (565,119)
WM_SETCURSOR (2004X) Sent   wp=00060366  lp=02000014  MouseMove in HT_0014h  hwnd 00060366
WM_NCMOUSEMOVE (a004X) Dispatched   wp=00000014  lp=00770235  (565,119) in HT_0014h
WM_NCHITTEST (8404X) Sent   wp=00000000  lp=00780235  (565,120)
WM_SETCURSOR (2004X) Sent   wp=00060366  lp=02000014  MouseMove in HT_0014h  hwnd 00060366
WM_NCMOUSEMOVE (a004X) Dispatched   wp=00000014  lp=00780235  (565,120) in HT_0014h
WM_NCHITTEST (8404X) Sent   wp=00000000  lp=00780235  (565,120)
WM_SETCURSOR (2004X) Sent   wp=00060366  lp=02010014  LButtonDown in HT_0014h  hwnd 00060366
WM_NCLBUTTONDOWN (a104X) Dispatched   wp=00000014  lp=00780235  (565,120) in HT_0014h
WM_LBUTTONUP (20204X) Dispatched   wp=00000000  lp=FFF301C3  (451,65523)
WM_CLOSE (1004X) Sent   wp=00000000  lp=00000000
WM_WINDOWPOSCHANGING (4604X) Sent   wp=00000000  lp=0012FA84  (0,0)-(0,0)    Z-Order Top
WM_WINDOWPOSCHANGED (4704X) Sent wp=00000000 lp=0012FA84 (110,110)-(577,719) NoSize, NoMove, NoZOrder, NoActivate, HideWindow Z-Order Unchanged
WM_NCACTIVATE (8604X) Sent   wp=00000000  lp=00000000  Deactivate to hwnd 00000000
WM_ACTIVATE (604X) Sent   wp=00000000  lp=00000000  Deactivate to hwnd 00000000
WM_ACTIVATEAPP (1c04X) Sent   wp=00000000  lp=000005A0  Deactivate to  task 000005A0
WM_KILLFOCUS (804X) Sent   wp=00000000  lp=00000000  Focus to hwnd 00000000
WM_DESTROY (204X) Sent   wp=00000000  lp=00000000
WM_NCDESTROY (8204X) Sent   wp=00000000  lp=00000000

V-B. Structure d'un message

Un message est un enregistrement de type tMessage, dont voici la déclaration :

 
Sélectionnez
Type tMessage = Record
                  Receiver : HWnd;
                  Message : Word;
                  case Integer of
                    0 : (wParam : Word;
                         lParam : LongInt;
                         Result : LongInt);
                    1 : (wParamLo : SmallWord;
                         wParamHi : SmallWord;
                         lParamLo : SmallWord;
                         lParamHi : SmallWord;
                         ResultLo : SmallWord;
                         ResultHi : SmallWord);
                    2 : (wParamLoLo : Byte;
                         wParamLoHi : Byte;
                         wParamHiLo : Byte;
                         wParamHiHi : Byte;
                         lParamLoLo : Byte;
                         lParamLoHi : Byte;
                         lParamHiLo : Byte;
                         lParamHiHi : Byte;
                         ResultLoLo : Byte;
                         ResultLoHi : Byte;
                         ResultHiLo : Byte;
                         ResultHiHi : Byte);
                end;

Les champs se définissent ainsi :

  • Receiver permet d'identifier le destinataire du message
  • Message identifie le message lui-même (wm_Destroy, wm_MouseMove, etc)
  • Result renseigne sur l'état de traitement du message
  • wParam et lParam sont des informations contextuelles

Le contenu des champs wParam et lParam varie d'un type de message à l'autre. Par exemple, pour le message wm_MouseMove, envoyé à une fenêtre lorsque le curseur de souris bouge, le champ wParam indique l'état des touches de contrôle du clavier, le champ lParamLo donne l'abscisse du curseur de la souris et le champ lParamHi donne son ordonnée.
Vous pouvez toujours consulter l'aide du SDK pour connaître le contenu des différents champs d'un message.

V-C. Identificateurs de messages

Sur l'extrait de fichier log créé par l'utilitaire WinSight ci-dessus, vous constatez que tous les messages répertoriés commencent par wm_. Par convention, tous les messages commencent par un code mnémonique suivi d'un underscore (caractère souligné).
Voici les codes mnémoniques les plus courants :

Code mnémonique Utilisation
wm_ Messages de Windows (Windows messages)
cm_ Identificateurs de commandes (menus et raccourcis clavier)
id_ Identicateurs de contrôles (éléments des boîtes de dialogue)
lb_ Messages des boîtes listes (list box messages)
cb_ Messages des boîtes combo (combo box messages)
em_ Messages des contrôles d'édition (edit messages)
bm_ Messages des boutons (button messages)


Il y en a bien sûr d'autres, que nous passerons sous silence pour le moment puisque nous ne les rencontrerons que beaucoup plus loin dans le tutoriel.

V-D. Réponse aux clics de souris : CLIC.PAS

Nous allons à présent voir comment un programme OWL répond aux messages Windows en prenant comme exemples les messages générés lorsque l'on clique avec la souris dans une fenêtre : wm_LButtonDown pour le bouton gauche (L pour Left) et wm_RButtonDown pour le bouton droit (R pour Right).
Le programme CLIC.PAS va afficher un message chaque fois que l'on cliquera avec le bouton gauche ou le bouton droit dans la fenêtre. Voici le source :

 
Sélectionnez
Program CLIC;

(* Réponse aux clics de la souris.

   Réalisé par Alcatîz pour Developpez.com - 25-06-2006 *)


Uses Windows,    (* API Win32 *)
     OWindows;   (* Objets OWL *)

                  
Type pFenetrePrincipale = ^tFenetrePrincipale;
     tFenetrePrincipale = Object(tWindow)
                            Constructor INIT (aParent : pWindowsObject; aTitle : pChar);
                            Procedure WMLBUTTONDOWN (var Msg : tMessage);
                               virtual wm_First + wm_LButtonDown;
                            Procedure WMRBUTTONDOWN (var Msg : tMessage);
                               virtual wm_First + wm_RButtonDown;
                          end;

     tProgramme = Object(tApplication)
                    Procedure INITMAINWINDOW; virtual;
                  end;


(* ----- Méthodes de l'objet tFenetrePrincipale ----- *)

Constructor tFenetrePrincipale.INIT (aParent : pWindowsObject; aTitle : pChar);
(* Initialisation de la taille de la fenêtre principale *)
Begin
  tWindow.INIT(aParent,aTitle);
  with Attr do
    begin
      x := 20; y := 20; w := 300; h := 200;
    end;
End;

Procedure tFenetrePrincipale.WMLBUTTONDOWN (var Msg : tMessage);
(* Réponse au clic du bouton gauche *)
Begin
  MessageBox(hWindow,'Bouton gauche','Clic souris',mb_IconInformation or mb_OK);
End;

Procedure tFenetrePrincipale.WMRBUTTONDOWN (var Msg : tMessage);
(* Réponse au clic du bouton droit *)
Begin
  MessageBox(hWindow,'Bouton droit','Clic souris',mb_IconInformation or mb_OK);
End;                  
                  
                
(* ----- Méthodes de l'objet tProgramme ----- *)                  

Procedure tProgramme.INITMAINWINDOW;
(* Allocation de la fenêtre principale du programme *)
Begin
  MainWindow := New(pFenetrePrincipale,INIT(Nil,'Cliquez dans la fen'#234'tre'));
End;


Var Programme : tProgramme;


(* ----- Programme principal ----- *)

Begin
  Programme.INIT('Clic');
  Programme.RUN;
  Programme.DONE;
End.

Prenez votre souffle car il y a pas mal de notions à découvrir pour comprendre ce programme.

V-D-1. La zone client de la fenêtre

Si vous testez le programme, vous pouvez essayer de cliquer sur tous les composants de la fenêtre (la barre de titre, les bordures, les boutons...). Vous constatez qu'il n'y a que lorsque vous cliquez dans la "zone intérieure" de la fenêtre que le programme réagit en affichant un message. Cette zone est appelée la zone client de la fenêtre. Vous retrouverez ce mot de vocabulaire dans la suite du tutoriel.

V-D-2. Objet descendant de tWindow

Contrairement à notre premier programme MINIMUM, nous ne nous contentons plus d'une fenêtre standard qui ne fait rien. Nous créons une fenêtre un peu plus spécialisée qui va réagir aux clics de souris. Pour ce faire, nous profitons du principe d'héritage de la POO pour créer une fenêtre tFenetreprincipale, descendante de la fenêtre standard tWindow, dont elle hérite bien sûr de tous les champs et méthodes. Nous ajoutons simplement les nouvelles fonctionnalités de la fenêtre sous forme de nouvelles méthodes : WMLBUTTONDOWN et WMRBUTTONDOWN.

L'objet tFenetrePrincipale hérite également du constructeur Init de l'objet ancêtre tWindow. C'est ainsi que la création de notre fenêtre principale est similaire à celle du programme MINIMUM :

 
Sélectionnez
  MainWindow := New(pFenetrePrincipale,INIT(Nil,'Cliquez dans la fen'#234'tre'));

V-D-3. Méthodes virtuelles dynamiques indexées

OWL fournit une technique de réponse aux messages Windows très simple : les méthodes virtuelles dynamiques indexées. Lorsque l'application reçoit un message Windows, OWL appelle la méthode qui a spécifiquement été écrite pour répondre à ce message.

Pour déclarer une méthode virtuelle dynamique indexée, on termine sa déclaration par le mot clé virtual suivi d'un index de plage et d'un identificateur de message

 
Sélectionnez
Procedure WMLBUTTONDOWN (var Msg : tMessage);
   virtual wm_First + wm_LButtonDown;

Tous les messages Windows ont été scindés par convention en différentes plages. Chaque plage est réservée à un type de message particulier et correspond à un index de début de plage.
Le tableau suivant reprend tous les index de début de plage :

Bornes de la plage Type de messages Index de plage
0000h .. 7FFFh Messages Windows wm_First
8000h .. 8FFFh Messages enfants id_First
9000h .. 9FFFh Messages de notification nf_First
A000h .. FFFFh Commandes cm_First

L'usage veut que le nom d'une méthode virtuelle dynamique indexée reflète le nom du message associé

En effet, par convention, il est fortement conseillé de calquer le nom des méthodes sur celui des messages. Ce n'est pas une obligation absolue si vous savez que personne d'autre que vous ne sera amené à relire vos sources mais bon, sachez que cette norme sera appliquée tout au long du tutoriel.
Ainsi, vous savez déjà que la méthode virtuelle dynamique indexée qui répondra au message wm_Size sera nommée WMSIZE.

V-D-4. L'unité Windows

Lorsque nous avons réalisé notre tout premier programme, nous n'avions eu besoin de déclarer que l'unité OWindows. Pour ce second programme, nous déclarons également l'unité Windows car nous avons besoin de l'API Windows.
En effet, c'est dans cette unité que sont déclarées toutes les constantes de Windows, dont les identificateurs de messages (wm_LButtonDown et wm_RButtonDown). En outre, nous utilisons une fonction de l'API : MessageBox.

V-D-5. La fonction MessageBox

Cette fonction de l'API très fréquemment utilisée. Elle permet d'afficher un message et, éventuellement, de poser une question à l'utilisateur. Vous pouvez même agrémenter le tout d'une petite icône et d'un avertissement sonore. Remarquez que, grâce à la syntaxe étendue, nous employons la fonction MessageBox comme une procédure, puisque nous n'avons pas besoin de récupérer une valeur en sortie.
Détaillons la manière dont nous utilisons MessageBox dans notre programme :

 
Sélectionnez
  MessageBox(hWindow,'Bouton gauche','Clic souris',mb_IconInformation or mb_OK);

Le premier paramètre, hWindow, est le handle de la fenêtre de notre application. Tous les objets Windows possèdent un handle, qui est en quelque sorte un identificateur attribué de manière interne par le système.

Dans la plupart des objets OWL, le handle est stocké dans un champ appelé hWindow

Les 2ème et 3ème paramètres de MessageBox sont le texte et le titre du message (dans cet ordre-là), de type chaîne AZT.

Le dernier paramètre est une combinaison logique de flags (drapeaux) permettant à la fois d'afficher une icône - associée à un avertissement sonore - et de décider quels boutons doivent être affichés. L'aspect de la boîte à messages affichée par notre programme est celui-ci :

Image non disponible

Le flag mb_IconInformation affiche l'icône d'information et mb_Ok un simple bouton "OK".
Ces drapeaux sont également des constantes de l'API Windows, déclarées dans l'unité Windows.

MessageBox peut également permettre de poser une question à l'utilisateur. Pour illustrer ceci, nous allons apporter une petite modification au programme pour que, lorsque l'on clique avec le bouton droit de la souris dans la zone client de la fenêtre, une boîte à messages nous demande si nous désirons quitter le programme. Remplaçons notre méthode WMRBUTTONDOWN par celle-ci :

 
Sélectionnez
Procedure tFenetrePrincipale.WMRBUTTONDOWN (var Msg : tMessage);
(* Réponse au clic du bouton droit *)
Begin
  if MessageBox(hWindow,'D'#233'sirez-vous quitter le programme ?','Question',
                mb_IconQuestion or mb_YesNo) = id_Yes
     then
       CloseWindow;
End;

Cette fois, comme nous avons besoin de connaître le choix de l'utilisateur, nous utilisons réellement MessageBox comme une fonction.
Voici à présent l'aspect de la boîte à messages :

Image non disponible

Pour afficher le point d'interrogation, nous utilisons le flag mb_IconQuestion; pour afficher les deux boutons "Oui" et "Non", nous utilisons le flag mb_YesNo.

La valeur de retour de MessageBox contient l'identificateur du bouton pressé par l'utilisateur, en l'occurence soit id_Yes soit id_No (également déclarées dans l'unité Windows). Si cette valeur est id_Yes, nous fermons l'application à l'aide de la méthode CloseWindow héritée de tWindow.

Voici un tableau reprenant les flags principaux utilisés par la fonction MessageBox :

Flag Résultat Valeurs possibles renvoyées
mb_Ok Bouton OK id_Ok
mb_OkCancel Boutons OK et Annuler id_Ok ou id_Cancel
mb_YesNo Boutons Oui et Non id_Yes ou id_No
mb_YesNoCancel Boutons Oui, Non et Annuler id_Yes, id_No ou id_Cancel
mb_AbortRetryIgnore Boutons Annuler, Réessayer et Ignorer id_Abort, id_Retry ou id_Ignore
mb_RetryCancel Boutons Réessayer et Annuler id_Retry ou id_Cancel
mb_SystemModal Empêche de swapper vers une autre application -
mb_IconHand Icône Stop -
mb_IconExclamation Icône Point d'exclamation -
mb_IconQuestion Icône Point d'interrogation -
mb_IconInformation Icône Information -

V-E. En résumé

Pour qu'une fenêtre d'application réponde à un message :

  • On utilise l'unité Windows pour accéder aux identificateurs de messages
  • On utilise une fenêtre descendante de tWindow
  • On crée une méthode virtuelle dynamique indexée qui réagit au message
  • Par convention, le nom de cette méthode dérive du nom du message

V-F. Téléchargement des sources des programmes de cette page


précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2005-2007 Jean-Luc Gofflot. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.