[delphi] les messages

[delphi] les messages

Ce tutoriel n'a pas vocation à les expliquer tous (il y en a près de 850), mais plutôt à voir comment ils fonctionnent.

Description

Pour accéder à ces messages, il suffit d'ajouter l'unité MESSAGES dans la clause USES. Cette unité énumère tous les messages existant et déclare tous les types qui serviront au décodage des informations transmises par Windows.

Un message peut-être considéré comme un évènement spécial déclenché par Windows : c'est comme les ordres, lorsqu'ils arrivent, on exécute. On revient alors aux TWinControl : fenêtrage implique l'existence d'un handle. C'est un numéro attribué par Windows et qui est la "plaque d'immatriculation" de la fenêtre, numéro non définitif et constant pour toute la durée de vie du composant.

Connaissant ce numéro, on peut interagir sur les ressources fenêtrées.

Un message est caractérisé par plusieurs choses :

  • par le type de ressource sur lequel il peut agir (Static, ListBox, Combo, Edit, Dialog...)
  • par sa valeur numérale
  • par les paramètres qui lui seront fournis

Ainsi, les messages LB_* sont destinés aux ListBox, EM_* sur les Edit, WM_* sur les fenêtres... etc. Et souvent, au lieu de bidouiller des calculs grotesques dans Delphi, on fait appel à ces messages qui renvoient souvent les informations désirées très rapidement.

Pourquoi rapidement ? En fait, il faut savoir que les messages sont numérotés de telle manière que leur valeur soit unique et strictement inférieure à WM_USER = 1024. Si vous avez des messages personnels à créer, alors la valeur numérique devra être supérieure à 1024, sachant que 1024 correspondrait à une valeur neutre. On a vu que les composants "Standard" sont gérés uniquement par Windows... et c'est grâce aux messages que la VCL de Delphi a pu intégrer ces ressources. Windows gère le fonctionnement du composant, Delphi n'en fait qu'un interfacement grâce aux messages.

Pour tout avouer, les TWinControl sont très étonnants du point de vue fonctionnel. Ils ont une propriété STYLE cachée qui permet en une seule commande de les transformer (en apparence seulement) en fenêtre, en bouton cliquable ou en TGroupBox.

De plus, tous les évènements OnClick, OnMouseMove... que vous connaissez sont liés aux messages.

Envoyer un message

Supposons que vous avez une TListBox et vous voulez savoir quel est l'index de liste du tout premier item visible en haut. Pas besoin de manipuler les ressources de la souris, on fait simplement ceci. N'oubliez pas d'activer l'unité ShellApi dans les USES.

Index := SendMessage(MaListBox.Handle, LB_GetTopIndex, 0, 0);

On connait le Handle, ce qu'on recherche (remarquez que le nom du message est très transparent) et il n'y a ici pas de paramètres à fournir. Savoir qui sont les paramètres se déduit de la lecture de l'aide : c'est indispensable. Vous cliquez une fois sur LB_GetTopIndex et appuyez sur F1. Le fichier d'aide WIN32.HLP [qui fait 11 Mo !!] s'ouvre et vous avez toute la connaissance de Windows, même si l'aide n'est pas aussi esthétique que l'aide générale de Delphi.

Paramétrer le message

Généralement, il n'y a pas de paramétrage inattendu. On se doute quand même un peu de ce qu'on doit fournir.

has no parameters
Dans ce cas là, vous mettez 0 et 0.

1 or 2 parameters
Vous regardez ce que l'aide propose et vous notez les constantes identificatrices. Si une seule est nécessaire vous notez son nom dans le paramètre approprié, sachant que wParam est souvent plus utile que lParam. Si vous devez mettre plusieurs constantes en une seule, alors utiliser l'opérateur logique OR. Vous obtenez alors :

Index := SendMessage(MaListBox.Handle, Msg_Message, CTE_N1 or CTE_N2, 0);

Ça marche à l'identique pour le second paramètre. Mais vous allez me dire qu'il y a un problème. Comment savoir si une constante est combinée ? C'est simple :

if Combinaison and not IDMessage = IDMessage then
  ShowMessage('IDMessage est contenu dans la combinaison');

Dès lors, on peut faire passer de nombreuses combinaisons définissant une caractéristique. Si vous développez un composant, déterminer correctement les constantes et agissez en conséquence. C'est très utile...

Mais parfois, c'est une structure (TYPE...RECORD) qui est demandée en paramètre. C'est-à-dire que le message va entraîner une modification d'une variable au sein de votre application. Comment est-ce possible ?

Une fois de plus, c'est très simple ! On va simplement indiquer où se trouve la variable dans la mémoire. On prend l'opérateur @ pour trouver l'adresse. Je pense tout particulièrement au code suivant.

procedure SetEditRect;
var R: TRect;
begin
  R:=Rect(0, 0, 15, 15);
  SendMessage(Handle, EM_SetRectNP, 0, LongInt(@R));
end;

wParam n'est pas concerné (=0), car il sert à qualifier le message choisi (style, contenu...) alors que lParam rentre plus dans tout ce qui est technique. Mais en fait, ça dépend beaucoup des messages. On notera dans notre exemple que notre variable R ne subit aucune modification (c'est un cas particulier).

Intercepter un message

On l'a vu dans le développement de composants.

Mais parfois, le message tombe des nues comme ça brusquement. Par exemple, quand vous retirez une clé USB, le dispatcheur envoie à tous les handle le message WM_ChangeDevice pour informer les applications qui doivent traiter le signal en conséquence.

Pour gérer des messages lorsqu'on n'a pas de fenêtre, on utilise la variable Application. Pour l'activer, il faut référencer l'unité Forms dans les USES. Cela alourdit terriblement votre application (peut-être est-ce inutile ?). Il faudrait alors se pencher sur des hook externes qui ont pour tâche d'intercepter les messages (c'est juste que ce n'est pas non plus facile à faire). Utilisons le code suivant :

unit HookTest;
interface
uses Forms, Classes, Messages, SysUtils;
type
  THookPerso = class
  private
    procedure WinHook(var Msg:TMsg; var Handled:boolean);
  public
    constructor Create;
  end;
var HK : THookPerso;
implementation
constructor THook.Create;
begin
  inherited Create;
  Application.OnMessage:=WinHook;
end;
procedure THookPerso.WinHook(var Msg:TMsg; var Handled:boolean);
begin
  Handled:=false; //on laisse tout filtrer
end;
initialization
  HK:=THookPerso.Create;
finalization
  HK.Free;
end.

Pour surveiller les messages via Application.OnMessage, vous devez avoir une variable initialisée correspondant à une classe dans laquelle existe une procédure privée de même paramétrage que Application.OnMessage. Lorsqu'on assigne quelque chose à OnMessage, il faut que la procédure soit portée par une classe et c'est fondamental !

Dans le cas général, TForm1 (pour un nouveau projet) suffit à héberger WinHook. On aurait HK <=> Form1, THookPerso <=> TForm1. Il n'y a pas de blocs initialization...finalization avec Form1, car Form1 est créée dans le DPR via Application.CreateForm.

Application.OnMessage intercepte les messages arrivant sur l'application, et pas forcément les messages postés sur l'un de ses composants via un SendMessage pirate.

Freiner un message

On vient de le voir au-dessus, pour ce qui est de la variable Application, laisser passer ou non un message se fait selon le résultat de la variable Handled.

Pour ce qui est des composants, dans les procédures d'interception, utiliser le mot clé INHERITED rend la main aux classes parentes, et ne pas l'utiliser bloque tout. Ainsi, en fonction de la position de INHERITED, vous exécuterez du code avant ou après le code qui aurait dû s'exécuter dans les classes parentes. Théoriquement, dans la logique des choses, dans toutes les configurations inimaginables, le passage du BEGIN au END ne doit laisser qu'une seule rencontre au maximum du mot INHERITED. Revoyez le code suivant pour voir le truc :
http://codes-sources.commentcamarche.net/source/25416-proteger-les-tedit-contre-les-revelateurs-de-mots-de-passe

Conclusion

Après, il existe d'autres procédure sur les messages, mais avec ce qui précède vous devriez vous en sortir dans 95% des cas. Et aussi, avant de finir, comment peut-on connaître quels messages arrivent quand et où ? Eh bien, c'est le sujet du prochain chapître... ;)

Tanguy ALTERT, http://altert.family.free.fr/

Ce document intitulé « [delphi] les messages » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.
Rejoignez-nous