Utilisation de classes et interfaces graphiques▲
Pré-requis▲
Les bases de la programmation doivent être bien assimilées. En particulier, le cours sur les types structurés, puisque les objets sont en quelque sorte une généralisation de ceux-ci.
Utilisation de classes▲
Notion de classe▲
Une classe est une manière de définir un type qui peut être vue comme l'association de sous-programmes à un type structuré.
Tout comme un type structuré, une classe possède des attributs, qui peuvent être de type quelconque.
La nouveauté par rapport à un type structuré est l'intégration des sous-programmes, appelés méthodes. Ils représentent les opérations que l'on peut effectuer sur un type de données.
L'idée sous-jacente à la base de la programmation objet (programmation utilisant des classes) est de voir un type comme des données associé à des opérations (les méthodes).
Comme un type structuré, une classe doit en principe être déclarée avant de pouvoir être utilisée. Mais dans ce premier cours sur la programmation objet, nous nous limiterons à l'utilisation de classes prédéfinies (et donc intégrées au langage de programmation). Par conséquent, la déclaration d'une classe ne sera pas abordée dans ce cours.
Exemple de la classe TStringList▲
Pour introduire la notion de classe, nous prendrons comme exemple la classe TStringList du Pascal, qui permet de manipuler des listes de chaînes de caractères.
Cette classe possède (entre autres) deux attributs nommés Count et Strings et quatre méthodes nommées Clear, Add, IndexOf et LoadFromFile :
- Count est un attribut de type entier destiné à contenir le nombre d'éléments de la liste ;
- l'attribut Strings quant-à lui contient la liste des chaînes de caractères ;
- la méthode Clear est une procédure sans paramètre qui permet de vider une liste ;
- la méthode Add est une procédure à un paramètre (de type String), qui permet d'ajouter une chaîne de caractères à une liste ;
- la méthode IndexOf est une fonction à un paramètre de type String. Elle retourne la position d'une chaîne de caractères dans la liste. La position de la première chaîne est 0, celle de la deuxième 1, etc. Si la chaîne n'est pas dans la liste, IndexOf retourne -1 ;
- la méthode LoadFromFile est une procédure à un paramètre de type String représentant le nom d'un fichier. Elle remplit la liste avec les lignes contenues dans ce fichier. C'est donc un moyen pratique pour initialiser la liste des chaînes de caractères contenues dans un objet de la classe TStringList.
Objet ou instance d'une classe▲
À partir du moment où une classe est définie, il est possible de créer des instances de cette classe ou, autrement dit, des objets.
Par comparaison avec un type structuré, la création d'une instance peut être comparée à la déclaration d'une variable de type structuré.
Il y a pourtant une différence importante avec la déclaration d'une variable de type structuré : la déclaration d'un objet n'alloue pas d'espace mémoire pour cet objet, mais pour un pointeur sur cet objet.
Prenons un exemple. Le code suivant ressemble à la déclaration d'une variable de type TStringList :
var
Mayonnaise: TStringList;
En réalité, cette ligne de code déclare l'adresse d'un objet (encore inexistant à ce stade) de la classe TStringList. Pour l'instant, cet objet n'existe pas et par conséquent, son adresse est indéfinie.
Pour créer un objet, une deuxième opération appelé instanciation est nécessaire.
En Pascal, l'instanciation se fait par l'intermédiaire d'une méthode spéciale nommée Create, définie pour toutes les classes.
Voici, par exemple, l'instanciation d'un objet de la classe TStringList :
Mayonnaise := TStringList.Create ();
C'est uniquement lors de l'instanciation :
- que la place mémoire est allouée à un objet. Il s'agit plus précisément de la place mémoire nécessaire à stocker les attributs de cet objet ;
- que l'adresse de l'objet est définie. En effet, la méthode Create retourne l'adresse de la zone mémoire contenant les données de l'objet.
Les objets sont stockés dans une zone spéciale de la mémoire appelée le tas (heap en anglais), utilisée de manière plus générale pour l'allocation dynamique de mémoire.
Accès aux attributs▲
L'accès aux attributs d'un objet se fait de la même manière que l'accès aux champs d'une structure. On utilise le nom de l'objet, suivi d'un point, suivi du nom de l'attribut.
La définition des attributs d'une classe offre toutefois des possibilités supplémentaires, que nous verrons essentiellement dans la deuxième partie du cours. Mais certaines de ces possibilités apparaissent déjà dans la classe TStringList :
- l'attribut count est en lecture seule, c'est-à-dire que l'affectation d'une valeur à cet attribut provoquera une erreur ;
- l'attribut Strings est l'attribut par défaut de la classe. On peut donc écrire Mayonnaise[1] au lieu de Mayonnaise.Strings[1] pour désigner le deuxième élément de la liste Mayonnaise.
La notation Strings[i] peut faire croire que Strings est un tableau. Si c'était le cas, il aurait été nécessaire de définir sa dimension. En réalité, Strings est représenté par une liste utilisant l'allocation dynamique de mémoire (donc stockée dans le tas). Sa dimension peut donc varier pendant l'exécution du programme. La taille de cette liste est uniquement limitée par l'espace mémoire disponible dans le tas.
Application de méthodes▲
Bien que les méthodes soient des sous-programmes, l'appel d'une méthode ne s'écrit pas comme l'appel d'un sous-programme.
Un des paramètres d'une méthode est forcément un objet de la classe pour laquelle cette méthode a été définie.
Par exemple, toute méthode de la classe TStringList a un paramètre de type TStringList ou, autrement dit, un objet de cette classe.
Mais dans l'écriture de l'appel de la méthode, cet objet ne figurera pas dans les parenthèses, mais en dehors, à gauche du nom de la méthode et séparé par un point. Cette écriture est l'application d'une méthode à un objet.
Voici, par exemple, l'application de la méthode Add (adjonction d'une chaîne) à un objet de la classe TStringList :
Mayonnaise.Add('
Oeuf
'
);
Toute méthode s'applique donc nécessairement à un objet de sa classe.
Une autre différence importante avec les sous-programmes se situe au niveau du nom. Le nom d'un sous-programme normal le définit de manière unique. Ce n'est pas vrai pour une méthode.
En effet, deux classes distinctes peuvent avoir des méthodes de même nom qui ne font pas du tout la même chose !
On pourrait, par exemple, imaginer une classe Date avec une méthode Add permettant d'ajouter un certain nombre de jours à une date. L'application de cette méthode à un objet D de la classe Date pourrait par exemple s'écrire :
D.Add(5
);
Ce qui aurait comme effet d'ajouter cinq jours à la date D.
Mais dans ce cas, comment le compilateur peut-il distinguer les deux méthodes ?
L'ambiguïté est résolue grâce à la nature de l'objet à laquelle est appliquée la méthode. S'il s'agit d'un objet de la classe TStringList, le compilateur en déduira qu'il s'agit de la méthode Add de la classe TStringList. S'il s'agit d'un objet de la classe Date, il en déduira qu'il s'agit de la méthode Add de la classe Date.
Interfaces graphiques▲
Principes de réalisation d'une interface graphique▲
Depuis l'apparition des écrans graphiques, de la souris, du multi-fenêtrage, la majorité des applications possèdent une interface graphique.
Elle se présente sous la forme d'une ou plusieurs fenêtres sur lesquelles apparaissent différents composants (boutons, menus, zones de liste, case à cocher, etc.) sur lesquels l'utilisateur peut agir afin d'obtenir des informations ou bien d'en donner à l'ordinateur.
La conception de l'interface graphique d'une application est donc aujourd'hui une partie très importante du développement d'application.
Utilisation de l'E.D.I▲
Avec un E.D.I. (comme Lazarus, Visual Basic, Visual C++), la réalisation d'une interface graphique se décompose en deux parties :
- la réalisation du rendu visuel de l'interface. Cette partie ne nécessite aucune programmation. Le développeur sélectionne les composants qu'il veut utiliser dans des palettes de composants et les dépose sur la fenêtre. Il peut ensuite modifier leurs propriétés (position, dimension, couleur par exemple) ;
- l'écriture des gestionnaires d'évènements ou, autrement dit, la programmation évènementielle. C'est ce qui permettra de rendre l'interface fonctionnelle. C'est ici que des notions de programmation objet vont intervenir.
Les onglets de composants de Lazarus▲
Lazarus offre un très grand choix de composants graphiques répartis sous 14 onglets :
Par manque de temps, je n'ai pu en présenter qu'une petite partie, principalement ceux de l'onglet Standard et Dialogs (boîtes de dialogue), ainsi que quelques composants des onglets Additional, Common Controls et Misc.
Les onglets Data Controls , Data Access et SQLdb sont spécialisés dans l'accès aux bases de données. Nous en reparlerons dans le cours consacré à l'accès aux bases de données par programmation.
Les composants en tant qu'objets▲
Quelques rappels▲
Dans le premier cours (Premières notions), nous avions déjà un peu abordé la création d'interface graphique sous Lazarus.
Dans l'exercice de prise en main de Lazarus, nous avions créé l'interface graphique d'un programme permettant d'additionner deux nombres. Cette interface ne comportait que trois types de composants : les étiquettes, les zones de texte et les boutons.
Pour ajouter un de ces composants à l'interface, il vous suffisait de le sélectionner dans la palette des composants standard :
Rappelez-vous que Lazarus génère un nom par défaut pour chaque nouveau composant déposé sur le formulaire :
et que les noms de ces composants apparaissent dans le code source :
Déclaration de la classe TForm1 et de l'objet Form1▲
La partie du code représentée ci-dessus est la déclaration d'une classe. Plus précisément, c'est la déclaration de la classe TForm1 qui représente le formulaire de votre application. Le nom TForm qui figure entre parenthèses est le nom d'une autre classe. Il s'agit d'une classe prédéfinie de Lazarus sur laquelle nous reviendrons ultérieurement.
Le texte en blanc est la définition des attributs de la classe TForm1. On voit donc que les attributs de cette classe sont les composants déposés sur le formulaire : les attributs Label1, Label2, Label3 de type TLabel sont les trois étiquettes, les attributs Edit1, Edit2, Edit3 de type TEdit sont les trois zones de texte et enfin l'attribut Button1 de type TButton est le bouton.
En fait, TLabel, TEdit et Tbutton ne sont pas réellement des types, mais des classes prédéfinies de Lazarus. Ainsi, TButton est la classe représentant les boutons, TLabel celle des étiquettes et TEdit, celle des zones de texte.
De manière générale, tous les composants déposés sur l'interface sont des objets appartenant à différentes classes.
Un peu plus bas dans le code, figure la déclaration d'une variable globale nommée Form1 (à moins que cette variable ait été renommée, mais nous supposerons ici pour simplifier les explications que ce n'est pas le cas) :
Avec ce que nous avons vu dans la première partie de ce cours, on comprend qu'il s'agit là de la déclaration d'un objet de la classe TForm1. Le formulaire de l'application est donc également un objet : c'est l'objet Form1 de la classe TForm1. Cette instance de la classe TForm1 est créé automatiquement au démarrage de l'application.
Les composants du formulaire sont donc des attributs de cet objet. Cela explique, par exemple, pourquoi l'écriture Form1.Edit1 peut être utilisée pour désigner la zone de texte Edit1 du formulaire Form1.
Accès aux attributs d'un composant▲
Dès qu'un composant a été déposé sur le formulaire, l'inspecteur d'objet permet de modifier ses propriétés. Il suffit pour cela de sélectionner ce composant, puis de sélectionner la propriété voulue dans l'inspecteur d'objet et de modifier la valeur de cette propriété.
La figure suivante illustre, par exemple, la modification de la propriété Caption du bouton du projet Additionner :
Les propriétés que l'on modifie ainsi sont en réalité des attributs de l'objet qui représente ce composant. On peut donc également y accéder par programmation. Par exemple, l'instruction :
Form1.BoutonAdditionner.Caption := '
Additionner
'
;
a le même effet que la modification de propriété illustrée par la figure précédente.
Notion d'héritage▲
Pour aller un peu plus loin dans la représentation d'une interface graphique sous Lazarus, il nous faut introduire une autre notion fondamentale de la programmation objet : il s'agit de la notion d'héritage.
L'héritage est un mécanisme qui a été introduit dans la programmation objet dans le but d'éviter la réécriture inutile de code lorsqu'une classe n'est qu'un cas spécial d'une autre classe :
La classe plus spécialisée est appelée sous-classe et la classe plus générale, classe mère.
Lorsqu'une relation d'héritage est définie entre deux classes, la sous-classe hérite de tous les attributs et méthodes de la classe mère. C'est-à-dire que tout se passe comme si les attributs et méthodes de la classe mère avaient été explicitement définis pour la sous-classe.
Une sous-classe d'une classe donnée représente donc une sorte de cas particulier de cette classe qui possède des attributs et méthodes supplémentaires et spécifiques.
La classe TForm▲
Revenons un instant sur la déclaration de la classe TForm1 du projet Addition :
et plus précisément sur la première ligne de cette déclaration de classe :
TForm1 = class
(TForm)
Cette ligne de code définit la classe TForm1 comme une sous-classe de TForm.
TForm est une classe prédéfinie de Lazarus qui représente l'interface graphique par défaut, c'est-à-dire un formulaire sans composant. Cette classe possède déjà un certain nombre de méthodes et d'attributs. Par exemple, la méthode Close, qui permet de fermer le formulaire, l'attribut Caption qui permet de lui donner un titre.
Comme TForm1 est une sous-classe de TForm, elle hérite de tous les attributs et de toutes les méthodes de la classe TForm. Vous pouvez donc accéder au titre de votre formulaire par Form1.Caption et le fermer en lui appliquant la méthode Close.
Les attributs spécifiques de TForm1 sont les composants qui ont été déposé sur le formulaire. Ses méthodes spécifiques sont les procédures évènementielles associées à ces composants.
Composants abstraits▲
Tout comme TForm1, les composants de l'interface ont des classes mères qui représentent en quelque sorte des « composants abstraits ». La figure suivante montre l'arborescence (très simplifiée) des classes Lazarus intervenant dans la représentation d'une interface graphique :
La classe TControl▲
On voit que de nombreux composants graphiques héritent de la classe TControl. C'est, par exemple, le cas des composants que vous connaissez déjà : les zones de texte (classe TEdit), les zones de liste (classe TListBox), les étiquettes (classe TLabel) ainsi que les boutons (classe TButton) sont des sous-classes de TControl. De plus, le formulaire lui-même est un objet de la classe TControl, car TForm1 est une sous-classe TForm, qui est elle-même une sous-classe de TControl.
Il est donc utile de connaître les méthodes et attributs de cette classe. En voici quelques uns :
La classe TComponent▲
La classe mère de tous les composants graphiques est TComponent. L'attribut Name de cette classe contient le nom du composant. Tout composant graphique possède donc cet attribut.
La classe TObject▲
Pour terminer, TComponent est une sous-classe de TObject, la classe mère de toutes les classes en Pascal Objet. Elle possède une méthode ClassName, qui retourne le nom de la classe à laquelle appartient un objet. Cette méthode existe par conséquent pour toutes les classes.
Les procédures évènementielles en tant que méthodes▲
Nous allons à présent éclaircir quelques points concernant les procédures évènementielles.
Pour cela, reprenons l'exemple du projet d'addition. Lorsque vous double-cliquez pour la première fois sur le bouton d'addition, Lazarus génère automatiquement la déclaration de la procédure évènementielle associée à ce bouton dans l'unité du formulaire. Voici ce code :
Dans ce code, il y a deux choses que nous n'avons jamais expliquées : pourquoi le nom de la procédure est-il préfixé par TForm1. ? Que signifie le paramètre Sender de type TObject ?
D'autre part, rappelez-vous que lorsque vous voulez accéder à un composant de l'interface dans une procédure non évènementielle, il faut préfixer le nom du composant par Form1.. Dans une procédure évènementielle, ce n'est pas nécessaire. Pour quelle raison ?
Enfin, vous avez sans doute remarqué que les en-têtes des procédures évènementielles sont automatiquement ajoutées à l'intérieur de la déclaration de la classe TForm1. Par exemple, dans le projet Addition, le double-clic sur le bouton Additionner génère également le code suivant (en blanc) :
On y retrouve donc l'en-tête de la procédure évènementielle. Pourquoi cela ?
Les réponses à toutes ces questions se trouvent dans la programmation objet et, plus précisément, dans la manière dont la programmation objet est utilisée dans Lazarus pour représenter des interfaces graphiques. Reprenons chacune de ces questions.
Préfixage des procédures évènementielles par TForm1▲
Dans les en-têtes des procédures évènementielles, le nom de la procédure est toujours préfixé par TForm1.. Pour quelle raison ?
Le préfixage des procédures évènementielles par TForm1 s'explique par le fait que les procédures évènementielles sont des méthodes de la classe TForm1. Or, lorsque l'on déclare une méthode en Lazarus, il faut obligatoirement préfixer le nom de la procédure par le nom de la classe (nous reviendrons là-dessus plus en détails dans le deuxième cours de programmation objet).
En-tête des procédures évènementielles à l'intérieur de la classe TForm1▲
Pour quelle raison les en-têtes des procédures évènementielles apparaissent-elles à l'intérieur de la déclaration de la classe Tform1 ?
L'explication est encore une fois dans la manière de déclarer une méthode d'une classe : en Pascal Objet, les en-têtes des méthodes d'une classe doivent forcément figurer à l'intérieur de la déclaration de la classe.
Préfixage des composants par Form1▲
Dans une procédure non évènementielle, les composants doivent être préfixés par Form1., mais ce n'est pas nécessaire dans une procédure évènementielle. Pour quelle raison ?
Rappelons-nous que les procédures évènementielles sont des méthodes de la classe TForm1 et que les composants sont des attributs de cette classe.
Or, à l'intérieur d'une méthode d'une certaine classe, un nom d'attribut de cette classe désigne implicitement l'attribut de l'objet auquel il sera appliqué au moment de son exécution.
Comme les procédures évènementielles sont des méthodes de la classe TForm1, que les composants sont des attributs de cette classe et que les procédures évènementielles sont forcément appliquées à l'objet Form1 de cette classe, tout nom de composant apparaissant à l'intérieur d'une procédure évènementielle est automatiquement interprété comme un attribut de l'objet Form1. À l'intérieur d'une procédure évènementielle, le préfixage des composants par Form1. est donc inutile.
Par exemple, à l'intérieur de la procédure évènementielle Button1Click du projet Addition, Edit1 désigne implicitement la zone de texte Edit1 de l'objet Form1, car Edit1 est un attribut de la classe TForm1.
D'un autre côté, dans une procédure non évènementielle, le préfixage des composants est obligatoire car, tout simplement, les procédures non évènementielles ne sont pas des méthodes de la classe TForm1 (à moins, bien sûr, de les déclarer en tant que telles).
Signification du paramètre Sender▲
Dans l'en-tête d'une procédure évènementielle figure toujours un mystérieux paramètre de type TObject nommé Sender. Que représente ce paramètre ?
Le paramètre Sender contient l'adresse du composant qui a déclenché l'évènement. Par exemple, dans la procédure Button1Click :
Sender contient l'adresse de l'objet Button1.
On peut se demander quel est l'intérêt de ceci, étant donné que l'on peut tout simplement accéder à cet objet en utilisant son nom.
En fait, l'utilité du paramètre Sender apparaît lorsque l'on cherche à réutiliser la même procédure évènementielle pour plusieurs composants de l'interface.
Il est en effet possible, via l'inspecteur d'objet, d'associer la même procédure évènementielle à plusieurs composants. Ces composants peuvent même être de types différents (d'où l'intérêt de déclarer Sender comme un objet de la classe Tobject) !
Pour vous en convaincre, ouvrez ProjetSender dans le dossier exemple. Le formulaire contient un bouton et une case à cocher. Ces deux composants sont associés à la même procédure évènementielle qui affiche le nom du composant sur lequel l'utilisateur a cliqué.
Le code de la procédure évènementielle utilisé par les deux composants est le suivant :
procedure
TForm1.Button1Click(Sender: TObject);
begin
ShowMessage ('
Vous
avez
cliqué
sur
le
composant
'
+(Sender As
TComponent).Name);
end
;
Ce code utilise le polymorphisme, une notion de programmation objet sur laquelle nous reviendrons dans le cours suivant.
Les évènements▲
Dans le cours d'introduction à la programmation, nous avons vu qu'un évènement est le résultat d'une action de l'utilisateur sur l'interface graphique d'un programme. Toute action sur le clavier ou la souris provoque une interruption, qui est d'abord capturée par le système d'exploitation. Si le curseur de la souris est à ce moment-là situé au-dessus de la fenêtre d'une application, le système va alors lui retransmettre cette interruption.
Un évènement sera alors généré au sein de l'application.
Jusqu'ici, le seul type d'évènement que nous avons considéré est le clic sur un bouton. Il existe en réalité de nombreux autres types d'évènements et, de plus, ils peuvent être associés à n'importe quel type de composant.
Dans cette partie du cours, nous allons voir de nouveaux types d'évènements et comment leur associer un gestionnaire d'évènement ou, autrement dit, une procédure évènementielle.
Utilisation de l'onglet évènement▲
Pour connaître les évènements associés à un composant▲
Dans Lazarus, tout composant graphique peut intercepter des évènements de différents types. Pour les connaître, il suffit de cliquer sur le composant, puis sur l'onglet Évènement de l'inspecteur d'objet.
Voici, par exemple, l'onglet évènement d'un bouton :
Celui d'une zone de texte :
Celui d'une étiquette :
Et d'un formulaire (les 21 premiers puis les 18 suivants) :
Chaque ligne de l'onglet Évènement correspond à un attribut de la classe représentant le composant. Les noms des attributs sont dans la colonne de gauche et leurs valeurs respectives dans la colonne de droite.
Tous les attributs commençant par On servent à stocker un pointeur vers un gestionnaire d'évènement. Le nom de l'évènement en question est défini par la chaîne de caractères qui suit On.
Reprenons, par exemple, l'onglet Évènement du bouton Additionner :
OnClick représente ici un attribut de la classe TButton (classe représentant les boutons). La valeur de cet attribut est BoutonAdditionnerClick. Il pointe donc vers l'adresse en mémoire de la procédure évènementielle BoutonAdditionnerClick.
Notez également que Lazarus génère un nom par défaut pour le gestionnaire d'évènement. Il est formé du nom du composant, suivi du nom de l'évènement.
En comparant les exemples d'onglets Évènement précédents, on constate que certains évènements peuvent se retrouver dans plusieurs composants. C'est le cas, par exemple, de l'évènement OnClick, que l'on retrouve aussi bien dans l'onglet Évènement d'un bouton que dans celui d'une étiquette, d'une zone de texte ou d'un formulaire (voir les illustrations ci-dessus).
D'autres, par contre, sont spécifiques à certains composants. L'évènement OnEditingDone, par exemple, existe pour les zones de texte mais pas pour les boutons, ni les formulaires, ni les étiquettes.
Pour associer un gestionnaire d'évènement à un composant▲
Chaque composant possède un évènement par défaut. Par exemple, Click pour un bouton ou une étiquette, Change pour une zone de texte et Create pour un formulaire.
Lorsque vous double-cliquez sur un composant, Lazarus considère que vous voulez écrire le gestionnaire d'évènement pour l'évènement par défaut de ce composant. Il va donc automatiquement générer une procédure évènementielle de la forme suivante dans le code de l'application :
procedure
TForm1.XY(Sender: TObject);
begin
end
;
où X est le nom du composant et Y le nom de l'évènement par défaut de ce composant. D'autre part, cette procédure évènementielle sera déclarée en tant que méthode de la classe TForm1.
Par exemple, avec l'interface graphique suivante :
un double-clic sur le bouton Additionner (dont le nom interne est BoutonAdditionner) génère automatiquement la procédure évènementielle suivante :
procedure
TForm1.BoutonAdditionnerClick(Sender: TObject);
begin
end
;
car l'évènement Click est l'évènement par défaut des boutons.
Lorsque l'on affiche l'onglet Évènement de ce bouton, on constate que la procédure évènementielle est bien enregistrée dans l'attribut OnClick de cet objet :
Comment fait-on alors pour écrire un gestionnaire d'évènement d'un composant lorsque cet évènement n'est pas l'évènement par défaut de ce composant ?
C'est ici qu'intervient l'onglet Évènement. Supposons, par exemple, que nous souhaitons gérer l'évènement EditingDone d'une zone de texte. Comme il ne s'agit pas de l'évènement par défaut, il faut ouvrir l'onglet Évènement du composant et cliquer sur les … de l'attribut OnEditingDone, comme dans l'exemple suivant :
et Lazarus génèrera alors le code initial du gestionnaire d'évènement dans le code source (la zone de texte se nomme ZoneTexteX) :
procedure
TForm1.ZoneTexteXEditingDone(Sender: TObject);
begin
end
;
On constate que cette procédure évènementielle est enregistrée dans l'attribut OnEditingDone :
Quelques évènements utiles▲
Dans les applications courantes, il est rare de devoir gérer d'autres évènements que le clic. Les suivants pourront toutefois vous être utiles dans de nombreux cas :
- DblClick : le double-clic. Attention :
- EditingDone : en français, « édition achevée ». Cet évènement est généré dès que l'utilisateur a fini de saisir un texte. Il est par exemple disponible pour les zones de texte. L'évènement se produit dès que l'on appuie sur la touche Entrée ou que l'on quitte la zone de texte en allant, par exemple, cliquer dans une autre zone de texte. On peut donc de cette manière saisir une donnée sans avoir à cliquer sur un bouton pour la confirmer ;
- Create : déclenche la création d'un formulaire et la procédure évènementielle FormCreate, que vous connaissez déjà. Pour être plus précis, cet évènement est déclenché par l'instanciation d'un objet de la classe TForm. Il n'existe donc que pour les composants de cette classe ;
- Resize : évènement déclenché par le redimensionnement d'un composant. Particulièrement utile pour gérer le redimensionnement de la fenêtre d'une application. On peut, par exemple, ainsi redimensionner les composants d'une fenêtre lorsque ses dimensions sont modifiées par l'utilisateur ;
- CloseQuery : généré par la fermeture d'un formulaire. Utile pour protéger l'utilisateur contre une fermeture involontaire de l'application qui pourrait entraîner une perte de données. Cet évènement n'existe que pour les formulaires (classe TForm).
- cet évènement n'est pas disponible pour les boutons ;
- d'après mes expérimentations, on ne peut pas gérer à la fois le clic et le double-clic sur un même composant. Un double-clic implique forcément un clic simple. Le clic simple est géré en priorité. Il y a toutefois une exception pour les zones de liste ;
Exemple▲
L'exemple présenté ici se trouve dans le répertoire Exemple-ProgObjet1/Evenement. Il s'agit d'une nouvelle version du programme d'addition fonctionnant sans bouton. Cet exemple illustre l'utilisation des différents types d'évènements dont nous avons parlé précédemment. Voici le formulaire de cette application :
Utilisation de EditingDone▲
La somme des deux nombres est effectuée dès que l'utilisateur appuie sur la touche Entrée dans une des deux zones de texte étiquetées X ou Y (ZoneTexteX et ZoneTexteY respectivement). Ceci est réalisé grâce à la gestion de l'évènement EditingDone.
Voici le code du gestionnaire de l'évènement EditingDone pour ZoneTexteX :
procedure
TForm1.ZoneTexteXEditingDone(Sender: TObject);
begin
Additionner ();
end
;
et celui du gestionnaire de l'évènement EditingDone pour ZoneTexteY :
procedure
TForm1.ZoneTexteYEditingDone(Sender: TObject);
begin
Additionner ();
end
;
Les deux procédures appellent la procédure Additionner, qui lit les deux nombres à afficher puis affiche leur somme dans la zone de texte étiquetée X+Y.
Clic sur une étiquette▲
Un clic sur les étiquettes X ou Y (respectivement Etiquette_X et Etiquette_Y) efface les zones de texte associées (ZoneTexteX et ZoneTexteY respectivement).
Voici le code du gestionnaire de l'évènement Click pour Etiquette_X :
procedure
TForm1.Etiquette_XClick(Sender: TObject);
begin
Effacer(ZoneTexteX);
end
;
et celui du gestionnaire de l'évènement Click pour Etiquette_Y :
procedure
TForm1.Etiquette_YClick(Sender: TObject);
begin
Effacer(ZoneTexteY);
end
;
Utilisation de Resize▲
Si vous élargissez la fenêtre, vous constaterez que la dimension des zones de texte augmente. On obtiendra par exemple ceci :
Nous nous sommes en fait arrangés pour que la différence entre la largeur de la fenêtre et celle des zones de texte reste constante. Pour cela, la différence entre les deux largeurs est mémorisée dans une variable globale dès le démarrage de l'application. Nous avons donc placé cette instruction dans FormCreate :
procedure
TForm1.FormCreate(Sender: TObject);
begin
DifferenceLargeur := Form1.width - ZoneTexteX.Width;
end
;
Un redimensionnement de la fenêtre génère un évènement Resize associé au formulaire (Form1). Pour maintenir une différence de largeur constante, nous avons écrit le gestionnaire d'évènement Resize de Form1. Le voici :
procedure
TForm1.FormResize(Sender: TObject);
var
NouvelleLargeur : integer
;
begin
NouvelleLargeur := Form1.width - DifferenceLargeur;
ZoneTexteX.width := NouvelleLargeur;
ZoneTexteY.width := NouvelleLargeur;
ZoneTexteSomme.width := NouvelleLargeur;
end
;
Utilisation de DblClick, CloseQuery▲
Un double-clic sur le formulaire provoque l'apparition de la boîte de dialogue suivante :
Si l'utilisateur clique sur le bouton Non, l'application reste ouverte. S'il clique sur Oui, elle se termine.
Cette boîte de dialogue est affichée par la fonction ConfirmationConfirmation d'une action. Elle affiche une boîte de dialogue demandant à l'utilisateur de confirmer son choix.
Si l'utilisateur ne clique pas le bouton par défaut (ici Non), la fonction retourne True.
Logiquement, si un double-clic sur le formulaire provoque l'affichage de la boîte de dialogue, c'est nécessairement par l'intermédiaire du gestionnaire de l'évènement DblClick de Form1. Le voici :
procedure
TForm1.FormDblClick(Sender: TObject);
begin
Form1.Close();
end
;
Ô surprise ! Le gestionnaire n'appelle pas la fonction Confirmation. Comment le double-clic peut-il alors faire apparaître la boîte de dialogue ?
Logiquement, Form1.Close devrait fermer immédiatement la fenêtre.
L'explication de ce mystère est la suivante. La méthode Close génère l'évènement CLoseQuery associé au formulaire Form1 et c'est donc dans le gestionnaire de cet évènement que se trouve l'instruction permettant d'afficher la boîte de dialogue et de quitter ou de ne pas quitter l'application, selon la réponse de l'utilisateur. Voici ce gestionnaire d'évènement :
procedure
TForm1.FormCloseQuery
(Sender: TObject; var
CanClose: boolean
);
begin
CanClose := Confirmation ('
Mise
en
garde
'
,
'
Voulez-vous
réellement
quitter
l
'
'
application
?
'
,
'
Non
'
,'
Oui
'
);
end
;
Le paramètre CanClose du gestionnaire détermine si l'application doit être effectivement fermée (CanClose = True dans ce cas).
Composants standard de Lazarus▲
Sans vouloir être exhaustif, nous décrivons ici quelques uns des composants Lazarus, que vous pouvez trouver sous l'onglet Standard :
Les zones de texte (classe TEdit)▲
Nous utilisons depuis longtemps des zones de texte pour lire des données ou afficher des résultats avec les procédures de la librairie entrees_sortie.pas (Lire, LireEntier, LireNombre, Afficher, AfficherEntier, AfficherNombre). Avec ce qui suit, vous allez enfin savoir comment faire ceci sans utiliser ces procédures.
Afficher dans une zone de texte▲
Pour afficher une expression de type chaîne de caractères dans une zone de texte, il suffit d'affecter cette expression à l'attribut .Text de cet objet.
Exemple :
ZT.Text := '
Toto
'
;
affiche Toto dans la zone de texte ZT.
Si l'expression n'est pas de type chaîne de caractères, il faut au préalable la convertir en chaîne de caractères. On utilisera par exemple IntToStr pour afficher un nombre entier et FloatToStr pour afficher un nombre décimal.
Il peut paraître étrange qu'une simple affectation produise un affichage à l'écran, étant donné qu'une affectation ne fait que copier de l'information en mémoire. En réalité, l'affectation d'une valeur à l'attribut Text d'une zone de texte n'est pas une simple affectation. Nous reviendrons là-dessus dans le deuxième cours de programmation objet.
Lire une variable depuis une zone de texte▲
Inversement, pour lire le contenu d'une zone de texte dans une variable de type chaîne de caractères, on affecte à la variable la valeur de l'attribut .Text de la zone de texte.
Exemple :
Toto := ZT.Text;
affecte à Toto la chaîne de caractères contenue dans ZT.Text.
De même que pour l'affichage, il faudra utiliser les fonctions de conversion si la variable n'est pas de type String. On utilisera, par exemple, StrToInt pour une variable de type Integer et StrToFloat pour une variable de type Double.
Effacer une zone de texte▲
La méthode Clear() appliquée à une zone de texte permet d'effacer son contenu.
Exemple▲
Reprenons notre bon vieux projet d'addition qui permettait d'additionner deux nombres entiers (la version présentée ici se trouve dans Exemple-ProgObjet1/ZoneTexte). Voici son interface graphique :
Nous avons ici trois zones de texte nommées ZoneTexteX, ZoneTexteY et ZoneTexteSomme. Et voici la nouvelle version de la librairie entrees_sortie.pas de la procédure évènementielle associée au bouton Additionner :
procedure
TForm1.BoutonAdditionnerClick(Sender: TObject);
begin
x := StrToInt(zoneTexteX.Text);
y := StrToInt(ZoneTexteY.Text);
somme := x + y;
ZoneTexteSomme.Text := IntToStr(somme);
end
;
La classe TStrings▲
La classe TStrings ne représente pas un composant graphique particulier, mais elle est utilisée dans les classes TListBox (zones de liste), TMemo (mémo) et TComboBox (combobox) que nous présentons plus loin. C'est la raison pour laquelle il nous faut commencer par présenter cette classe.
Tout comme la classe TstringListExemple de la classe TStringList que nous avons présentée dans la première partie du cours, TStrings est une classe qui permet de manipuler des listes de chaînes de caractères.
En fait, TStringList n'est qu'une sous-classe de TStrings et toutes les méthodes Clear, Add, LoadFromFile, IndexOf ainsi que les attributs Count et Strings, dont nous avions déjà parlé (voir classe Exemple de la classe TStringList), ne sont pas des attributs spécifiques de TStringList, mais des attributs hérités de la classe TStrings.
TStrings possède d'autres méthodes dont nous n'avons pas encore parlé, comme SaveToFile et Delete. La première permet de sauvegarder son contenu dans un fichier, et la deuxième de supprimer un élément d'indice donné (la première chaîne est indicée 0).
Les zones de liste (classe TListBox)▲
Jusqu'à présent, nous avons utilisé les zones de liste pour afficher du texte sur plusieurs lignes à l'aide des procédure Afficher, AfficherEntier… de la librairie entrees_sorties.pas.
En réalité, les zones de liste servent à autre chose. Elles sont utilisées pour permettre à l'utilisateur de choisir un élément dans une liste de choix.
Dans Lazarus, les zones de liste sont représentées par la classe TListBox. Cette dernière contient, parmi ses attributs, un attribut nommé Items de type TStrings. C'est dans cet attribut que sont stockés les différents choix possibles, sous la forme d'un objet de la classe TStrings.
On pourra donc ajouter un choix dans une zone de liste en appliquant la méthode Add à son attribut Items. De même, on pourra initialiser la liste des choix à partir d'un fichier, en appliquant la méthode LoadFromFile à cet attribut.
L'attribut ItemIndex est l'indice de l'élément sélectionné (le premier est indicé 0). S'il vaut -1, cela signifie qu'aucun élément n'est sélectionné.
L'attribut Sorted est un booléen qui détermine si la liste des choix doit être triée automatiquement par ordre alphabétique.
TlistBox contient (entre autres) une méthode nommée GetSelectedText. Il s'agit d'une fonction qui retourne le choix sélectionné par l'utilisateur. Si aucun choix n'est sélectionné, elle retourne la chaîne vide.
Exemple▲
Pour illustrer l'utilisation des zones de liste, nous présentons ici un exemple de projet simulant le site d'un magasin de bricolage. Ce projet se trouve dans le dossier Exemple-ProgObjet1/ZoneDeListe (ouvrir le fichier MagasinBricolage.lpi). Voici son interface graphique :
La zone de liste de gauche (ZL_Categorie) est initialisée dès le démarrage du programme dans la procédure FormCreate :
procedure
TForm1.FormCreate(Sender: TObject);
begin
ZL_Categorie.Items.Add ('
Electricite
'
);
ZL_Categorie.Items.Add ('
Jardinage
'
);
ZL_Categorie.Items.Add ('
Materiaux
'
);
ZL_Categorie.Items.Add ('
Plomberie
'
);
end
;
Nous avons utilisé ici la méthode Add de la classe TStrings.
Une autre manière de procéder aurait été de saisir manuellement les différentes chaînes de caractères « Électricité », « Jardinage », etc. via l'inspecteur d'objet de Lazarus : on sélectionne la zone de liste ZL_Categorie, puis sa propriété Items dans l'inspecteur d'objet et enfin on clique sur les … :
L'éditeur de chaînes permet alors de saisir les différents choix.
Lorsque l'utilisateur clique sur une des catégories de produits, les types de produits de cette catégorie sont affichés dans la deuxième zone de liste (nommée ZL_TypeProduit) :
Voici le code de la procédure évènementielle gérant cet évènement :
procedure
TForm1.ZL_CategorieClick(Sender: TObject);var
NomCategorie, NomFichierCategorie :String
;begin
NomCategorie := ZL_Categorie.GetSelectedText;
NomFichierCategorie := NomCategorie+
'
.txt
'
;ZL_TypeProduit.Items.LoadFromFile(NomFichierCategorie);
ZL_TypeProduit.ItemIndex:=
0
;end
;
Pour comprendre son fonctionnement, il faut savoir que les types de produits d'une certaine catégorie se trouvent dans un fichier texte de même nom que la catégorie et d'extension '.txt'. Les types de produits de la catégorie Materiaux se trouvent, par exemple, dans le fichier Materiaux.txt.
Détaillons à présent les instructions de cette procédure évènementielle.
La ligne 4 permet de récupérer le nom de la catégorie de produit sélectionnée en appliquant la méthode GetSelectedText à la zone de liste ZL_Categorie.
La ligne 5 construit le nom du fichier contenant les différents types de produits de cette catégorie en concaténant l'extension '.txt' au nom de la catégorie.
La ligne 6 initialise la zone de liste ZL_TypeProduit avec les types de produits contenus dans ce fichier. Pour cela, la méthode LoadFromFile est appliquée à l'attribut Items de la zone de liste.
La ligne 7, enfin, définit le premier choix (choix d'indice 0) comme étant sélectionné. Cela explique pourquoi le premier type de produits apparaît sélectionné dès que la liste des types de produits est affichée.
Si vous comparez le contenu du fichier Materiaux.txt avec la liste des matériaux affichés par le programme, vous constaterez que le programme les affiche dans l'ordre alphabétique alors qu'ils ne sont pas dans l'ordre alphabétique dans le fichier. Cela s'explique par le fait que l'attribut Sorted de la zone de liste ZL_TypeProduit vaut True. Elle est donc automatiquement triée.
Les combobox (classe TComboBox)▲
Un combobox est une sorte de combinaison entre une zone de texte et une zone de liste. La zone de texte permet à l'utilisateur de saisir un choix qui n'est pas présent dans la zone de liste.
De plus, un combobox prend moins de place qu'une zone de liste, car la liste des choix n'est pas systématiquement déployée.
Voici quelques attributs utiles de la classe TComboBox :
On retrouve les attributs Items et Sorted des zones de listes. Par contre, la méthode GetSelectedText a disparu car le texte sélectionné se retrouve automatiquement dans l'attribut Text.
Autocomplete permet la complétion automatique des choix. Par exemple, s'il existe un choix commençant par un f, dès que l'utilisateur tape f, la zone de texte contiendra automatiquement le premier choix de la liste commençant par f et ce choix sera sélectionné dans la zone de liste. Si ce n'est pas le bon choix, il lui suffira en général de saisir quelques caractères de plus.
Enfin, lorsque l'attribut AutoDropDown vaut True, la liste est déployée dès que l'utilisateur tape un caractère dans la zone de texte.
Exemple▲
L'exemple présenté ici se trouve dans le dossier ComboBox du dossier Exemple (projet ProjetMoyenTransport). Voici l'allure de l'interface graphique au démarrage du programme :
Le combobox est initialement vide. Comme nous avons mis AutoDropDown à True, la liste est automatiquement déployée dès que l'on tape un caractère :
Comme nous avons mis AutoComplete à True, si on tape « M » dans le combobox, ce choix est automatiquement complété en « Mobylette » car c'est le premier choix de la liste commençant par un M :
L'utilisateur peut également saisir un choix qui ne se trouve pas dans la zone de liste :
Lorsqu'il clique sur le bouton Afficher le choix, le choix sélectionné est affiché dans la zone de texte (ZT_Vehicule). Voici le code de la procédure évènementielle associée à ce bouton :
procedure
TForm1.BT_AfficherChoixClick(Sender: TObject);
begin
ZT_Vehicule.Text := CB_Vehicule.Text;
end
;
Les cases à cocher (classe TCheckBox)▲
Les cases à cocher sont utilisées pour saisir des informations du type vrai/faux ou oui/non. L'état coché/décoché de la case est mémorisé dans l'attribut Checked, de type booléen. Avec Checked = True, la case est cochée. Sinon, elle ne l'est pas.
Exemple▲
L'exemple présenté ici se trouve dans le dossier Exemple-ProgObjet1/CaseACocher (ouvrir ProjetHotel.lpi). Voici la fenêtre de l'application :
Lorsque l'utilisateur sélectionne ou désélectionne les options Petit déjeuner ou Douche, le prix de la chambre est automatiquement actualisé. Avec petit déjeuner :
Avec la douche en plus :
C'est trop cher. On enlève le petit déjeuner :
Voici la procédure évènementielle associée à la case à cocher Petit déjeuner (CC_PetitDej) :
procedure
TForm1.CC_PetitDejChange(Sender: TObject);
begin
AfficherLePrix ();
end
;
et voici celle associée à la case à cocher Douche (CC_Douche) :
procedure
TForm1.CC_DoucheChange(Sender: TObject);
begin
AfficherLePrix ();
end
;
Ces deux procédures appellent la procédure non évènementielle AfficherLePrix que voici :
procedure
AfficherLePrix ();
var
Prix : Double
;
begin
Prix := 40
;
if
Form1.CC_PetitDej.Checked then
Prix := Prix + 5
;
if
Form1.CC_Douche.Checked then
Prix := Prix + 10
;
Form1.ZT_Prix.Text := FloatToStr(Prix);
end
;
Les boutons radio (classe TRadioButton)▲
Les boutons radio s'utilisent pour définir un choix parmi n, lorsque n n'est ni trop petit (pour n=2, on peut tout aussi bien prendre une case à cocher), ni trop grand (il vaut mieux dans ce cas utiliser une zone de liste ou un combobox).
Comme pour les cases à cocher, c'est l'attribut Checked qui définit le fait qu'un bouton radio soit coché ou non.
Par contre, les boutons radio ne se comportent pas exactement comme des cases à cocher. En effet, des boutons radio directement déposés sur le formulaire s'excluent mutuellement : si l'on coche un des boutons radio, les autres sont automatiquement décochés.
Cette interdépendance des boutons radio est gênante lorsque l'on souhaite utiliser des boutons radio pour des choix indépendants. C'est ici qu'intervient le composant GroupBox. Pour rendre deux groupes de boutons radio indépendants, on peut déposer deux GroupBox sur le formulaire, puis mettre les boutons radio du premier groupe dans le premier GroupBox et ceux du second groupe dans le second.
Un petit conseil pour terminer : pour mettre des composants à l'intérieur d'un autre, il vaut mieux commencer par le plus gros (le conteneur donc) puis déposer les petits composants dedans. Si on procède dans le sens inverse, les petits composants seront cachés par le gros. Dans ce cas, il faut faire un clic droit sur le conteneur, puis sélectionner Z-Order, puis déplacer en arrière.
Exemple▲
L'exemple présenté ici se trouve dans le dossier Exemple-ProgObjet1/BoutonRadio (ouvrir StationEssence.lpi ). Voici la fenêtre de l'application à son démarrage :
Nous avons utilisé ici un GroupBox pour chaque pompe. Vous constaterez que les boutons radio d'une même pompe s'excluent mutuellement (un seul choix d'essence possible), mais que les boutons radio de la pompe 1 sont indépendants de ceux de la pompe 2. Ici, par exemple, on a choisi E10 à la pompe 1 et SP95 à la pompe 2 :
Voici les procédures évènementielles des boutons radio de la pompe 1 :
procedure
TForm1.BR_SP95_P1Change(Sender: TObject);
begin
AfficherPrixPompe1 ();
end
;
procedure
TForm1.BR_E10_P1Change(Sender: TObject);
begin
AfficherPrixPompe1 ();
end
;
procedure
TForm1.BR_Gazole_P1Change(Sender: TObject);
begin
AfficherPrixPompe1 ();
end
;
Elles appellent toutes la procédure AfficherPrixPompe1 que voici :
procedure
AfficherPrixPompe1 ();
var
Prix :double
;
begin
if
Form1.BR_SP95_P1.Checked Then
Prix:=PRIX_SP95;
if
Form1.BR_E10_P1.Checked Then
Prix:=PRIX_E10;
if
Form1.BR_Gazole_P1.Checked Then
Prix:=PRIX_Gazole;
Form1.ZT_Prix_P1.Text := FloatToStr (Prix);
end
;
Pour les boutons radio de la pompe 2, nous avons utilisé le même principe.
Les mémos (classe TMemo)▲
Les mémos sont en quelque sorte des zones de texte étendues sur plusieurs lignes. Le texte contenu dans un mémo est stocké dans son attribut Lines de type TStrings.
L'attribut WordWrap définit la manière de gérer le passage à la ligne dans le mémo. Sa valeur par défaut est True, ce qui signifie qu'une ligne de texte sera automatiquement coupée et donc répartie sur plusieurs lignes, lorsque la largeur du mémo n'est pas suffisante pour l'afficher en entier. Dans ce cas, chaque chaîne de caractères contenue dans l'attribut Lines ne représente pas nécessairement une ligne de texte unique dans le mémo (tel qu'il est affiché).
Si WordWrap vaut False, une ligne trop longue pour être affichée en entier sera affichée en partie (ce qui dépasse n'est pas visible). Dans ce cas, chaque chaîne de caractères dans Lines correspond à une unique ligne affichée.
L'attribut Scrollbars définit les barres de défilement horizontales ou verticales. Il ne peut y avoir de barre de défilement horizontale que si Wordwrap vaut False, car dans le cas contraire elle n'est d'aucune utilité. Scrollbars a sept valeurs possibles, qui sont des constantes prédéfinies de Lazarus :
- ssVertical : ajout systématique d'une barre de défilement verticale ;
- SsHorizontal : ajout systématique d'une barre de défilement horizontale ;
- SsBoth : ajout systématique des deux barres de défilement ;
- SsNone : aucune barre de défilement ;
- SsAutoVertical : ajout d'une barre de défilement verticale lorsque c'est nécessaire ;
- SsAutoHorizontal : ajout d'une barre de défilement horizontale lorsque c'est nécessaire ;
- ssAutoBoth : ajout d'une barre de défilement horizontale ou verticale, lorsque c'est nécessaire.
Exemple▲
L'exemple présenté ici se trouve dans le dossier Exemple-ProgObjet1/Memo (ouvrir le fichier EditeurDeTexte.lpi). Il s'agit d'un éditeur de texte très simplifié qui permet d'ouvrir un fichier, de le modifier et de l'enregistrer. Le fichier ouvert est affiché à l'intérieur d'un mémo. Voici, par exemple, l'affichage du fichier Dylan.txt (que vous trouverez dans le même répertoire que le projet) :
Dans cet exemple, nous avons mis WordWrap à False et Scrollbar à ssAutoBoth. Comme le texte est trop large et trop long, deux barres de défilement ont été ajoutées et on voit que la première ligne est affichée en partie. Si vous n'avez pas très bien compris le rôle des attributs WordWrap et Scrollbar, amusez-vous à modifier leur valeur pour en voir l'effet.
Comme l'attribut Lines d'un mémo est de type TStrings, le chargement d'un fichier dans un mémo se fait simplement en appliquant la méthode LoadFromFile à cet attribut. Nous utilisons ceci dans la procédure évènementielle associée au bouton Ouvrir (ZT_NomFichier est le nom de la zone de texte contenant le nom du fichier et MM_Editeur, le nom du mémo) :
procedure
TForm1.BT_OuvrirClick(Sender: TObject);
var
NomFichier : String
;
begin
NomFichier := ZT_NomFichier.Text;
MM_Editeur.Lines.LoadFromFile(NomFichier);
end
;
Pour enregistrer le fichier, même principe avec la méthode SaveToFile :
procedure
TForm1.BT_EnregistrerClick(Sender: TObject);
var
NomFichier : String
;
begin
NomFichier := ZT_NomFichier.Text;
MM_Editeur.Lines.SaveToFile(NomFichier);
end
;
Les menus (classes TMainMenu et TMenuItem)▲
Les menus, comme les boutons, servent à activer différents traitements d'un logiciel. Lorsque le nombre de traitements est important, l'utilisation d'un bouton pour chaque traitement prend trop de place. Dans ce cas, il est préférable d'utiliser des menus. Ce sont en quelque sorte des boutons organisés en listes déroulantes, qui ne se déploient que lorsque l'on clique dessus.
Exemple▲
Nous reprenons ici l'exemple précédent de l'éditeur de texte en y intégrant une barre de menu. Ce projet se trouve dans Exemple-ProgObjet1/Menu (fichier EditeurDeTexte.lpi ).
Présentation de l'interface graphique▲
Voici la nouvelle interface graphique du programme, telle qu'elle apparaît à son démarrage :
La fenêtre comprend à présent une barre de menus contenant deux menus, intitulés Fichier et Affichage. Le menu Fichier permet d'ouvrir un fichier, de l'enregistrer ou de quitter l'application :
Remarquez qu'à chaque entrée du menu est associé un raccourci clavier. Nous verrons un peu plus loin comment les définir.
Le menu Affichage offre deux possibilités, intitulées WordWrap et FullWindow, que nous détaillerons un peu plus loin :
Ce menu est un peu différent du précédent car chacune de ses entrées peut être cochée ou décochée.
Pour commencer, voyons comment construire ce menu avec Lazarus.
Utilisation de l'éditeur de menu▲
Vous pouvez, si vous le désirez, effectuer les manipulations suivantes à partir de la version précédente de l'éditeur de texte (dans Exemple-ProgObjet1/Memo).
Commencez par déposer un menu principal (objet MainMenu1 de la classe TMainMenu) sur le formulaire. Cet objet représente la barre de menus.
Pour modifier le contenu de la barre des menus, il faut utiliser l'éditeur de menu, que l'on ouvre par un double clic sur le composant MainMenu1 déposé sur le formulaire :
Pour l'instant, le menu principal ne contient qu'une seule entrée, nommée New Item1. Pour en ajouter une deuxième, on fait un clic droit sur le New Item1 affiché dans l'éditeur de menu et on sélectionne Insérer un nouvel élément après.
Notre barre de menu contient à présent deux entrées (donc deux menus). Pour faire apparaître les libellés Fichier et Affichage à la place de New Item1 et New Item2, utilisez l'inspecteur d'objet et modifiez les propriétés Caption de ces deux composants (attention : cela n'est possible que si l'éditeur de menu est ouvert !).
Vous pouvez ensuite ajouter une première entrée au menu Fichier par un clic droit sur Fichier (dans l'éditeur de menu) et en sélectionnant Créer un sous-menu. On obtient ainsi une première entrée dans le menu Fichier. Elle est libellée New Item3 :
Pour ajouter une deuxième entrée après New Item3, faites un clic droit sur New Item3, puis sélectionnez Insérer un nouvel élément après :
À ce stade, vous avez certainement compris le principe de construction d'un menu sous Lazarus. Nous allons donc arrêter ici les explications concernant l'utilisation de l'éditeur de menu.
Encore deux petit détails avant de passer à l'aspect programmation :
- pour rendre une entrée du menu cochable, il faut mettre la propriété AutoCheck à True ;
- pour définir un raccourci clavier, utilisez la propriété ShortCutKey. Voici, par exemple, comment a été défini le raccourci clavier CTRL O pour l'entrée Ouvrir du menu Fichier :
Gestionnaires d'évènements des menus▲
Pour accéder au gestionnaire du clic sur une entrée de menu (ou le générer s'il n'existe pas encore), il suffit de cliquer une fois dessus.
En cliquant, par exemple, sur l'entrée Ouvrir du menu fichier, vous êtes automatiquement redirigé sur la procédure évènementielle suivante :
procedure
TForm1.MN_OuvrirClick(Sender: TObject);
var
NomFichier : String
;
begin
NomFichier := ZT_NomFichier.Text;
MM_Editeur.Lines.LoadFromFile(NomFichier);
end
;
Nous avons simplement réutilisé ici le code qui se trouvait précédemment dans la procédure évènementielle associée au bouton Ouvrir.
Notez que si un raccourci clavier a été défini, l'exécution de la procédure évènementielle sera également déclenchée lorsque l'utilisateur tape ce raccourci. Dans notre exemple, la procédure évènementielle associée à l'entrée Ouvrir sera donc également déclenchée si l'utilisateur tape CTRL O.
Même principe pour l'entrée Enregistrer :
procedure
TForm1.MN_EnregistrerClick(Sender: TObject);
var
NomFichier : String
;
begin
NomFichier := ZT_NomFichier.Text;
MM_Editeur.Lines.SaveToFile(NomFichier);
end
;
Enfin, pour déclencher la fermeture de l'application (entrée Quitter), nous appliquons simplement la méthode Close à Form1 :
procedure
TForm1.MN_QuitterClick(Sender: TObject);
begin
Form1.Close();
end
;
Les gestionnaires du menu Affichage provoquent une action différente selon que l'entrée du menu correspondante est cochée ou non.
L'entrée WordWrap active ou désactive le mode WordWrapLes mémos (classe TMemo) du mémo :
procedure
TForm1.MN_WordWrapClick(Sender: TObject);
begin
MM_Editeur.WordWrap:= Not
MM_Editeur.WordWrap;
end
;
Il est inutile de cocher ou décocher l'entrée dans le code, car nous avons mis AutoCheck à True et, dans ce cas, cela se fait automatiquement.
Lorsque l'entrée FullWindow est cochée, le mémo occupe la quasi-totalité de la fenêtre et la zone de texte permettant de saisir le nom du fichier disparaît :
Si on la décoche, le mémo revient à sa taille originale et la zone de texte réapparaît. Nous avons réalisé ceci de la manière suivante :
procedure
TForm1.MN_FullWindowClick(Sender: TObject);
begin
if
MN_FullWindow.Checked then
begin
MM_Editeur.Width := Form1.Width - 50
;
MM_Editeur.Height := Form1.Height - 50
;
Label1.Hide();
ZT_NomFichier.Hide();
end
else
begin
MM_Editeur.Width := LargeurMemo;
MM_Editeur.Height := HauteurMemo;
Label1.Show();
ZT_NomFichier.Show();
end
;
end
;
Ici, nous avons besoin de savoir si l'entrée du menu est cochée ou non, car il ne suffit pas, comme dans l'entrée WordWrap, d'inverser la valeur d'un booléen.
Si l'entrée FullWindow est cochée (MN_FullWindow.Checked), les dimensions du mémo (propriétés Width et Height) sont redéfinies comme celles du formulaire moins 50 pixels. Puis on fait disparaître l'étiquette et la zone de texte en leur appliquant la méthode Hide.
Si l'entrée FullWindow n'est pas cochée, les dimensions du mémo sont restaurées et l'on fait réapparaître la zone de texte et l'étiquette on leur appliquant la méthode Show.
Remarquez que pour pouvoir retrouver les dimensions d'origines du mémo, nous les avons sauvegardées dans les variables LargeurMemo et HauteurMemo au démarrage de l'application :
procedure
TForm1.FormCreate(Sender: TObject);
begin
LargeurMemo := MM_Editeur.Width;
HauteurMemo := MM_Editeur.Height;
end
;
Lorsque l'on affecte une nouvelle valeur à l'attribut Width (largeur) ou Height (hauteur) d'un composant, celui-ci est immédiatement réaffiché avec les nouvelles dimensions. Comment une simple affectation peut-elle provoquer ce résultat ? Nous avons déjà rencontré ce phénomène dans la modification de la propriété Text d'une zone de texte, qui provoque immédiatement l'affichage de sa nouvelle valeur. Nous reviendrons là-dessus dans le deuxième cours sur la programmation objet.
Boîtes de dialogues de Lazarus▲
Les boîtes de dialogue sont des fenêtres permettant de choisir des objets complexes (fichiers, couleurs, polices, dates, etc.). Elles possèdent en général un bouton de confirmation du choix ainsi qu'un autre bouton permettant d'annuler le choix. Dès que l'utilisateur clique sur le bouton de confirmation ou d'annulation, la boîte de dialogue disparaît.
Dans Lazarus, toutes les classes représentant des boîtes de dialogue sont des sous-classes de TCommonDialog. Cette dernière possède une méthode Execute, permettant d'afficher une boîte de dialogue à l'écran. De plus, cette méthode retourne la valeur false si l'utilisateur annule son choix et la valeur true s'il le confirme.
Vous trouverez les boîtes de dialogue sous l'onglet Dialog. Nous présenterons ici les boîtes de dialogues suivantes :
ainsi que l'utilisation de la fonction Confirmation, permettant d'afficher une boîte de dialogue pour confirmer un choix.
Cette partie du cours se termine par un exemple : une nouvelle version de l'éditeur de texteLes menus (classes TMainMenu et TMenuItem) utilisant des boîtes de dialogue.
Ouverture de fichier (classe TOpenDialog)▲
Les boîtes de dialogues d'ouverture de fichier évitent à l'utilisateur de taper le nom (et éventuellement le chemin) d'un fichier. Elles se présentent de la manière suivante :
Vous en avez certainement utilisé des centaines de fois, car on les trouve dans tous les logiciels utilisant des fichiers. Pour sélectionner un fichier, vous vous déplacez dans l'arborescence des fichiers de l'ordinateur. Une fois le répertoire atteint, il vous suffit de cliquer sur le nom du fichier.
Si l'utilisateur confirme de choix de fichier, l'attribut FileName de la boîte de dialogue contiendra le nom complet du fichier (avec extension et chemin si nécessaire).
Les boîtes de dialogue d'ouverture de fichier permettent également de filtrer les fichiers par leur extension. Par exemple, dans la boîte de dialogue de la figure précédente, seuls les fichiers d'extension « .txt » sont visibles. L'utilisateur ne pourra donc sélectionner qu'un fichier texte. Ceci permet d'éviter l'ouverture de fichiers de format incompatible avec le programme qui les traite.
La boîte de dialogue contient une zone de liste permettant à l'utilisateur de sélectionner un certain type de filtrage. La boîte dialogue suivante, par exemple, présente deux choix de filtrage :
On peut également définir un filtrage autorisant plusieurs extensions. Voici, par exemple, le résultat du filtrage simultané des extensions « *.txt » et « *.pas » :
Le filtrage des fichiers est défini par le contenu de la propriété Filter de la boîte de dialogue. Pour définir son contenu, le plus simple est d'utiliser l'éditeur de filtre. On y accède en cliquant sur les « … » de la propriété Filter dans l'inspecteur d'objet.
Chaque ligne de l'éditeur de filtre représente un choix de filtrage dans la zone de liste. Voici, par exemple, comment définir séparément deux modes de filtrage :
La colonne de gauche est un commentaire pour l'utilisateur. Il sert en principe à expliquer la signification de l'extension. La colonne de droite définit le filtre sous la forme « *.??? », où les trois caractères « ??? » sont ceux de l'extension.
Pour autoriser tous les formats, on utilise la notation « *.* » :
Pour définir un filtrage autorisant simultanément plusieurs extensions, il faut séparer les filtres par des points-virgules :
Enregistrement de fichier (classe TSaveDialog)▲
Les boîtes de dialogue d'enregistrement de fichier fonctionnent selon le même principe que les boîtes de dialogue d'ouverture de fichier, mis à part que le bouton Ouvrir est remplacé par un bouton Enregistrer :
Ouverture de fichier image (classe TOpenPictureDialog)▲
La classe TOpenPictureDialog est une variante de TOpenFileDialog qui permet de visualiser des fichiers images. L'intérêt principal de cette boîte de dialogue est que les fichiers images sont visualisés sous forme d'icônes. Comme ceci, par exemple :
Comme pour la classe TOpenPictureDialog, le fichier sélectionné se retrouve dans l'attribut FileName.
Choix d'une couleur (classe TColorDialog)▲
Une couleur est représentée, sous Lazarus, par le type TColor. La boîte de dialogue de sélection de couleur permet à l'utilisateur de sélectionner visuellement une couleur, sans se préoccuper de la manière dont elle est codée en mémoire :
Le programmeur, quant-à lui, récupère la couleur dans l'attribut Color. Il pourra ensuite utiliser cette valeur pour affecter cette couleur à n'importe quel composant graphique, sans se préoccuper lui non plus du codage des couleurs.
Choix d'une police (classe TFontDialog)▲
La police de caractères, c'est-à-dire la manière dont s'affiche une chaîne de caractères à l'écran, est représentée sous Lazarus par la classe TFont, qui possède de très nombreux attributs. La boîte de dialogue de sélection de police permet à l'utilisateur de sélectionner visuellement les propriétés d'une police de caractères :
Le programmeur récupère la police de caractères sélectionnée dans l'attribut Font, sous la forme d'un objet de la classe TFont.
Confirmation d'une action▲
Cette section ne présente pas une boîte de dialogue Lazarus, mais une fonction que j'ai écrite moi-même permettant d'afficher une boîte de dialogue de ce type :
Cette fonction, nommée Confirmation, est intégrée à la librairie entrees_sorties.pas. Si vous regardez son code, vous constaterez qu'elle utilise la fonction QuestionDlg. Il s'agit d'une fonction standard de Lararus, plus complexe et plus générale, permettant d'afficher une boîte de dialogue posant une question à l'utilisateur avec éventuellement plus de deux boutons.
La fonction Confirmation affiche une boîte de dialogue permettant de confirmer l'exécution d'une action potentiellement dangereuse. Elle retourne un booléen : True si l'utilisateur confirme son choix, False sinon.
Dans l'exemple présenté dans la figure ci-dessus, l'appel de la fonction serait :
Confirmation ('
Mise
en
garde
'
,
'
Voulez-vous
réellement
quitter
l
'
'
application
?
'
,
'
Non
'
,'
Oui
'
);
De manière générale, vous pouvez utiliser cette fonction pour confirmer des choix potentiellement dangereux de l'utilisateur. Voici son en-tête :
function
Confirmation (t,m,td,ta:string
) : boolean
;
Elle possède donc quatre paramètres de type String :
- t : titre de la boîte de dialogue ;
- m : message affiché dans la boîte de dialogue ;
- td : titre du bouton par défaut ;
- td : titre de l'autre bouton.
Le résultat retourné est True si l'utilisateur ne clique pas sur le bouton par défaut, c'est-à-dire s'il confirme vouloir exécuter l'action potentiellement dangereuse.
Exemple▲
L'exemple suivant se trouve dans le dossier Exemple-ProgObjet1/boîteDeDialogue. Il s'agit d'une version améliorée de l'éditeur de texte que nous avons utilisé pour expliquer le principe des menusLes menus (classes TMainMenu et TMenuItem). Cette nouvelle version intègre (entre autres) des boîtes de dialogue pour l'ouverture et l'enregistrement des fichiers.
Le menu Fichier est le même que dans la version précédente :
Le menu Affichage, par contre, a été remplacé par le menu Texte, qui permet de modifier la police de caractères ou la couleur du mémo.
Cette nouvelle version utilise quatre boîtes de dialogue :
- OpenDialog1 est la boîte de dialogue pour l'ouverture des fichiers. ;
- SaveDialog1 est la boîte de dialogue pour l'enregistrement des fichiers ;
- FontDialog1 est la boîte de dialogue pour le choix de police ;
- ColorDialog1 est la boîte de dialogue pour choisir la couleur.
Notez que ces boîtes de dialogue ne seront pas immédiatement visibles au démarrage du programme. Pour les afficher, il est nécessaire de leur appliquer la méthode Execute.
Ouverture de fichier▲
L'entrée Ouvrir du menu Fichier provoque l'exécution de la procédure suivante :
procedure
TForm1.MN_OuvrirClick(Sender: TObject);
begin
if
(OpenDialog1.Execute()) then
MM_Editeur.Lines.LoadFromFile(OpenDialog1.FileName);
end
;
Si la méthode Execute retourne True, l'utilisateur a confirmé le choix de fichier. Son nom se trouve donc dans l'attribut Filename. Ce fichier est chargé dans le mémoLes mémos (classe TMemo) MM_Editeur via la méthode LoadFromFileExemple de la classe TStringList.
Enregistrement de fichier▲
L'entrée Enregistrer du menu Fichier provoque l'exécution de la procédure suivante :
procedure
TForm1.MN_EnregistrerClick(Sender: TObject);
begin
if
(SaveDialog1.Execute()) then
if
not
FileExists (SaveDialog1.FileName) then
MM_Editeur.Lines.SaveToFile(SaveDialog1.FileName)
else
if
Confirmation('
Mise
en
garde
'
,
'
Fichier
existant.
'
+
'
Voulez
vous
écraser
l
'
'
ancienne
version
?
'
,
'
Ne
rien
faire
'
,'
Ecraser
'
)
then
MM_Editeur.Lines.SaveToFile(SaveDialog1.FileName);
end
;
Ici, nous testons l'existence du fichier pour éviter d'écraser par mégarde une version ultérieure. Si le fichier existe, la fonction ConfirmationConfirmation d'une action est utilisée pour confirmer le choix de l'utilisateur.
Choix de police▲
L'entrée Police du menu Texte provoque l'exécution de la procédure suivante :
procedure
TForm1.MN_PoliceClick(Sender: TObject);
begin
if
(FontDialog1.Execute) then
MM_Editeur.Font:= FontDialog1.Font;
end
;
La police sélectionnée est affectée à l'attribut Font du mémo, ce qui a pour effet de modifier la police de caractère du texte affiché dans le mémo.
Choix de couleur▲
L'entrée Couleur du menu Texte provoque l'exécution de la procédure suivante :
procedure
TForm1.MN_CouleurClick(Sender: TObject);
begin
if
(ColorDialog1.Execute()) then
MM_Editeur.Color:=ColorDialog1.Color;
end
;
La couleur sélectionnée est affectée à l'attribut Color du mémo, ce qui a pour effet de modifier la couleur du fond du mémo.
Composants divers▲
Composant image (classe TImage)▲
Le composant image permet d'afficher une image provenant d'un fichier. La plupart des formats images courants (jpg, gif, bmp, etc.) sont acceptés. Vous le trouverez sous l'onglet Additionnal :
L'attribut Picture▲
L'image elle-même est contenue dans l'attribut Picture (classe TPicture) du composant. La méthode LoadFromFile permet de charger une image dans un objet de cette classe. D'autre part, lorsqu'une image est chargée dans l'attribut Picture d'un composant image, elle est immédiatement affichée.
Par exemple, l'instruction suivante :
Image1.Picture.LoadFromFile('
Toto.jpg
'
)
charge le fichier image Toto.jpg dans le composant image Image1 (ou plus exactement dans son attribut Picture), ce qui a pour effet de l'afficher à l'écran.
Pour l'effacer, on utilisera la méthode Clear de la classe TPicture. En effet, cette méthode supprime l'image chargée dans un objet cette classe. Si on l'applique à l'attribut Picture d'un composant image, l'image affichée sur ce dernier sera immédiatement effacée.
Par exemple, l'instruction :
Image1.Picture.Clear();
efface l'image affichée dans le composant Image1.
Attributs définissant la manière d'afficher l'image▲
Pour bien comprendre les explications qui suivent, il faut avoir à l'esprit que les dimensions d'un composant image (en pixels) ne coïncident généralement pas avec les dimensions de l'image contenue dans un fichier.
La manière dont une image sera affichée à l'intérieur d'un composant image dépend essentiellement des attributs Stretch, Proportional et Center. Ces trois attributs sont tous des booléens.
Si Stretch vaut true, l'image est agrandie ou rapetissée de manière à remplir le composant image en hauteur ou en largeur. L'effet de Stretch dépend de la valeur de l'attribut Proportional :
- si Proportional vaut False, les dimensions de l'image sont ajustées en largeur et en hauteur. Cela aura généralement pour effet de déformer l'image, car les proportions du composant image ne sont pas nécessairement les mêmes que celles de l'image contenue dans le fichier ;
- si Proportional vaut True, l'image sera agrandie ou rapetissée en conservant ses proportions. Dans ce cas, le composant image sera rempli en largeur, mais pas forcément en hauteur ou inversement.
L'attribut Center définit le centrage de l'image. Par défaut Center = false et l'image n'est pas centrée. Son coin supérieur gauche correspond alors au coin supérieur gauche du composant image. Par contre, si Center vaut true, le centre du composant image coïncidera avec le centre de l'image.
Exemple▲
L'exemple présenté ici se trouve dans le dossier Exemple-ProgObjet1/Image. Ouvrez le fichier Visualisateur.lpi. Il s'agit d'une application très simple permettant d'afficher une image.
La fenêtre de l'application comprend une barre de menuLes menus (classes TMainMenu et TMenuItem) et un composant image.
Pour charger une image, utilisez l'entrée Ouvrir du menu Fichier. Elle vous permettra de sélectionner un fichier image à l'aide d'une boîte de dialogue d'ouverture d'imageOuverture de fichier image (classe TOpenPictureDialog).
Des exemples d'images se trouvent dans le répertoire Exemple-ProgObjet1/Oiseaux.
Vous pouvez ensuite vous amuser à modifier les attributs Stretch, Proportional ou Center du composant image via le menu Option.
Voici, par exemple, l'affichage de l'image de la cigogne avec Stretch = true et Proportional = False :
et la même image avec Stretch = true et Proportional = true :
L'entrée Ouvrir du menu fichier active la procédure évènementielle suivante :
procedure
TForm1.MN_OuvrirClick(Sender: TObject);
var
nfi : string
;
begin
if
OpenPictureDialog1.execute() then
begin
nfi := OpenPictureDialog1.FileName;
Image1.Picture.LoadFromFile(nfi);
end
;
end
;
OpenPictureDialog1 est la boîte de dialogue d'ouverture d'image. Si l'utilisateur a sélectionné une image, le nom du fichier image est affecté à la variable nfi, puis chargé dans le composant image Image1, ce qui a pour effet de l'afficher.
Liste de fichiers (classe TFileListBox)▲
Le composant liste de fichiers vous permettra d'afficher les fichiers contenus dans un répertoire et éventuellement d'effectuer un traitement sur ces fichiers. Vous le trouverez sous l'onglet Misc :
La classe TFileListBox représentant ce composant est une sous-classe de TListBox (zone de listeLes zones de liste (classe TListBox)), si bien que tous les attributs et méthodes des zones de liste peuvent être utilisés avec les listes de fichiers.
Le chemin du répertoire associé au composant est mémorisé dans l'attribut Directory.
La liste des fichiers est initialisée au démarrage de l'application. Si l'utilisateur modifie le contenu du répertoire pendant son exécution, la liste des fichiers affichés dans le composant ne sera pas automatiquement réactualisée. Pour la réactualiser, il faut lui appliquer la méthode UpdateFileList.
Exemple▲
L'exemple présenté ici (répertoire Exemple-ProgObjet1/ListeDeFichiers) est une nouvelle version du visualisateur d'image avec un composant liste de fichiers permettant de sélectionner l'image à afficher :
On accède à une image en cliquant sur son nom. Les touches de votre clavier représentant des flèches vers le haut ou le bas vous permettront de vous déplacer dans la liste (remarquez que ces touches fonctionnent également dans une zone de liste standard).
Le bouton Rafraîchir permet de réactualiser la liste des fichiers, au cas où elle aurait été modifiée pendant l'exécution du programme.
Un clic sur un élément de la liste de fichiers (ou un déplacement dans la liste via les « touches flèches ») provoque l'exécution de la procédure évènementielle suivante :
procedure
TForm1.FileListBox1Click(Sender: TObject);
var
nom_fichier : string
;
begin
nom_fichier:=FileListBox1.Directory+'
\
'
+
FileListBox1.GetSelectedText;
Image1.Picture.LoadFromFile(nom_fichier);
end
;
FileListBox1 est le composant liste de fichiers de l'application. La première instruction récupère le nom du fichier grâce à la méthode GetSelectedText des zones de liste. Ce nom est concaténé avec le chemin du répertoire mémorisé dans l'attribut Directory du composant et affecté à la variable nom_fichier.
La deuxième instruction charge l'image dans le composant image Image1.
Enfin, voici le code de la procédure évènementielle associée au bouton rafraichir :
procedure
TForm1.BT_Rafraichir_ListeClick(Sender: TObject);
begin
FileListBox1.UpdateFileList;
end
;
Multi-fenêtrage▲
Une application peut utiliser plusieurs fenêtres, s'appelant les unes les autres. En général, on aura une fenêtre principale qui permet d'appeler des fenêtres secondaires. Nous allons voir ici comment réaliser une application de ce type en partant directement d'un exemple. Il s'agit d'une nouvelle version du visualisateur d'images, qui permet cette fois-ci de visualiser simultanément deux images dans deux fenêtres distinctes. Vous trouverez ce projet dans le répertoire Exemple-ProgObjet1/MultiFenetrage.
Il y a donc en tout trois fenêtres : une fenêtre principale ainsi que deux fenêtres secondaires pour visualiser les images. La fenêtre principale permet de commander l'affichage.
La fenêtre principale comporte un menuLes menus (classes TMainMenu et TMenuItem), une liste de fichiersListe de fichiers (classe TFileListBox) et deux boutons radioLes boutons radio (classe TRadioButton) :
Les deux entrées du menu Voir permettent de faire apparaître les fenêtres (elles sont invisibles au démarrage de l'application). La liste de fichiers permet de sélectionner une image à afficher. Les boutons radio, quant-à eux, définissent dans quelle fenêtre sera affichée l'image sélectionnée.
Adjonction de nouvelles fenêtres▲
Voyons tout d'abord comment créer une application multi-fenêtres sous Lazarus. Lorsque vous démarrez la réalisation d'une nouvelle application, vous avez une seule fenêtre :
Par défaut, la classe représentant cette fenêtre est TForm1Déclaration de la classe TForm1 et de l'objet Form1, sous-classe de TFormLa classe TForm, et l'objet représentant votre fenêtre est Form1, déclaré comme objet de la classe TForm1.
L'unité associée à cette classe se nomme par défaut Unit1 et son code sera contenu dans le fichier Unit1.pas.
Pour ajouter une nouvelle fenêtre, sélectionnez l'entrée Nouvelle Fiche du menu Fichier. Cela fera apparaître une nouvelle fenêtre nommée Form2 :
La classe de cette fenêtre se nomme à présent TForm2 et l'unité associée Unit2 sera par défaut enregistrée dans le fichier Unit2.pas.
Définition de la fenêtre principale▲
Dans une application multi-fenêtres, la fenêtre principale est celle qui va s'afficher en premier au démarrage de l'application (par défaut, les fenêtres secondaires ne s'affichent pas). C'est donc par l'intermédiaire de cette fenêtre que les fenêtres secondaires pourront être affichées. Lorsque l'utilisateur ferme la fenêtre principale, les autres fenêtres sont automatiquement fermées. Par contre, l'inverse n'est pas vrai : si l'utilisateur ferme une fenêtre secondaire, la fenêtre principale reste ouverte.
Le fenêtre principale joue donc un rôle particulier par rapport aux autres fenêtres.
Mais comment Lazarus sait-il quelle fenêtre sera la fenêtre principale ?
Par défaut, c'est la première fenêtre créée.
Si, par mégarde, vous avez commencé par une fenêtre secondaire, vous pouvez redéfinir la fenêtre principale en éditant le fichier .lpr.
Voici, par exemple, le contenu du fichier lpr pour un projet à deux fenêtres :
program
project1;
{$
mode
objfpc
}
{$
H+
}
uses
{$
IFDEF
UNIX
}
{$
IFDEF
UseCThreads
}
cthreads,
{$
ENDIF
}
{$
ENDIF
}
Interfaces, //
this
includes
the
LCL
widgetset
Forms, Unit1, Unit2
{
you
can
add
units
after
this
}
;
{$
R
*.res
}
begin
RequireDerivedFormResource := True
;
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.CreateForm(TForm2, Form2);
Application.Run;
end
.
Si vous voulez que Form2 soit la fenêtre principale au lieu de Form1, il suffit d'inverser les appels de la méthode CreateForm :
program
project1;
{$
mode
objfpc
}
{$
H+
}
uses
{$
IFDEF
UNIX
}
{$
IFDEF
UseCThreads
}
cthreads,
{$
ENDIF
}
{$
ENDIF
}
Interfaces, //
this
includes
the
LCL
widgetset
Forms, Unit1, Unit2
{
you
can
add
units
after
this
}
;
{$
R
*.res
}
begin
RequireDerivedFormResource := True
;
Application.Initialize;
Application.CreateForm(TForm2, Form2);
Application.CreateForm(TForm1, Form1);
Application.Run;
end
.
De manière générale, la fenêtre principale est celle qui correspond au premier appel de la méthode CreateForm.
Renommage des unités et des fenêtres▲
Dans une application multi-fenêtres, je vous conseille de renommer les unités et les fenêtres. Car des noms comme Form1, Form2, Unit1, Unit2… sont très peu évocateurs.
Nommage des unités▲
Le nommage des unités se fait au moment de la sauvegarde du projet, car le nom d'une unité est lié au nom du fichier Pascal qui la contient.
Par exemple, pour changer le nom de l'unité Unit1 en UnitePrincipale, sélectionnez l'entrée Enregistrer le projet sous… dans le menu Projet. Vous aurez à un moment donné une boîte de dialogue vous demandant d'enregistrer Unit1.pas :
Il suffit alors de remplacer Unit1.pas par UnitePrincipale.pas :
et l'unité Unit1 sera alors automatiquement renommée UnitePrincipale dans l'éditeur de source :
Nommage des fenêtres▲
Pour renommer les fenêtres, il suffit de modifier la propriété Name de Form1, Form2, etc., via l'inspecteur d'objets.
Dans notre exemple, nous avons utilisé Frm_Principal pour la fenêtre principale, Frm1 pour la fenêtre 1 et Frm2 pour la fenêtre 2.
Remarquez que le renommage entraîne automatiquement le renommage des classes associées. Dans notre exemple, Form1 a été renommée Frm_Principal, ce qui a automatiquement entraîné le renommage de TForm1 en TFrm_Principal :
type
TFrm_Principal = class
(TForm)
.
.
end
;
var
Frm_Principal : TFrm_Principal;
Importation des unités▲
Notre exemple de projet multi-fenêtres contient trois unités nommées UnitePrincipale (associée à la fenêtre principale), UniteImage1 (associée à la fenêtre 1) et UniteImage2 (associée à la fenêtre 2).
En ouvrant le projet, vous constaterez que UniteImage1 et UniteImage2 sont importées dans UnitePrincipale :
unit
UnitePrincipale;
{$
mode
objfpc
}
{$
H+
}
interface
uses
Classes, SysUtils, FileUtil, Forms,
Controls, Graphics, Dialogs, Menus,
FileCtrl, StdCtrls, UniteImage1, UniteImage2;
Cette importation est nécessaire car l'unité principale contient des instructions agissant sur les composants des deux autres fenêtres.
De manière générale, si une unité A agit sur un composant d'une unité B alors l'unité A doit importer l'unité B.
Code de l'exemple▲
Pour bien comprendre le code de notre exemple , il est préférable d'avoir lu les parties du cours concernant les menusLes menus (classes TMainMenu et TMenuItem), les listes de fichiersListe de fichiers (classe TFileListBox), les boutons radioLes boutons radio (classe TRadioButton) et les composants imageComposant image (classe TImage).
Précisons que toutes procédures évènementielles décrites ici se trouvent dans l'unité UnitePrincipale associée à la fenêtre principale.
Voici la procédure évènementielle gérant le clic sur une entrée de la liste des fichiers :
procedure
TFrm_Principal.FileListBox1Click(Sender: TObject);
var
nom_fichier : string
;
begin
nom_fichier:=
FileListBox1.Directory+ '
\
'
+FileListBox1.GetSelectedText;
if
BR_Fenetre1.Checked then
Frm1.Image1.Picture.LoadFromFile(nom_fichier)
else
Frm2.Image1.Picture.LoadFromFile(nom_fichier);
end
;
FileListBox1 est le nom de la liste de fichiers.
La première instruction construit le nom complet du fichier image à partir du chemin du répertoire qui la contient (stocké dans l'attribut Directory du composant FileListBox) et du nom de fichier sélectionné par l'utilisateur.
Le bouton radio étiqueté Fenêtre 1 se nomme BR_Fenetre1. S'il est coché, l'image est affichée dans la fenêtre 1. Sinon, elle est affichée dans la fenêtre 2.
Comme les composants image ne se trouvent pas dans la fenêtre principale, il est nécessaire de préfixer leurs noms par le nom de la fenêtre qui les contient. On accède donc ici au composant image de la fenêtre 1 par Frm1.Image1 et au composant image de la fenêtre 2 par Frm2.Image2.
L'entrée Fenetre1 du menu Voir déclenche l'exécution de la procédure suivante :
procedure
TFrm_Principal.MN_Voir_Fenetre2Click(Sender: TObject);
begin
Frm2.Show;
end
;
On applique la méthode Show à Frm2 pour faire apparaître la fenêtre.
L'entrée Fenetre2 du menu Voir utilise le même principe pour la deuxième fenêtre :
procedure
TFrm_Principal.MN_Voir_Fenetre1Click(Sender: TObject);
begin
Frm1.Show;
end
;
Exercices▲
Téléchargez les exercices :
Pour obtenir les corrigés, le téléchargement n'est possible que via un login et un mot de passe, que vous pouvez obtenir en envoyant un mail à l'adresse suivante :
en précisant un peu qui vous êtes et les raisons pour lesquelles ce cours vous intéresse.