I. Un programme de tracé de ligne

Tout d'abord, il faut permettre à l'utilisateur d'indiquer les deux extrémités de la ligne à tracer. Nous avons deux possibilités :

  1. Quand l'utilisateur appuie sur le bouton gauche de la souris, il indique le point de départ de la ligne. Quand il relache ce bouton, il indique le point final.
  2. L'utilisateur clique une première fois pour indiquer le point de départ, puis une seconde fois pour le point final. Cette deuxième solution présente l'avantage de permettre une plus grande précision, car l'utilisateur n'a pas à garder son doigt appuyé lorsqu'il déplace la souris. C'est cette approche que nous allons détailler.

Tout le code qui gére ce comportement est placé dans l'événement OnMouseDown de la fiche. Nous avons besoin de deux variables globales :

  1. WaitForSecondPoint: Boolean; Cette variable booléenne nous permet de savoir si le clic correspond au premier point (La variable est alors alors égale à False) ou au second point (La variable est égale à True). A chaque clic, on inverse sa valeur.
  2. FirstPoint: TPoint; Cette variable nous permet de mémoriser la position du premier point.
 
Sélectionnez

[...]


var
  FirstPoint: TPoint;
  Form1: TForm1;
  WaitForSecondPoint: Boolean;

implementation

{$R *.DFM}
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if WaitForSecondPoint then
  begin
    // C'est le deuxième point, on dessine réellement la ligne.
    Canvas.MoveTo(FirstPoint.X, FirstPoint.Y);
    Canvas.LineTo(X, Y);
  end
  else FirstPoint := Point(X, Y); // On mémorise la position du premier point.

  WaitForSecondPoint := not WaitForSecondPoint;
end;

end.
			

II. Implémenter la ligne élastique

Voilà, nous avons un programme qui nous permet de tracer des lignes. Il ne nous reste plus qu'à implémenter cette fameuse ligne élastique. Pour ce faire, nous devons ajouter un gestionnaire à l'événement OnMouseMove de notre fiche.

 
Sélectionnez

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  if WaitForSecondPoint then
  begin
    // On passe le crayon en mode inverse.
    Canvas.Pen.Mode := pmNot;

    // Si on a tracé une ligne la fois précédente, alors on l'efface.
    if ErasePrevious then
    begin
      Canvas.MoveTo(FirstPoint.X, FirstPoint.Y);
      Canvas.LineTo(PreviousPoint.X, PreviousPoint.Y);
    end;

    // On trace la ligne du premier point au point en cours.
    Canvas.MoveTo(FirstPoint.X, FirstPoint.Y);
    Canvas.LineTo(X, Y);

    // On mémorise le point courant pour l'éventuel effacement.
    PreviousPoint := Point(X, Y);

    // On a tracé une ligne, il faut l'effacer la prochaine fois.
    ErasePrevious := True;   

    // On restore le mode initial.
    Canvas.Pen.Mode := pmCopy;
  end;
end;
			

A chaque fois que le message WM_MOUSEMOVE est envoyé à notre fiche par Windows, nous traçons une ligne du premier point sélectionné à la position courante du curseur et nous effaçons la ligne qui a été éventuellement tracée à l'occurence précédente de cet événement. Pour ce faire, nous mémorisons la position précédente du curseur dans la variable globale PreviousPoint de type TPoint et nous passons le crayon en mode inverse. Tous les points qui composent la ligne ont alors leur couleur inversée. Les pixels noirs deviennent blanc, et vice et versa. L'intéret de la méthode, c'est que si nous traçons deux fois la ligne, nous inversons l'effet. Les pixels noirs passés en blanc reprennent leur couleur noire, et ainsi de suite, ce qui nous permet de tracer, puis d'effacer une ligne sans affecter le fond de l'écran.

Pour savoir si nous avons une ligne à effacer, nous basculons la variable globale ErasePrevious (Effacer précédent) à False dans le gestionnaire de l'événement OnMouseDown, puis à True après que l'on ait tracé une ligne dans le gestionnaire de l'événement OnMouseMove. Le code de la méthode FormMouseDown devient donc :

 
Sélectionnez

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if WaitForSecondPoint then
  begin
    // C'est le deuxième point, on dessine réellement la ligne.
    Canvas.MoveTo(FirstPoint.X, FirstPoint.Y);
    Canvas.LineTo(X, Y);
  end
  else 
  begin
    FirstPoint := Point(X, Y); // On mémorise la position du premier point.    
    ErasePrevious := False;
  end;

  WaitForSecondPoint := not WaitForSecondPoint;
end;
			

III. Un petit plus

Notre ligne élastique fonctionnant, nous pouvons l'améliorer un peu. En effet, notre programme souffre d'un léger défaut. La couleur de la ligne élastique est différente de celle de la ligne finale. En fait, elle correspond à l'inverse de la couleur de la fiche (La fiche est par défaut de couleur gris clair, ce qui nous donne une ligne élastique de couleur gris foncé).
Nous allons utiliser un autre mode pour notre crayon : le mode pmXor.

Alors que le mode pmNot inversait la couleur du fond, celui ci combine la couleur du crayon et celle du fond à l'aide d'une opération binaire Xor. Pour mémoire, voici le tableau des différentes combinaisons possibles :

A B A xor B = C
0 0 0
0 1 1
1 0 1
1 1 0

Cette opération présente la même particularité que l'opérateur unaire not : elle est réversible. Si nous calculons C xor B, nous obtenons à nouveau A.

C B C xor B = A
0 0 0
1 1 0
1 0 1
0 1 1

Nous pouvons donc utiliser le mode pmXor comme le mode pmNot. Si nous traçons deux fois une ligne dans ce mode, nous l'effaçons sans altérer le fond de l'écran. L'intérêt du mode pmXor, c'est que nous pouvons controler la couleur qui sera affichée à l'écran en modifiant la couleur du crayon.

Windows détermine la couleur à afficher de la façon suivante :

Soit Ca la couleur à afficher, Cc la couleur du crayon et Cf la couleur du fond. On a Ca = Cc xor Cf. On connait Ca : c'est la couleur qu'on veut donner à la ligne. Cf correspond à la valeur de la propriété Color de la fiche. Pour déterminer la couleur à donner au crayon, on fait le tableau des différentes combinaisons :

Cc Cf Ca
0 0 0
0 1 1
1 0 1
1 1 0

Après examen, on en déduit que Cc = Ca xor Cf.

Nous pouvons donc modifier notre méthode FormMouseMove de la façon suivante :

 
Sélectionnez

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  OldColor: TColor;
begin
  if WaitForSecondPoint then
  begin
    // On passe le crayon en mode pmXor, et on modifie sa couleur.
    Canvas.Pen.Mode := pmXor;
    OldColor := Canvas.Pen.Color;
    Canvas.Pen.Color := ColorToRgb(Canvas.Pen.Color) xor ColorToRgb(Color);

    // Si on a tracé une ligne la fois précédente, alors on l'efface.
    if ErasePrevious then
    begin
      Canvas.MoveTo(FirstPoint.X, FirstPoint.Y);
      Canvas.LineTo(PreviousPoint.X, PreviousPoint.Y);
    end;

    // On trace la ligne du premier point au point en cours.
    Canvas.MoveTo(FirstPoint.X, FirstPoint.Y);
    Canvas.LineTo(X, Y);

    // On mémorise le point courant pour l'éventuel effacement.
    PreviousPoint := Point(X, Y);

    // On a tracé une ligne, il faut l'effacer la prochaine fois.
    ErasePrevious := True;   

    // On restore le mode et la couleur initiale.
    Canvas.Pen.Mode := pmCopy;
    Canvas.Pen.Color := OldColor;
  end;
end;
			

Le code modifié est en rouge. Vous noterez qu'on utilise la fonction ColorToRgb pour convertir les couleurs au format Delphi en couleur au format Window, ceci pour éviter les problèmes avec les couleurs déterminées à l'exécution (clBtnFace, clWindowText ... etc).

Ce code fonctionne parfaitement... sauf avec un affichage qui utilise une palette. En effet dans ce cas, ce ne sont pas des couleurs qui sont manipulées, mais des index dans la palette système. C'est complétement illogique et on peut dire que sur ce coup là, les programmeurs de chez MicroSoft ne se sont pas foulés. Comme il semble impossible de savoir à quelles couleurs correspondent ces index, il nous est impossible de déterminer la couleur à donner au crayon. Cela peut être génant dans certains cas, car on obtient parfois une couleur pour la ligne élastique qui est similaire à la couleur du fond. La ligne élastique n'est alors plus visible. Pour éviter cela, il faut tester la profondeur de couleur. Vous pouvez ainsi déterminer si le système utilise une palette ou non. Dans le premier cas, utilisez le mode pmNot pour éviter les problèmes.

 
Sélectionnez

if GetDeviceCaps(Canvas.Handle, BITSPIXEL) >= 24  
then PenMode := pmXor  // Le système travaille en vraies couleurs, on peut utiliser le mode pmXor.
else PenMode := pmNot; // Le système utilise une palette, on ne peut pas utiliser le mode pmXor.
			

Pour télécharger le code source de l'exemple suivant (Delphi 2.0), cliquez ici.
Si ce lien ne fonctionne pas chez vous, essayez celui-ci.

Image non disponible
Fig. 1 - Le programme exemple.

Vous pouvez modifier la couleur du crayon ou de la fiche dans la méthode FormCreate pour observer le comportement du programme avec différentes couleurs et des profondeurs de couleurs différentes.