20 juin 2010

Guide de Delphi: Manipulation des composants

IX. Manipulation des composants

Votre connaissance du langage Pascal augmentant, il est désormais possible de nous attaquer à un morceau de choix : l'utilisation des composants. Par composant, on entend ici en fait les fiches, qui sont (techniquement seulement) des composants, ainsi que tous les composants que vous pourrez être amenés à manipuler.

Ce chapitre sera encore un peu théorique et approfondira des notions déjà survolées auparavant. Vous apprendrez ainsi plus en détail ce que sont les composants, comme on les utilise, comment on accède à leurs propriétés, ce que sont les événements et comment on les utilise. Quelques exemples tenteront de rompre la monotonie de ce genre de cours. Un mini-projet est proposé vers la fin du chapître.


IX-A. Introduction

Nous avons déjà parlé dés le début du guide de l'interface de vos applications. Ces interfaces seront constituées d'un ensemble bien structuré d'éléments pour la plupart visibles : boutons, cases à cocher, textes, menus... mais aussi d'autres non visibles sur lesquels nous reviendront. Chacun de ces éléments individuels se nomme « composant ». Un composant est en fait une sorte de boite noire qui a plusieurs têtes. La partie la plus facile à saisir d'un composant est assurément son coté visuel, mais ce n'est pas le seul aspect d'un composant : il contient tout un tas d'autres choses telles que des propriétés, des événements et des méthodes. Les propriétés sont des données de types très divers, dont certaines sont des constantes et d'autres des variables. Les événements sont tout autre : ils permettent d'exécuter une procédure dans certaines circonstances en réponse à un evènement concernant le composant (ces circonstances dépendent de l'evènement), comme par exemple lors du clic sur un bouton (vous devez commencer à vous familiariser avec celui-là, non ?). Les méthodes sont tout simplement des procédures et des fonctions internes au composant.

Au niveau du code source, chaque composant est une variable. Cette variable est de type objet. Vous n'avez nul besoin pour l'instant de savoir ce qu'est et comment fonctionne un objet, car ceci fera l'objet d'un long chapitre. Ce qu'il est intéressant pour vous de savoir pour l'instant, c'est qu'un objet ressemble beaucoup (en fait, pas du tout, mais on fera semblant de le croire, hein ?) à un enregistrement (type record) amélioré. Un objet peut contenir des données, comme les record, mais aussi des procédures, des fonctions, et encore certaines autres choses spécifiques aux objets dont nous parlerons plus tard et qui permettent entre auters de faire fonctionner les événements.

Au risque d'avoir l'air de changer de sujet (ce qui n'est pas du tout le cas), avez-vous parfois regardé cette partie du code source de l'unité 'Principale.pas' (fiche vierge créée avec une nouvelle application) située dans l'interface ? Jetez-y un coup d'oeil, vous devriez voir quelque chose du genre :

type
  TForm1 = class(TForm)
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
Cet extrait de code, malgré son apparente complexité, fait bien peu de choses : il déclare un nouveau type (bloc type) nommé 'TForm1' et une variable nommée 'Form1' qui est justement de type 'TForm1'. La déclaration du type 'TForm1' devrait vous rappeler celle d'un type enregistrement (record), mis à part qu'ici on utilise le mot class suivi d'autre chose entre parenthèses. Une autre différence est que des mots réservés (private (« privé » en anglais) et public) sont présents à l'intérieur de cette déclaration, ne vous en occupez surtout pas pour l'instant.

La variable nommée 'Form1' est la fiche. Comprenez par là que cette variable contient tout ce qui a trait à la fiche : sa partie visible que vous pouvez voir en lancant l'application aussi bien que sa partie invisible, à savoir ses propriétés et ses événements. Le type de cette variable 'Form1' est 'TForm1'. Ce type est défini dans l'unité car il est spécifiquement créé pour cette fiche. Tout ajout de composant à la fiche viendra modifier ce type et donc modifiera la variable 'Form1' (mais pas sa déclaration).

Essayez par exemple de poser un bouton sur une fiche vierge. L'extrait de code devient alors :

type
  TForm1 = class(TForm)
    Button1: TButton;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
Le seul changement par rapport au premier code source est la ligne :

Button1: TButton;
Cette ligne anodine déclare en fait une variable « interne » au type 'TForm1' (au même sens que dans un enregistrement). La variable est nommée 'Button1' et est de type 'TButton'. Cette variable est le bouton, c'est-à-dire bien plus que ce que vous voyez à l'écran quand vous lancez l'application. Tout comme la variable 'Form1', la variable 'Button1' contient bien d'autres choses que sa partie visible : ce qui fait fonctionner les composants.


IX-B. Aperçu de la structure interne d'un composant

Il est ici hors de question de rentrer déjà dans les détails de la structure d'un composant, c'est quelque chose que nous verrons plus tard. Il est cependant intéressant pour vous de connaître au moins la constitution théorique d'un composant : c'est quelque chose qui vous aidera mieux, j'en suis certain, à maîtriser ces « boites noires ».

Voici un schémas qui donne une représentation d'un composant :

Vous pouvez voir sur ce schémas qu'un composant a plusieurs aspects regroupés autour d'un coeur central.

  • Un premier aspect, souvent le plus connu, est l'interface (le coté visible) du composant. C'est ainsi que lorsque vous placez un composant 'Button' sur une fiche, vous voyez un bouton apparaître. Ce bouton n'est après tout qu'une image qui est la partie visible d'un très très gros iceberg.
  • D'un autre coté, on voit les propriétés d'un composant. Ces éléments, dont nous avons déjà un peu parlé sont pour une partie présentes dans l'inspecteur d'objets lorsque le composant est sélectionné, et pour l'autre partie utilisables depuis le code source uniquement (nous verrons comment et pourquoi en temps et en heure).
  • Un troisième aspect d'un composant est ses événements. Ces événements donnent simplement la possibilité (et non l'obligation comme certains le croient au début) de réagir en réponse à une intervention sur le composant : clic, double clic, mouvement de souris, appui d'une touche, activation, etc... autant de procédures qui pourront être créées et reliées automatiquement au composant par Delphi. Ces procédures seront exécutées à chaque fois que l'evènement se produira.
  • Le dernier aspect d'un composant est constitué par ses méthodes. On emploie le terme de méthode pour désigner les procédures et fonctions internes à un composant (et plus généralement à tout objet). Chacune de ces méthodes a un rôle à jouer dans le composant, et dans lui seul. Les méthodes sont les petits bras articulés qui vont faire le travail à l'intérieur du composant.
Afin d'illustrer tout cela, je vais reprendre un exemple trouvé dans l'aide en ligne d'un autre langage (Visual Basic pour ne pas le citer...) : celui d'un ballon. Notre ballon est un objet, et dans notre cas un composant (c'est plus qu'un simple objet). Reprenons maintenant les 4 aspects d'un composant en pensant à notre ballon.

  • L'interface, ici, sera évidemment un morceau de caoutchouc. Mais n'importe quel bout de caoutchouc n'est pas un ballon, il a une forme, une composition, certaines particularités physiques qui font un ballon d'un morceau de caoutchouc. Nous allons pouvoir modifier ce ballon « virtuel » à volonté.
  • C'est ici qu'interviennent les propriétés : couleur du ballon, contenu du ballon (air, hélium, eau, ...), gonflé ou dégonflé, etc... Ces caractéristiques vont pouvoir être règlées pour nous permettre d'avoir le ballon dont nous aurons besoin : ballon rouge gonflé à l'hélium par exemple.
  • Comme evènement, je prendrai la rencontre fortuite de notre ballon et d'une épingle : c'est un evènement. Dans la réalité, notre ballon n'aurait pas d'autre choix que d'éclater : se dégonfler en émettant un bruit d'explosion. Eh bien, pour notre ballon virtuel, c'est la même chose : il va nous falloir changer l'état de notre ballon (changer la valeur de la propriété « gonflé » et emettre un son). Prenons un exemple moins barbare : la personne qui tient le ballon le lâche. Plusieurs possibilités sont à envisager alors : si le ballon est gonflé d'hélium, il va s'envoler, si il est vide, il va tomber. Nous devrons gèrer plusieurs scénarios et donc faire une analyse de l'état de notre ballon avant de le modifier.
  • Les méthodes, enfin, sont tout ce qui va nous permettre de modifier notre ballon : le faire monter, le faire tomber, lui faire emettre un bruit d'explosion, se dégonfler, se gonfler (même si en réalité ce n'est pas physiquement très correct, personne ne sera présent dans votre programme pour gonfler les ballons !).

IX-C. Manipulation des propriétés d'un composant

Cette partie vous explique les moyens de modifier les propriétés d'un composant. Ces moyens sont au nombre de deux. Delphi, tout d'abord, vous propose de faire ces modifications de façon visuelle. Le code Pascal Objet, d'autre part, vous permet de faire tout ce que fait Delphi, et bien d'autres choses.


IX-C-1. Utilisation de Delphi

Delphi est un fantastique outil de programmation visuelle, vous ais-je dit au début de ce guide. C'est le moment d'expliquer comment tirer partie de cela : Delphi vous propose divers outils pour modifier facilement les propriétés des composants.

Lorsque vous avez une fiche sous Delphi, vous pouvez la redimensionner et la déplacer à volonté : vous manipulez sans le savoir les quatre propriétés nommées 'Height', 'Width', 'Top' et 'Left' de cette fiche. Ces propriétés, vous pouvez également les modifier depuis l'inspecteur d'objets, qui a l'avantage de présenter d'autres propriétés non accessibles ailleurs. C'est la même chose lorsque vous placez un composant sur une fiche : vous pouvez le dimensionner à volonté sur la fiche comme dans un logiciel de dessin, mais vous pouvez aussi utiliser l'inspecteur d'objets pour modifier les mêmes propriétés que pour la fiche.

Pour déplacer ou dimensionner une fiche, il vous suffit de le faire comme n'importe quelle autre fenêtre sous Windows. Pour dimensionner un composant, il faut le sélectionner d'un simple clic, puis déplacer les poignées (petits carrés noirs tout autour du composant) avec la souris. Pour déplacer un composant, laissez faire votre intuition : prenez le composant à la souris en cliquant une fois dessus et en maintenant le bouton gauche de la souris, puis déplacez-le. Pour le poser, relâchez simplement le bouton de la souris. Un autre moyen consiste à sélectionner le composant, puis à maintenir enfoncée la touche 'Ctrl' du clavier tout en utilisant les fléches de direction, ce qui déplace la sélection d'un pixel dans l'une des 4 directions désignée.

Au niveau de la sélection, vous pouvez sélectionner plusieurs composants en en sélectionnant un premier, puis en sélectionnant les autres en maintenant la touche 'Shift' ('Maj' en français) du clavier. Vous pouvez également dessiner un rectangle avec la souris en cliquant et en maintenant enfoncé le bouton de la souris à un emplacement sans composant, puis en allant relâcher le bouton à l'angle opposé du rectangle : tous les composants ayant une partie dans ce rectangle seront sélectionnés. Pour annuler une sélection, appuyez sur 'Echap' jusqu'à ce que toutes les poignées de sélection aient disparu.

En ce qui concerne les tailles et positions des éléments, vous disposez également sous Delphi d'une palette d'alignements prédéfinis accessible par le menu « Voir », choix « Palette d'alignement ». Cette palette permet, en sélectionnant par exemple plusieurs boutons, de répartir également l'espace entre ces boutons, de centrer le groupe de boutons, et bien d'autres choses que vous essaierez par vous-même.

Pour les autres propriétés, il est nécessaire d'utiliser l'inspecteur d'objets. Vous pouvez directement modifier une propriété commune à plusieurs composants en les sélectionnant au préalable : l'inspecteur d'objets n'affichera alors que les propriétés communes à tous les composants sélectionnés.

Une propriété des plus importantes dont vous devez maintenant apprendre l'existence et le fonctionnement est la propriété 'Name' ('Nom' en français). Cette propriété est une propriété spéciale utilisable uniquement sous Delphi et qui décide du nom de la variable qui stockera le composant. Prenons par exemple la seule fiche d'un projet vide : sa propriété 'Name' devrait être « Form1 » (voir capture d'écran ci-dessous).

Vous vous souvenez certainement de l'extrait de code dont nous avons parlé dans l'introduction, cet exemple où était déclarée une variable 'Form1'. C'est la propriété 'Name' qui décide du nom de cette variable. Changez donc ce nom en un nom plus adéquat, tel « fmPrinc » ('fm' comme 'Form': habituez-vous dés à présent à rencontrer beaucoup de ces abréviations en deux lettres minuscules désigant le type de composant et donc différentes pour chaque type de composant. je vous conseille d'utiliser ces abréviations dés que vous les connaissez, ou de créer les vôtres et de vous y tenir absolument). Examinez ensuite le code source, le voici tel qu'il devrait être :

type
  TfmPrinc = class(TForm)
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  fmPrinc: TfmPrinc;
Vous voyez que le nom de la variable à changé, mais aussi que le type utilisé a aussi changé. Le nom de ce type est en fait généré en fonction du nom que vous indiquez pour la propriété 'Name' : un T majuscule est simplement ajouté devant pour signifier que c'est un Type. habituez-vous à rencontrer ce T majuscule dans beaucoup de situations, et même si possible à l'utiliser vous-mêmes comme je le fais dans le guide et en dehors.

Placez maintenant trois boutons sur la fiche. L'extrait de code devrait maintenant être :

type
  TfmPrinc = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  fmPrinc: TfmPrinc;
Vous avez certainement remarqué que trois lignes ont fait leur apparition, déclarant chacune une variable « interne » au type 'TfmPrinc'. Chacune de ces variables est un des boutons que vous venez de placer sur la fiche. Le type employé pour chacune des trois variables est 'TButton', qui est le type de composant utilisé pour les boutons. Renommez ces trois boutons (toujours depuis l'inspecteur d'objets, jamais depuis le code source !) en les nommant à votre convenance, et en utilisant comme abréviation de deux lettres : 'bt'. Voici le code source qui vous indique les noms que j'ai personnellement utilisés.

type
  TfmPrinc = class(TForm)
    btBonjour: TButton;
    btSalut: TButton;
    btCoucou: TButton;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  fmPrinc: TfmPrinc;
Il n'est pas encore très important pour vous de connaître cette partie du code source, mais il est tout de même essentiel de savoir que cette propriété 'Name' désigne un nom de variable et doit donc contenir un identificateur valide, respectant la règle du préfixe de deux minuscules désignant le type de composant. Nous allons encore creuser un petit peu le sujet en créant une procédure répondant à un évènement (comme vous l'avez certainement déjà fait plusieurs fois) : double-cliquez sur l'un des trois boutons sous Delphi, une procédure est alors générée. N'écrivez rien dans cette procédure, ce n'est pas l'objet ici. Retournez plutôt voir le morceau de code déclarant le type 'TfmPrinc'. Vous devez voir une nouvelle ligne.

type
  TfmPrinc = class(TForm)
    btBonjour: TButton;
    btSalut: TButton;
    btCoucou: TButton;
    procedure btBonjourClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  fmPrinc: TfmPrinc;
Cette ligne déclare la procédure nommée 'TfmPrinc.btBonjourClick', mais en ne mettant qu'une partie du nom : 'btBonjourClick'. C'est en fait le vrai nom de la procédure. Jusqu'ici, je vous ai laissé croire (mais qui viendra s'en plaindre ?) que 'TForm1.btBonjourClick' était le nom de la procédure, ce qui est impossible puisque ce n'est pas un identificateur. 'btBonjourClick', en revanche, est un identificateur valide. Quid alors de 'TfmPrinc.' ? La réponse est assez simple : c'est le nom « qualifié » de la procédure. Ce nom comporte le nom du type auquel il appartient : 'TfmPrinc', suivi d'un point (.) pour indiquer qu'on va maintenant désigner un élément intérieur à ce type, à savoir ici la procédure nommée 'btBonjourClick'. C'est pour cela que la déclaration à l'intérieur du type 'TfmPrinc' ne comporte pas ce préfixe ('TfmPrinc.') : on est en fait déjà à l'intérieur du type !

Un autre fait intéressant qu'il vous faut connaître est que dans cette partie du code source que nous venons d'étudier succintement, tous les composants, ainsi que toutes les procédures associées à des événements seront déclarées automatiquement par Delphi. La gestion de cette partie du code source incombe exclusivement à Delphi, vous n'aurez donc pas le droit d'y toucher (sous peine de faire des bétises parfois irréparables). Cela ne vous épargne pas de connaître l'existence et de comprendre ce qui s'y trouve. Nous aurons au cours du guide l'occasion de revenir sur cette partie.

Pour revenir sur un terrain plus stable, de nombreuses autres propriétés sont disponibles dans l'inspecteur d'objets. Leur nombre et leur diversité peut effrayer, mais c'est un gage de puissance et de flexibilité des composants. Pour l'instant, nous n'allons pas manipuler ces propriétés, puisque vous l'avez normalement déjà fait au fur et à mesure des autres chapitres du guide, mais si le coeur vous en dit, n'hésitez pas à vous lancer et à vous tromper : en cas d'erreur, fermez le projet sans enregistrer, puis créez-en un nouveau !


IX-C-2. Utilisation du code source

L'utilisation de l'inspecteur d'objets est certes intuitive, facile et sympathique, mais d'une efficacité très limitée. Voici les limites principales :

  • Certaines propriétés sont inaccessibles depuis l'inspecteur d'objets.
  • On ne peut pas modifier les propriétés pendant l'exécution de l'application.
  • On ne peut manipuler que certains composants (nous verrons cela bien plus tard : certains composants, du fait de leur absence dans la palette des composants, ne peuvent être manipulés que depuis le code source de l'application).
De plus, il ne faut pas oublier, mais cela, vous ne le savez peut-être pas encore si vous êtes novice en programmation, que l'inspecteur d'objets est une facilité offerte aux programmeurs, mais qu'avant l'existence de Delphi, il fallait bien faire tout ce que fait maintenant l'inspecteur d'objet, et le faire depuis le code source : c'était parfois pénible, souvent fastidieux et source d'erreurs.

Il paraît donc inconcevable de se limiter à l'inspecteur d'objets pour la manipulation des propriétés : c'est très souvent dans le code Pascal d'une application que s'effectueront le plus grand nombre de manipulations de propriétés.

Une propriété se manipule, selon les cas, comme une variable ou une constante. En fait, une propriété est un élément spécial dont vous n'avez pour l'instant pas à connaître les détails, mais qui permet la lecture des données (on peut lire la valeur de la propriété, l'écriture de ces données (on peut modifier la valeur de cette propriété) ou les deux à la fois. La presque totalité des propriétés est soit en lecture seule soit en lecture/écriture.

  • Les propriétés en écriture seule sont rarissimes, vous n'en rencontrerez probablement jamais, l'intérêt de ce genre de propriété reste à prouver.
  • Les propriétés en lecture seule sont fréquentes : elles permettent de se renseigner sur l'état d'un composant, sans pouvoir agir sur cet état (Dans l'aide de Delphi, ces propriétés sont souvent signalées par une petite flèche bleue). Ces propriétés se manipulent comme n'importe quelle constante.
  • Les propriétés en lecture/écriture sont les plus courantes : elles permettent tout autant de s'informer que de modifier l'état du composant en modifiant la valeur de la propriété.
Pour utiliser une propriété d'un composant dans du code Pascal, il faut séparer deux cas, selon qu'on est ou pas dans une procédure interne à la fiche qui contient le composant.


  • Si on est dans une procédure interne à la fiche qui contient ce composant (le composant et la procédure dans laquelle on souhaite manipuler une propriété de ce composant doivent être déclarés dans le même type), ce qui est le cas le plus fréquent, il faut écrire


    1. le nom du composant auquel elle appartient (le nom de la variable qui stocke ce composant : cette variable, dans notre cas, est interne au type qui est utilisé pour déclarer la variable qui stocke la fiche)
    2. un point (.)
    3. le nom de la propriété
    Ainsi, pour désigner la propriété 'Caption' d'un bouton nommé (la valeur de la propriété 'name' est le nom du composant) 'btBonjour', on devra écrire :
    
    btBonjour.Caption
    On pourra utiliser cette expression comme une variable puisque la propriété 'Caption' est en lecture/écriture pour les boutons (composants de type 'TButton').


  • Dans les autres cas, c'est-à-dire dans les procédures non internes à la fiche, mais présentes dans la même unité, ou dans les procédures d'autres unités, il faudra en écrire un peu plus. C'est ici une notion des plus importantes : depuis l' « intérieur » de la fiche (c'est-à-dire dans une procédure interne à cette fiche), on peut utiliser tout ce que contient la fiche. Lorsqu'on est à l'extérieur de la fiche, il va d'abord falloir y entrer, puis ce sera le même procédé que ci-dessus. Voici ce qu'il faudra écrire :


    1. le nom de la fiche, c'est à dire le nom de la variable qui contient la fiche (c'est également le contenu de la propriété 'Name' de la fiche)
    2. un point (.)
    3. le nom du composant auquel elle appartient (le nom de la variable qui stocke ce composant : cette variable, dans notre cas, est interne au type qui est utilisé pour déclarer la variable qui stocke la fiche)
    4. un point (.)
    5. le nom de la propriété
    Ainsi, depuis une autre fiche par exemple, pour désigner la propriété 'Caption' d'un bouton nommé 'btBonjour' d'une fiche nommée 'fmPrinc', on devra écrire :
    
    fmPrinc.btBonjour.Caption
Pour résumer ces divers cas qui peuvent paraître compliqués au premier abord, il faut simplement s'assurer, lorsqu'on veut utiliser un élément, que ce soit un composant ou une propriété, qu'on y a accès directement, c'est-à-dire sans accèder à une (autre) fiche. Beaucoup de choses dans le style de programmation employé sous Delphi sont conçues sur ce principe : pour accèder à une information écrite sur un papier, on doit d'abord trouver ce papier en ouvrant le classeur qui le contient, et aussi parfois trouver le classeur dans une armoire, trouver le bureau qui contient l'armoire... ces dernières étapes étant bien entendues inutiles si on a déjà le classeur ouvert devant soi lorsqu'on a besoin de l'information sur la feuille de papier !

Avant de passer aux incontournables exemples (je sens que certains d'entre vous en auront absolument besoin), je tiens à revenir sur ce point qui apparaît pour séparer les noms de fiches, de composants et de propriétés. Le point, en Pascal Objet, est un opérateur qui permet d'accèder au contenu de ce qui le précède : lorsqu'on écrit 'fmPrinc.', on va s'adresser ensuite à quelque chose d'interne à fmPrinc. De même, lorsqu'on écrit 'btBonjour.', on s'adresse ensuite à quelque chose d'interne au bouton. Lorsqu'on est à l'extérieur de la fiche 'fmPrinc', on doit écrire 'fmPrinc.btBonjour.' pour s'adresser à quelque chose d'interne au bouton. Pour accèder à la propriété 'Caption' de la fiche, il suffira d'écrire alors 'fmPrinc.Caption'. Lorsqu'on sera en plus à l' « intérieur » de cette fiche, 'Caption' désignera directement la propriété 'Caption' de la fiche.

Pour revenir à notre exemple de la feuille de papier et du classeur, imaginons tout d'abord que vous ayez le classeur devant vous : vous indiquerez « page824.information75 » pour accèder à l'information sur la page (en admettant évidemment que vous connaissiez le nom du papier et le nom de l'information, ce qui revient à savoir quelle information vous cherchez et sur quel papier vous comptez la trouver. Imaginons maintenant que vous soyez dans le mauvais bureau : vous devrez alors écrire tout cela : « mon_bureau.armoire_archives.classeur_clients.page824.information75 ». C'est, je vous l'accorde, parfois un peu pénible, mais c'est la clé d'une bonne organisation : vous saurez toujours comment trouver l'information désirée sous Delphi à partir du moment où vous saurez où elle est.

Venons-en à un exemple très pratique : créez un projet vierge et placez deux boutons sur la feuille principale. Nommez la feuille « fmPrinc » et les deux boutons : « btTest1 » et « btTest2 » (utilisez la propriété 'name' en sélectionnant la fiche, puis chaque bouton). Donnez une tête présentable à tout cela en modifiant également les propriété 'Caption', 'Width', 'Height', 'Top' et 'Left' des boutons et de la fiche (selon votre humeur du moment). Double-cliquez sur le bouton 'btTest1', ce qui va générer une procédure locale à la fiche 'fmPrinc', nommée 'btTest1Click' (rien de très nouveau jusqu'à maintenant, mis à part que vous pouvez certainement remarquer que le nom de cette procédure ne doit rien au hasard, puisqu'il est composé du nom du composant et de 'Click'). Faites de même pour le second bouton ('btTest2').

Nous allons modifier les propriétés 'Caption' des deux boutons et de la fiche depuis ces procédures, ce qui signifie qu'un clic sur l'un des boutons changera les textes affichés sur les boutons ou dans le titre de la fenêtre. Utilisez l'extrait de code ci-dessous pour complèter vos procédures :

procedure TfmPrinc.btTest1Click(Sender: TObject);
begin
  Caption := 'Titre de fenêtre';
end;

procedure TfmPrinc.btTest2Click(Sender: TObject);
begin
  btTest1.Caption := 'Titre de bouton 1';
  btTest2.Caption := 'Titre de bouton 2';
end;
Exécutez l'application et cliquez sur chacun des boutons en prettant attention à l'effet produit : un clic sur l'un des boutons change le titre de la fenêtre, ce qui correspond à la propriété 'Caption' de la fiche. Un clic sur l'autre bouton, en revanche, modifie le texte affiché sur chacun de ces deux boutons : leur propriété 'Caption' est modifiée. Notez que cliquer plusieurs fois sur les boutons n'a pas d'effet visible puisque les valeurs des propriétés ne changent plus. Malgré cela, le code des procédures est bien exécuté.

Revenons maintenant à l'extrait de code ci-dessus : la ligne :

Caption := 'Titre de fenêtre';
modifie la propriété 'Caption' de la fiche. Comme la procédure fait partie de la fiche (deux moyens de s'en assurer : la procédure est déclarée à l'intérieur du type 'TfmPrinc' plus haut dans le code, et le nom de la procédure est précédé de 'TfmPrinc.'), nous n'avons rien à faire de plus pour accèder à l'intérieur de la fiche : on utilise directement 'Caption', et ceci comme une variable, à laquelle on affecte une chaîne de caractères. L'exécution de cette petite ligne va suffir pour changer le titre de la fenêtre en ce que nous voudrons. Le code de la deuxième procédure, comme vous l'avez vu en exécutant l'application, modifie le texte des deux boutons. Pour cela, on a utilisé deux instructions similaires, une pour chaque bouton. Pour modifier la propriété 'Caption' du bouton 'btTest1', on doit d'abord s'adresser au composant 'btTest1', placé sur la fiche. On est sur la fiche, donc on s'adresse directement au composant, puis à la propriété :

btTest1.Caption := 'Titre de bouton 1';
Passons maintenant à quelque chose de plus intéressant. Créez un nouveau projet, puis placez sur la seule fiche deux composants : un composant Edit (zone d'édition, l'icône est ) et un bouton classique. Placez-les pour obtenir quelque chose du style :

Modifiez les propriétés Caption de la fiche et du bouton en mettant respectivement « Perroquet » et « Répeter ». Nommez la fiche 'fmPrinc', le bouton 'btRepeter' et la zone d'édition (on l'appelera désormais "l'Edit" pour faire plus court) 'edMessage'. La propriété 'Text' (de type string) d'un composant Edit (type TEdit) permet de lire et écrire le texte contenu (mais pas forcément entièrement affiché, faute de manque de place à l'écran) dans la zone d'édition. Utilisez l'inspecteur d'objets pour effacer le texte indésirable qui doit s'y trouver : « edMessage » (nous expliquerons plus tard pourquoi ce texte a changé quand vous avez modifié la propriété 'Name').

Votre interface doit maintenant être quelque chose du style :

Evitez maintenant de visualiser dans votre navigateur le code source présent ci-dessous, réalisez l'exercice et regardez ensuite la solution pour éventuellement vous corriger. Le principe du programme simple que nous allons créer va être de répeter ce qui est écrit dans la zone d'édition lors d'un clic sur le bouton 'Répeter'. Générez la procédure répondant au clic sur le bouton (double-clic sur celui-ci). La première instruction va consister à attribuer à une variable chaîne le texte écrit dans la zone d'édition. Déclarez une variable 'Msg' de type Chaîne. Tapez maintenant (en tant que première instruction) l'instruction qui affecte à la variable 'Str' la valeur de la propriété 'Text' de la zone d'édition (nommée 'edMessage'). La deuxième instruction, plus simple consiste à afficher ce message par un moyen habituel déjà souvent utilisé.

Vous avez terminé ? Voici la solution :

procedure TfmMain.btRepeterClick(Sender: TObject);
var
  Msg: string;
begin
  Msg := edMessage.Text;
  ShowMessage(Msg);
end;
La seule difficulté est dans la première ligne : la propriété 'Text' du composant 'edMessage' s'obtient par 'edMessage.Text', car la procédure est interne à la fiche, et peut donc s'adresser directement aux composants. Notez que l'on aurait pu faire plus court en se passant de la variable Msg et en écrivant l'instruction unique :

ShowMessage(edMessage.Text);
car edMessage.Text est bien de type string et peut donc être transmis en tant que paramètre à 'ShowMessage'.

Nous avons fait la manipulation dans les deux sens : écriture (exemple avec 'Caption') et lecture (à l'instant). Dans un programme Pascal Objet sous Delphi, nous manipulerons les propriétés à longueur de temps. Il est donc primordial de vous entraîner un peu. Voici un autre exemple plus complexe.

Créez un projet vierge, placez un composant CheckBox (Case à cocher, son icône est ), une zone d'édition, et un bouton. Le but de cet exercice est de contrôler l'état de la case à cocher avec la zone d'édition et le bouton. Avant cela, il vous faut une propriété de plus : la propriété 'Checked' de type Booléen des cases à cocher (type TCheckBox) permet de lire ou fixer l'état de la case (cochée ou non cochée). Lorsque 'Checked' vaut 'true', la case apparaît cochée, et non cochée lorsque 'Checked' vaut 'false'. En outre, la propriété 'Caption' décide du texte présent à coté de la case. Ceci permet de (dé)cocher la case en cliquant sur ce texte (fonctionnement classique de Windows, rien de nouveau ici).

Nommez la case à cocher 'cbTest', le bouton 'btModif' et la zone d'édition 'edEtat', la fiche principale (et unique), comme presque toujours, sera nommée 'fmPrinc'. Donnez des textes acceptables à tout cela pour obtenir l'interface ci-dessous :

Générez la procédure de réponse au clic sur le bouton. Nous allons réaliser deux tests similaires : nous comparerons le texte écrit dans la zone d'édition à 'false' ou 'true', et s'il est égal à l'un des deux, la case à cocher sera mise à jour en conséquence. En outre, le texte de la zone d'édition sera effacé à chaque clic sur le bouton pour qu'on puisse y retaper facilement du texte.

Voici le code source :

procedure TfmPrinc.btModifClick(Sender: TObject);
begin
  if edEtat.Text = 'false' then
    cbTest.Checked := false;
  if edEtat.Text = 'true' then
    cbTest.Checked := true;
  edEtat.Text := '';
end;
Cet exemple montre bien à quel point on peut assimiler les propriétés à des variables (mais pas les confondre, attention, ce ne sont PAS des variables). 'edEtat.Text' par exemple est d'abord utilisé deux fois en lecture pour être comparé à une autre chaîne, puis en écriture en étant fixé à la chaîne vide. La propriété 'Checked' de 'cbTest' est modifiée comme le serait une variable booléenne, à ceci près qu'en plus, un effet visuel est produit : la coche apparait ou disparait.

D'innombrables exemples seront présents dans la suite du guide, puisque la manipulation des composants est un passage obligé sous Delphi. Pour l'instant passons à un sujet très lié et non moins intéressant : la manipulation des méthodes des composants.


IX-D. Manipulation des méthodes d'un composant

Il n'est pas ici question, comme pour les propriétés, d'utiliser l'interface de Delphi : tout ce qui touche aux méthodes touche à l'exécution des applications, c'est-à-dire que toutes les manipulations s'effectuent depuis le code source.

Nous avons déjà un peu parlé des méthodes, en disant qu'il s'agissait de procédures ou fonctions internes aux composants. Ces méthodes font tout le travail dans un composant. Même la manipulation des propriétés cache en fait la manipulation des méthodes, mais nous reparlerons de ca plus tard. Pour l'instant, il s'agit de savoir utiliser ces méthodes. Le principe est des plus simples pour utiliser une méthode : on s'en sert comme les propriétés, en s'assurant simplement qu'on y a accès en la précédant par le nom du composant auquel elle appartient. Si ces méthodes ont des paramètres, on les donne comme pour des procédures ou des fonctions.

Un petit exemple amusant pour commencer : créez un projet vierge et placez deux boutons 'Button1' et 'Button2' sur la fiche principale (et toujours unique, mais cela ne va plus durer très longtemps). Dans la procédure réagissant au clic sur 'Button1', entrez l'instruction ci-dessous :

Button2.Hide;
puis exécutez l'application. Lors d'un clic sur 'Button1', 'Button2' disparaît : il se « cache » ('Hide' en anglais). Vous remarquerez qu'aucune propriété n'a été modifiée par notre code puisqu'aucune affectation n'est écrite. Pourtant, la méthode 'Hide' des composants Button cache le bouton, et effectue donc en quelque sorte la même chose que si l'on avait changé la propriété 'Visible' du bouton (propriété qui décide si un composant est visible ou non).

Puisqu'on en est à l'amusement, mettez le code ci-dessous à la place du précédent :

Hide;
Sleep(5000);
Show;
En exécutant cet exemple, l'application « disparait » pendant 5 secondes (5000 millisecondes, d'où le 5000 dans le code) puis réapparait comme avant. L'explication est la suivante : la méthode 'Hide' nommée directement s'applique à la fiche : la fiche se cache donc. Puis la procédure 'Sleep' fait « dormir » l'application pendant 5 secondes. Enfin, après ces cinq secondes, la méthode 'Show' fait réapparaître la fiche.

Une méthode des fiches qui ne manquera pas de vous intéresser est la méthode 'Close'. Cette méthode ferme la fiche dont on appelle la méthode 'Close'. Il est alors intéressant de savoir que l'application se termine lorsque l'on ferme la fiche principale. Essayez donc, à la place du code précédent un peu fantaisiste, de mettre l'instruction unique :

Close;
Exécutez l'application. Un clic sur le bouton ferme la fiche, et donc quitte l'application : vous venez de créer votre premier bouton 'Quitter' !

Passons à un exemple plus croustillant : vous voyez régulièrement sous Windows des listes d'éléments. Un moyen de créer ces listes sous Delphi (on parle ici des listes simples, et non pas des listes complexes telles que la partie droite de l'explorateur Windows) passe par le composant ListBox (type TListBox, dont l'icône est ). Sur le projet déjà entamé, placez un composant ListBox, redimensionnez le tout pour obtenir ceci :

Commençons par le plus simple : assurez-vous que le bouton 'Quitter' quitte effectivement l'application (en générant la procédure répondant au clic et en écrivant comme unique instruction « close »). Le bouton 'Lancer le test' nous permettra d'effectuer divers tests de plus en plus élaborés.

Avant cela, un petit complément sur les ListBox s'impose. Les ListBox (zones de liste) sont composées d'un cadre, dans lequel apparaissent un certain nombre de lignes. Ces lignes comportent le plus souvent du texte. Un composant ListBox comporte une propriété 'Items' qui centralise tout ce qui a trait à la gestion des éléments de la liste. 'Items' n'est pas une simple donnée : c'est un objet. Pour l'instant, nul besoin encore pour vous d'en connaître plus sur les objets, sachez simplement qu'un objet se manipule comme un composant : il possède propriétés et méthodes. Cette propriété 'Items' permet donc l'accès à d'autres propriétés et méthodes internes. La méthode 'Add' de 'Items' permet d'ajouter une chaîne de caractères à une zone de liste. Elle admet un unique paramètre de type chaîne de caractères qui est ajouté à la liste. Pour appeler cette méthode, on doit écrire :

composant_liste.Items.Add(Chaîne)

Utilisons donc cette méthode pour ajouter un élément dans la liste lors d'un clic sur le bouton 'Lancer le test'. Servez-vous de l'extrait de code ci-dessous pour complèter la procédure de réponse au clic sur ce bouton :

procedure TForm1.Button1Click(Sender: TObject);
begin
  ListBox1.Items.Add('Ca marche !');
end;
La seule instruction présente dans cette procédure s'adresse à 'ListBox1' (que nous aurions dû renommer en 'lbTest' par exemple). Elle ajoute la chaîne « Ca marche ! » à la liste à chaque clic sur le bouton (essayez !).

Cet exemple ne permet cependant pas de vider la liste. Pour cela, il faudrait utiliser la méthode 'Clear' de 'Items'. Essayez donc de remplacer l'ancien code par celui-ci :

ListBox1.Items.Clear;
ListBox1.Items.Add('Ca marche aussi !');
La liste est vidée avant l'ajout de la chaîne, ce qui donne un effet visuel moins intéressant, mais a pour mérite de vous avoir appris quelque chose.

Maintenant que vous connaissez le nécessaire, passons à un exercice grandeur nature : un mini-projet.

Mini-projet n°2

(voir Indications, Solution et Téléchargement)

L'objectif de ce mini-projet est de vous faire créer une application exploitant les notions que vous venez de découvrir, à savoir :

  • Gestion de composants, propriétés et méthodes.
  • Utilisation d'une zone d'édition, d'une liste.
Si vous avez suivi le début de ce chapitre, vous avez toutes les connaissances nécessaires à la création de ce mini-projet. Des indications, une solution guidée pas à pas ainsi que le téléchargement sont proposés ici. Voici maintenant l'énoncé du problème.

Le but de ce projet est, à partir de l'interface que voici, composée d'une liste, d'une zone de saisie et de trois boutons, de permettre les actions mentionnées ci-dessous :

  • Ajout de texte dans la liste : pour cela, on entre le texte dans la zone d'édition puis on clique sur le bouton. Le texte est alors ajouté à la liste et la zone de saisie est vidée (le texte y est effacé).
  • Effacement de la liste : en cliquant sur le bouton adéquat, le contenu de la liste sera effacé.
  • Fermeture de l'application, en cliquant sur 'Quitter'.
Bon courage !


IX-E. Evénements

Depuis le début de ce guide, je ne cesse de vous parler des événements, qui sont d'une importance considérable. En effet, les événements sont le coeur du style de programmation employé sous Delphi : les événements ne sont pas seulement une possibilité offerte aux programmeurs, mais presque un passage obligé puisque c'est le style de programmation privilégié sous un environnement graphique tel que Windows. Nous consacrerons donc un paragraphe entier à l'étude du style de programmation à l'aide d'événements, puis nous passerons à leur utilisation en pratique.


IX-E-1. Style de programmation événementiel

Aux débuts de l'informatique, l'utilisation des ordinateurs se résumait souvent à concevoir un programme, le coder, l'exécuter et traiter les résultats. Depuis, l'apparition des PC a permis de se ménager une certaine liberté quant à l'utilisation d'un ordinateur. L'apparition de Windows, dans le monde du PC que vous connaissez, a favorisé un style non directif : on n'oblige en aucun cas l'utilisateur à suivre une démarche précise et prévisible : on sait seulement qu'il sera susceptible d'accomplir un certain nombre d'actions, auxquelles on est apte à réagir. Pour bien marquer la différence entre les deux grands styles de programmation, à savoir la programmation directive et la programmation événementielle, choisissons de réfléchir à un petit programme destiné à calculer des additions.

Premier programme, en style directif : vous demandez un premier nombre, puis un second, puis vous donnez le résultat. L'utilisateur ne peut rien faire d'autre que vous donnez les informations et attendre le résultat, il n'a aucune liberté.

Second programme, en style non directif : vous donnez à l'utilisateur deux endroits pour rentrer ses deux nombres, un espace ou sera affiché le résultat, et un bouton qui effectuera le calcul. L'utilisateur n'est alors pas astreint à respecter l'ordre normal : il peut entrer un premier nombre, en entrer un second, puis constater qu'il s'est trompé et corriger le premier, quitter sans effectuer d'addition, et calculer le résultat quand bon lui semble. Ce style paraît avantageux mais oblige le programmeur à montrer plus de vigilence : l'utilisateur peut sans le vouloir sauter des étapes dans le processus logique, en oubliant par exemple de donner l'un des deux nombres (évidemment, avec cet exemple, c'est peu vraisemblable, mais imaginez une boite de dialogue de renseignements sur une personne, où l'on omettrait un renseignement indispensable, comme la date de naissance).

Autre exemple directif : la procédure d'installation d'un logiciel : on vous guide dans l'installation divisée en étapes, dont certaines vous proposent quelques choix, mais sans vous permettre de passer directement à la copie des fichiers sans passer par le choix des programmes à installer. Le style directif s'impose alors, et on peut reconnaître son utilité car on voit mal un programme d'installation commencer à choisir les fichiers à installer avant de rentrer un numéro de série pour le logiciel.

Dernier exemple non directif : Windows en lui-même : vous êtes complètement libre, sous Windows, de lancer telle ou telle application, et de faire ainsi ce que vous voulez : à un moment vous travaillerez sous votre tableur préféré, et à l'autre vous travaillerez à l'extermination d'envahisseurs extra-terrestres, deux occupations que vous auriez pu choisir dans l'ordre inverse : vous êtes libre, c'est le style non directif.

Sous Delphi, c'est le style non directif qui est privilégié, avec aussi bien entendu la possibilité de créer un programme très directif. La programmation des applications s'en ressent, et en permier lieu par l'utilisation des événements, ce qui lui donne le nom de programmation événementielle. Ces événements permettent de laisser l'utilisateur libre, et de simplement réagir selon ce qu'il fait : clic sur un bouton, entrée de texte dans une zone de saisie, clic de souris, etc...

Il existe des événements de diverses nature. Un événement s'applique la plupart du temps à un composant (mais aussi parfois à une application entière) : c'est le composant ou la fiche qui déclenche l'événement. Delphi permet d'assigner une procédure à chacun de ces événements, destinée à réagir à l'événement comme il se doit.

Dans une application, de très nombreux événements se produisent. Il n'est pas question de réagir à TOUS ces événements : lorsqu'on n'affecte pas de procédure à un événement, le traitement de l'événement est fait pas Windows. Par exemple, lorsque vous n'affectez pas de procédure à l'événement « clic de la souris » d'un bouton, un clic produit toujours l'effet classique d'un bouton, à savoir l'enfoncement puis le relâchement, mais rien de plus.

Ce qu'il est important de comprendre, c'est qu'on fait généralement fonctionner un programme basé sur une interface en répondant à un éventail d'événements adaptés à l'application. Un programme qui effectue des additions n'a que faire des mouvements du pointeur de la souris, tandis qu'un logiciel de dessin aura tendance à s'intéresser de très près à ces mouvements, transmis au programmeur sous forme d'événements. Nous avons le plus souvent fait fonctionner nos programmes de test en cliquant sur des boutons, ce qui nous a permis d'éxécuter à chaque fois une ou des procédures.

C'est ainsi désormais que nous allons faire « vivre » nos programmes : nous construirons l'interface, puis nous lui donnerons vie en répondant à certains événements ciblés.


IX-E-2. Utilisation des événements

Venons-en maintenant à l'utilisation des événements. Ces derniers sont listés dans l'inspecteur d'objets, dans l'onglet « Evénements ». Comme pour les propriétés, la liste est spécifique à chaque composant : chaque composant, de même que chaque fiche, a sa liste d'événements. Il est possible d'assigner une procédure à un des événements d'un composant en suivant le processus suivant :

  1. Sélectionnez le composant qui déclenche l'événement
  2. Allez dans l'inspecteur d'objets (F11) dans l'onglet 'événements'.
  3. Effectuez un double-clic sur la zone blanche en face du nom de l'événement que vous souhaitez traiter. La procédure sera générée et appelée à chaque occurence de l'événement.
Les événements déjà attachés à des procédures montrent le nom de cette procédure en face de leur nom. Le nom de la procédure est constitué par défaut du nom du composant, puis du nom de l'événement auquel on a retiré le préfixe 'On' présent dans tous les noms d'événements.

Suivant les événements, les paramètres de la procédure changeront. Une procédure d'événement a toujours un paramètre 'Sender' de type 'TObject' que nous décrirons plus tard mais qui ne sert à rien dans une bonne partie des cas.

Une fois que vous disposez de la procédure (son « squelette »), vous pouvez y inscrire des instructions, y déclarer des constantes, des variables. Cependant, il y a certains points sur lesquels vous devez faire attention :

  • Lorsque vous compilez le projet, les procédures vides (sans déclarations ni instructions ni commentaires) associées aux événements sont automatiquement supprimées car parfaitement inutiles. La référence à cette procédure dans l'inspecteur d'objets est également supprimée car la procédure n'existe plus.

  • Pour supprimer une procédure associée à un événement, c'est-à-dire pour ne plus répondre à cet événement, il ne faut pas supprimer la procédure entière car Delphi serait dans l'incapacité de gérer cela. La procédure à suivre est la suivante :


    1. Supprimez toutes les instructions, commentaires et déclarations de la procédure : ne laissez que le squelette, c'est-à-dire la ligne de déclaration, le begin et le end.
    2. A la prochaine compilation, la procédure sera supprimée, de même que la référence à celle-ci dans l'inspecteur d'objets.
A la lumière de ces explications, vous comprenez certainement mieux maintenant le pourquoi de la procédure de réponse au clic sur les boutons : double-cliquer sur un bouton revient à double-cliquer dans l'inspecteur d'objets sur la zone blance associée à l'événement nommé « OnClick ». Essayez pour vous en convaincre.

La quasi-totalité des événements que vous rencontrerez sous Delphi commencent par le préfixe « On » : c'est un signe de reconnaissance des événements. Chaque composant dispose de son propre ensemble d'événements, mais d'un composant à un autre de même type (deux boutons par exemple), la liste des événements disponibles sera la même, mais la réponse (la procédure associée) sera différente.

Par exemple, le type de composant Edit propose l'événement OnChange, alors que les boutons ne proposent pas cet événement. Si vous utilisez plusieurs composants Edit, vous pourrez associer une procédure à l'événement OnChange de chacun.

Permier exemple pratique : dans un projet vierge, posez un composant Edit sur la fiche principale. Dans l'inspecteur d'objets, trouvez l'événement OnChange de ce composant :

Double-cliquez sur la zone blanche : une procédure est générée : le code source devrait en être :

procedure TForm1.Edit1Change(Sender: TObject);
begin

end;
Et entrez l'instruction suivante :

Caption := Edit1.Text;
Lancez l'application et tapez quelque chose dans la zone d'édition : vous devez constater que le texte dans la barre de titre de l'application est désormais le même que celui que vous tapez dans la zone de saisie. L'explication est assez simple : nous avons utilisé l'événement 'OnChange'. Cet événement se produit à chaque fois que le texte de la zone de saisie (propriété 'Text') change. Dés que vous tapez quelque chose, ou effacez du texte, la procédure 'Edit1Change' est appelée. C'est cette procédure qui fait le changement : elle assigne à 'Caption' (propriété de la fiche) la valeur de la propriété 'Text' de la zone d'édition 'Edit1', ce qui permet aux deux textes d'être identiques puisqu'à chaque modification, l'un est fixé identique à l'autre.

Lorsque vous modifiez le texte dans la zone d'édition, cela change tout d'abord la valeur de la propriété 'Text'. Ce changement fait, l'événement 'OnChange' est déclenché, et appelle la procédure qui lui est attaché, qui modifie la barre de titre de l'application. L'impression donnée par ce programme est de pouvoir fixer le titre de l'application : l'utilisateur n'a pas conscience des événements déclenchés par la saisie au clavier.

Difficile de faire un choix parmi les nombreux événements pour vous les montrer... Jettons un oeil à l'événement 'OnMouseMove' : cet événement est déclenché à chaque fois que la souris bouge « au dessus » du composant, en nous transmettant des informations fort utiles, comme les coordonnées de la souris et l'état des touches Shift, Control, Alt et des boutons de la souris. Cet événement a donc tendance à se produire des dizaines de fois par secondes : il faudra bien faire attention à ne pas y mettre d'instructions qui prendront trop de temps d'éxécution, sous peine de résultats assez imprévisibles.

Ajoutez une deuxième zone d'édition au projet précédent, et nommez les deux zones 'edX' et 'edY'. Veillez à dimensionner la fiche pour pouvoir balader la souris sans qu'elle souffre de claustrophobie. Nous allons utiliser l'événement 'OnMouseMove' de la fiche pour suivre les coordonnées de la souris. Allez donc chercher l'événement 'OnMouseMove' de la fiche :

Double-cliquez sur la zone blanche et entrez les deux instructions que vous pouvez voir ci-dessous :

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  edX.Text := 'X = ' + IntToStr(X);
  edY.Text := 'Y = ' + IntToStr(Y);
end;
Exécutez l'application : lorsque vous bougez la souris, les coordonnées relatives à la fiche s'inscrivent dans les deux zones d'édition. Le fonctionnement de l'application est simple : à chaque mouvement de la souris, l'événement 'OnMouseMove' est déclenché et transmet les coordonnées X et Y de la souris. Ces coordonnées sont transformées en chaînes de caractères, ajoutées à un texte de présentation et inscrites dans les deux zones d'édition. Vous remarquerez certainement que lorsque le pointeur passe sur les zones d'édition, les coordonnées ne sont plus mises à jour : le pointeur n'est en effet plus sur la fiche mais sur l'une des zones d'édition contenues dans la fiche.


IX-F. Composants invisibles

Certains composants ont une particularité des plus intéressante : ils sont invisibles. Non pas que leur propriété 'Visible' soit fixée à 'False', non, ils ne sont purement et simplement invisibles. Ces composants, lorsqu'ils sont placés sur une fiche, sont représentés par un petit cadre montrant l'icône qui les représente dans la palette des composants (nous parlerons dans ce guide de pseudo-bouton). Un exemple est "MainMenu" : lorsque vous placez un tel composant sur une fiche, une sorte de bouton est placé sur la fiche, mais si vous lancez immédiatement le projet sans rien modifier, ce bouton n'est pas visible : le composant que vous venez de placer est invisible.

Ce pseudo-bouton placé sur la fiche n'est visible que sous Delphi : c'est une sorte d'interface entre vous et le composant qui est vraiment invisible. Cette interface est faite pour vous permettre de sélectionner le composant, de changer ses propriétés, ses événements. Déplacer ce pseudo-bouton n'a aucun effet sur le composant : il est non visuel et n'a donc pas d'emplacement. Déplacez ces pseudo-boutons si vous le souhaitez pour bien les ranger quelque part sur la fiche si vous le voulez : ils ne seront de toute façon pas visibles à l'exécution de l'application.

L'utilité de tels composants ne paraît pas forcément évidente au néophyte que vous êtes peut-être : n'ai-je pas dit que les composants permettaient de créer une interface ? Et une interface est visible, non ? Et bien comme partout, il y a des exceptions : les composants invisibles rendent de nombreux services. Les plus courants sont ceux présents sur l'onglet 'Dialogs' : ces composants invisibles permettent l'accès à des fenêtres standard de Windows telles que les fenêtres d'ouverture ou d'enregistrement de fichiers, de choix de couleurs : vous pilotez ces fenêtres très simplement via un composant, au lieu de devoir recréer ces fenêtres standards au moyen de fiches. Il n'est pas concevable de montrer ces fenêtres standards à l'intérieur des fiches, ce qui force le choix de composants invisibles. Vous pouvez utiliser ces fenêtres à volonté : les montrer, les cacher, sans que l'utilisateur ne les ait sous les yeux en permanence.

Un dernier composant invisible dont vous devez connaître l'existence est le composant "ImageList". Ce composant, invisible, permet de stocker une liste d'images numérotées. Ce composant seul ne sert à rien, mais certains composants comme les menus, les arbres ou les listes d'icônes en ont absolument besoin. Vous apprendrez donc à vous servir de ce composant au chapitre 8, qui comporte plusieurs parties dédiées au fonctionnement de ces composants invisibles.


IX-G. Imbrication des composants

Contrairement à ce que l'on pourrait penser, placer un ensemble de composants sur une fiche ne suffit pas toujours à constituer une interface viable : il faut parfois constituer des interfaces défiant cette optique. Pensez pour bien comprendre ce point de vue à un classeur à onglets tel que la boite de dialogue d'options de Word : plusieurs pages sont présentes sur la même fiche, la plupart sont cachés pour ne laisser visible que le contenu de l'onglet sélectionné. Tout ceci serait evidemment possible sans regrouper les composants, mais au prix d'efforts et de contraintes dissuasives (il faudrait masquer et afficher manuellement un nombre incroyable de composants à chaque changement d'onglet actif, ce qui serait non seulement long et pénible, mais aussi une source intarissable d'erreurs et de bugs).

Le regroupement des composants est une des facettes de l'imbrication des composants : il est en effet possible d'imbriquer certains composants dans d'autres. Il existe à ce niveau deux familles de composants : ceux qui peuvent contenir d'autres composants, et ceux qui ne le peuvent pas. Parmi ceux qui peuvent en contenir d'autres, ceux auquels on ne pense pas sont... les fiches ! En effet, vous placez depuis le début de ce guide des composants sur les fiches, qui SONT des composants (spéciaux, il est vrai, mais non moins des composants tout à fait dignes de cette appellation). Parmi ceux qui ne peuvent contenir d'autres composants, je citerai les boutons, les cases à cocher, les zones d'édition, les listes, et beaucoup d'autres qui constituent en fait la majorité des composants.

Les composants qui peuvent en contenir d'autres, dits composants « conteneurs », sont assez peu nombreux, mais d'un intérêt majeur : on compte, en dehors des fiches les composants "Panel", "GroupBox", "PageControl" et "ToolBar" pour ne citer que les plus utilisés.

Regrouper les composants au sein de composants conteneurs a plusieurs avantages :

  • Avantage visuel : le composant "Panel" permet par exemple de créer des cadres en relief, mettant en évidence le regroupement des composants.
  • Avantage logique : lorsque vous construirez un classeur à onglet sous Delphi, il sera plus naturel de sélectionner un onglet, puis d'y placer les composants qui devront apparaître lorsque cet onglet est actif. Changez d'onglet sous Delphi et seul le contenu de cet onglet est visible, laissant de coté le reste des composants des autres onglets.
  • Avantage de conception : imaginez ce que serait l'interface d'une fiche si tous ses composants étaient visibles simultanément : ce serait inregardable, un fouillis incompréhensible.
  • Avantage de placement : les composants contenus dans d'autres se déplacent avec leur conteneur, ce qui permet de déplacer facilement tout un ensemble d'un endroit à un autre.
Nous n'allons étudier brièvement ici qu'un de ces composants : le composant "Panel" (qui sera étudié plus en détail au chapitre 8). Ce composant est un conteneur, qui peut donc recevoir d'autres composants. Faites l'expérience : placez un "Panel" sur une fiche, puis placez deux composants identiques (des boutons par exemple), l'un sur la fiche (cliquez pour cela en dehors du "Panel" au moment de placer le bouton) et un sur le "Panel" (cliquez pour cela sur le panel pour placer le bouton. Vous devriez obtenir quelque chose de ce genre :

Essayez maintenant de déplacer le bouton placé à l'intérieur du panel : vous constaterez qu'il vous est impossible de franchir les bords du panel. Le panel constitue en fait une sous-zone de la fiche que vous pouvez agencer comme vous le voulez, et que vous pouvez ensuite placer ou aligner où vous voulez. Le composant Panel a un autre intérêt : avec les propriétés "BevelInner", "BevelOuter", "BevelWidth" et "BorderStyle", il est possible de constituer une bordure personnalisée des plus appréciable. Attention à ne pas tomber dans l'excés à ce niveau, ce qui est en général le propre des débutants, il suffit souvent d'une bordure très fine comme ci-dessous :

Essayez maintenant de déplacer le panel et non le bouton : vous voyez que le bouton reste à l'intérieur du panel, au même emplacement relatif au panel. Fixez maintenant la propriété "Align" du panel à "alBottom" : le panel est maintenant bloqué en bas de la fiche. Redimensionnez la fiche, et vous aurez l'agréable surprise de voir votre panel rester en bas de celle-ci, et se redimensionner pour coller parfaitement au bas de la fenêtre, comme sur la capture ci-dessous :

Grâce à un tel composant, il sera facilement possible de créer des boutons restant à leur place en bas d'une fenêtre, quelle que soit la taille de celle-ci. Même si dans certains cas l'utilisateur n'aura pas à redimensionner la fiche, vous, le programmeur, aurez peut-être besoin de le faire, sans avoir à vous soucier des détails du style emplacement des boutons.

Pour plus de détails sur l'utilisation des composants conteneurs, consultez les parties du chapitre 8 consacrées à ces composants.


IX-H. Types de propriétés évolués

Cette partie présente succintement certains types particuliers de propriétés, ainsi que des caractéristiques importantes. Les notions abordées ici ne le sont que pour permettre d'aborder plus facilement le chapitre 8. Ces notions sont expliquées en détail dans le long chapitre consacré aux objets.


IX-H-1. Propriétés de type objet

Petit retour obligé ici sur les propriétés. La connaissance de l'utilisation des méthodes étant indispensable ici, ce paragraphe a volontairement été placé en dehors de celui réservé aux propriétés.

La plupart des propriétés que vous avez vu jusqu'ici sont de types connus par vous, à savoir des types simples comme des booléens, des entiers, des chaînes de caractères ou des types énumérés. Certaines propriétés, cependant, sont plus complexes car elles sont elles-mêmes de type objet ou même composant. Ces propriétés affichent dans l'inspecteur d'objets une valeur entre parenthèses du style "(TStrings)" et un bouton . La propriété "Items" des composants "ListBox", que vous avez déjà eu l'occasion de manipuler, en est un bon exemple : en cliquant sur le petit bouton, on accède à une interface spécifique d'édition de la propriété.

Ces propriétés sont certes un peu moins faciles à manipuler que les propriétés simples, mais cela veut simplement dire qu'elles ont plus de puissance. On manipule en fait ces propriétés comme on manipulerait un composant. Sous Delphi, une interface d'édition sera proposée pour vous simplifier la vie. Au niveau du code, on manipule ces propriétés comme tout composant, à savoir qu'elles ont des propriétés et des méthodes mais en général pas d'evénements. Il est possible d'utiliser de manière classique ces "sous-propriétés" et ces "sous-méthodes" (on oubliera très vite ces vilaines dénominations pour ne plus parler que de propriétés et de méthodes), comme vous l'avez fait avec la propriété "Items" des composants "ListBox".

Pour accèder à une propriété ou à une méthode d'une propriété objet, il suffit d'employer la syntaxe suivante qui doit vous rappeler celle utilisée avec le composant "ListBox" :

composant.propriete_objet.propriete := valeur; // modification d'une propriété
composant.propriete_objet.methode(paramètres); // appel d'une méthode 
Ces propriétés sont généralement employées dans des composants faisant intervenir des listes, à savoir les "ListBox" que vous connaissez déjà, les "ComboBox" que vous connaissez certainement aussi (liste déroulante), les arbres (pensez à la partie gauche de l'Explorateur Windows), les listes d'icônes ou d'éléments a plusieurs colonnes (pensez à la partie droite de l'explorateur windows), les Mémo (pensez au bloc-notes : chaque ligne est un élément d'une liste).

Nous aurons de nombreuses fois l'occasion d'utiliser ces propriétés, il n'était question ici que de les présenter brièvement.


IX-H-2. Propriétés de type tableau, propriété par défaut

Certaines propriétés que vous serez amenés à manipuler sont de type tableau. Vous connaissez déjà les tableaux et vous savez donc déjà comment on accède aux éléments d'un tableau. Les propriétés tableau ne sont pas de vrais tableaux, mais sont très proches des tableaux, à savoir qu'on accède à leurs éléments de la même façon que pour de vrais tableaux : par l'utilisation des crochets [] à la suite du nom du tableau. Ici, il faudra simplement remplacer le nom du tableau par le nom d'une propriété tableau, ce qui fait une différence théorique uniquement étant donné que les noms de propriétés et de tableaux sont des identificateurs.

Pour clarifier la situation, voici un petit rappel avec un tableau classique :

procedure TForm1.Button1Click(Sender: TObject);
var
  Tableau: array[1..10] of integer;
begin
  Tableau[2] := 374;
  ShowMessage(IntToStr(Tableau[2]));
end;
Je ne vous ferai pas l'affront d'expliquer ce que fait cet exemple simple. Voici maintenant un autre exemple démontrant l'utilisation d'une propriété tableau. Créez un projet vierge et placez une zone de liste ("ListBox") et un bouton sur l'unique fiche. Nommez la zone de liste "lbTest". Insérez le code présenté ci-dessous dans la procédure de réponse à l'evénement OnClick du bouton :

procedure TForm1.Button1Click(Sender: TObject);
begin
  lbTest.Items.Clear;
  lbTest.Items.Add('Chaîne n°0');
  lbTest.Items.Add('Chaîne n°1');
  lbTest.Items.Add('Chaîne n°2');
  ShowMessage(lbTest.Items.Strings[1]);
end;
Cet exemple est très riche dans le sens où il combine deux notions importantes, à savoir les propriétés objet et les propriétés tableau. Dans cet exemple, la propriété "Items" de "lbTest" est une propriété objet. Elle possède ses propriétés et ses méthodes. Parmi les méthodes, "Clear" permet d'effacer le contenu de la liste, et "Add" permet d'ajouter une chaîne (mais vous savez déjà cela si vous avez suivi ce qui précède).

Ce qui est nouveau, c'est la propriété "Strings" de "Items" : cette propriété est une propriété tableau. Les indices sont des entiers et les éléments du tableau sont des chaînes de caractères, qui sont précisément les chaînes affichées dans la liste. Comme beaucoup de propriétés tableau, "Strings" est indicée à partir de 0, c'est-à-dire que l'élément 0 est le premier du tableau et que l'élément n est le (n+1)-ième. Dans l'exemple ci-dessous, on prends le deuxième élément de la liste (le deuxième ajouté), qui est lbTest.Items.Strings[1], et on l'affiche, ce qui donne :

Les propriétés tableau sont des propriétés des plus utiles, et nous en reparlerons lorsque nous aborderons en détail les composants qui les utilisent. Pour l'instant, il me faut contenter certains lecteurs habitués à Delphi qui auront certainement tiqué à la lecture de l'exemple précédent. En effet, la dernière ligne de la procédure serait plutôt écrite comme cela par ces lecteurs :

ShowMessage(lbTest.Items[1]); // le ".Strings" a disparu !
C'est une écriture valable, et pourtant comment expliquer qu'on puisse se passer d'un morceau de code tel que ".items", étant donné qu'il fait appel à une propriété ? La réponse est que cette propriété, ne se satisfaisant pas d'être déjà une propriété tableau d'une propriété objet, a une caractéristique supplémentaire : elle est par défaut. Cette caractéristique réservée à une seule propriété tableau par composant ou propriété objet permet de raccourcir l'accès à cette propriété en n'écrivant plus explicitemet l'appel à la propriété tableau (qui est fait implicitement dans ce cas). Dans le cas de la propriété "Items" des composants "ListBox", les deux écritures suivantes sont donc parfaitement équivalentes :

lbTest.Items[1]
lbTest.Items.Strings[1]

La deuxième ligne est simplement plus longue que la première, c'est pourquoi on ne se privera pas d'utiliser exclusivement la première.


IX-H-3. Propriétés de type référence

Certaines propriétés, vous le verrez, ne sont pas de vraies propriétés au sens où nous l'entendons depuis le début : ce sont juste des références à d'autres éléments, tels que des composants la plupart du temps. Contrairement au modèle classique montré au début de ce chapitre, la propriété n'est pas entièrement stockée dans le composant, mais se trouve en dehors de celui-ci. A la place, se trouve une simple référence, un lien vers l'élément référencé, à savoir un composant la plupart du temps.

Prenons un exemple : lorsque vous créérez un menu pour votre application, vous utiliserez un composant "MainMenu". Vous serez certainement tentés de placer des images à coté des éléments de menus, comme cela semble être la mode actuellement. Ceci passe par l'utilisation d'un composant "ImageList" invisible. Ce composant doit ensuite être mis en relation avec le menu par l'intermédiaire d'une propriété "Images" de ce dernier. Vous n'avez plus ensuite qu'à piocher dans les images présentes dans le composant "ImageList" pour les placer dans les menus (la création de menus est décrite dans l'Annexe A du chapitre 8).

Ce qui nous intéresse ici, c'est la propriété "Images" en elle-même. Cette propriété est de type "TImageList", qui est précisément le type du composant "ImageList". Mais ici, il n'est pas question de stocker le composant "ImageList" dans le composant "MainMenu", puisque le premier est placé sur la fiche et est donc stocké dans celle-ci. La propriété "Images" n'est donc qu'une simple référence au composant "ImageList". Je sais que la nuance peut paraître inintéressante, mais elle est de taille.

Quant à l'utilisation de ces propriétés références, c'est un vrai plaisir. Supposons que notre menu soit nommé "MainMenu1" et notre "ImageList" soit nommée "ImageList1". Les deux écritures suivantes sont alors équivalentes :

MainMenu1.Images
ImageList1
ce qui signifie simplement que vous pouvez utiliser une référence comme l'élément lui-même. Il faudra cependant faire attention à vérifier qu'une référence est valable avant de l'utiliser.

Nous reviendront en détail sur ces notions de références dans deux chapitres, l'un consacré aux pointeurs et l'autre consacré aux objets. C'est un sujet qui ne peut hélas qu'être effleuré tant qu'on n'a pas vu ces deux notions plus complexes.


IX-I. Conclusion

Ceci termine ce chapitre consacré à la manipulation des composants. Ce chapitre n'est en fait qu'une entrée en matière pour le chapitre suivant. Vous savez maintenant ce que sont les composants, les propriétés, les méthodes, les événements. Vous savez également comment utiliser ces éléments. Des composants particuliers comme les composants invisibles ou conteneurs ont été abordés. Ce qu'il vous manque maintenant, c'est une visite guidée des différents composants utilisables dans Delphi, avec les propriétés, méthodes et événements importants à connaître pour chacun d'eux. C'est l'objet du chapitre 8. 
آخر مواضيع منتدى نقاش المغرب العربي


Aucun commentaire:

Enregistrer un commentaire