I. Approche générale

I-A. Présentation

Ce tutoriel a pour but de présenter la programmation avec Turbo Vision sous l'environnement Turbo Pascal. Seront ici abordés les sujets traitant de la mise en place d'une interface, et de ses moyens disponibles pour la contrôler. Ce tutoriel fait appel à certaines connaissances en matière de Programmation Orientée Objet, avec Pascal : si les notions de ce type de programmation vous perturbent, il serait préférable de vous documenter au sujet de la POO. Nous verrons dans cette première partie comment est construite cette librairie, et les moyens à mettre en œuvre pour l'utiliser.

Turbo Vision est né dans l'environnement Pascal au début des années 90, avec la sortie commerciale de Turbo Pascal 6. Son but était de permettre de développer des interfaces hommes/machines relativement simples et agréables, tout en garantissant un bon niveau d'efficacité. Ainsi Turbo Vision est une plate-forme complète pour développer des écrans performants.

I-B. Principe de programmation

I-B-1. Le rôle de la Programmation Orientée Objet

Entrons dès maintenant dans le vif du sujet. L'utilisation de Turbo Vision se base sur la Programmation Orientée Objet (POO), et ses trois concepts principaux : héritage, encapsulation et polymorphisme. La Programmation Orientée Objet joue ainsi un rôle majeur dans la construction d'une interface spécifique, et l'héritage est certainement la notion la plus utilisée dans Turbo Vision. Comme nous le verrons plus tard, tous les objets définis dans cette plate-forme descendent d'un seul objet TObject, et l'utilisateur définit ensuite ses propres objets à partir des objets existants, en créant un héritage. Ceci permet de garder une compatibilité ascendante afin de conserver l'homogénéité du système Turbo Vision : l'héritage évite ainsi à l'utilisateur de reprogrammer les mécanismes qui seraient propres à ses objets si il n'y avait pas eu d'héritage. Il convient également, pour la suite de ce tutoriel, de connaître les notions de la POO, et de savoir les appliquer en Pascal objet.

I-B-2. L'instruction « inherited »

Arrêtons-nous un instant sur l'instruction « inherited ». Les habitués de la programmation en Pascal objet sauront sans aucun doute identifier le but de cette instruction, mais il paraît nécessaire d'y jeter un œil. En effet, « inherited » permet, lors de la déclaration des méthodes d'un nouvel objet créé, d'appeler une méthode de l'objet parent dont dérive l'objet nouvellement créé. Ceci évite donc de taper inutilement du code qui aurait déjà été défini dans les objets supérieurs.

I-B-3. Constructeurs et Destructeurs

Un autre point important de la programmation objet est la notion de constructeur/destructeur. Un constructeur est une méthode qui permet de créer un objet en mémoire et d'enregistrer l'objet créé dans la table des méthodes virtuelles (TMV, « virtual methods table » ou VMT en Anglais). A contrario, un destructeur supprime l'objet de la TMV et libère l'espace mémoire qui lui est associé. La convention sous Turbo Pascal veut qu'un constructeur s'appelle Init et qu'un destructeur s'appelle Done (nous utiliserons ces noms pour notre programmation). Vous l'aurez compris, il existe un lien très fort entre les méthodes, qu'elles soient des constructeurs, des destructeurs ou de simples procédures, et l'héritage puisque une grande partie du code est déjà écrite et qu'il serait stupide de ne pas l'utiliser ! C'est ainsi que pour chaque objet défini par l'utilisateur, on utilisera les méthodes de l'objet parent : un constructeur utilisera le constructeur de son objet parent tout comme le destructeur (À noter que, puisque tous les objets de Turbo Vision dérivent de TObject, on fait toujours un appel au constructeur TObjet.Init et au destructeur TObjet.Done, et ceci avec n'importe quel objet de Turbo Vision). Nous verrons, au moment voulu, un exemple d'utilisation de l'instruction « inherited ».

I-B-4. Utilisation des pointeurs

Un point important avec la programmation sous Turbo Vision va être la gestion de la mémoire. Sous DOS, l'espace mémoire étant limité à 16 Mo en mode protégé, un tel système se doit d'optimiser la gestion : les pointeurs sont eux aussi mis à contribution, car ils jouent un rôle très important dans la préservation de l'espace mémoire. Les pointeurs permettant une allocation dynamique, la gestion de l'espace mémoire s'en trouve simplifiée, et l'on ne risque pas (ou l'on risque moins) de provoquer un débordement de tas (« Heap Overflow » en anglais). Il est impératif de maîtriser correctement l'utilisation des pointeurs, et l'allocation de mémoire. Cette dernière se fait grâce à l'instruction « New », qui depuis la version 7 de Turbo Pascal, accepte les constructeurs d'objets en paramètre. Nous aurons l'occasion de voir ceci en détail plus tard.

I-C. Les objets de Turbo Vision

I-C-1. Les unités disponibles

I-C-1-a. Les unités « basiques »

Analysons tout d'abord la structure de cette librairie. Turbo Vision est ainsi composé de plusieurs unités, chacune ayant un but précis. L'unité la plus « basique » mais certainement la plus importante est l'unité Objects. Elle contient et définit notamment le type TObject, type dont dérivent tous les objets utilisés en Programmation Orientée Objet sous Turbo Pascal. Cette unité contient également des objets permettant la gestion des listes de chaînes (TStringList), des flux de données (TStream) et des collections (TCollection). L'autre unité « de base » est App. Cette unité déclare et définit les deux objets permettant de construire une véritable application : TProgram et sa version surchargée plus complète TApplication. Ces deux objets permettent donc de mettre en œuvre les mécanismes nécessaires à la gestion des événements utilisateurs tout en gérant les éléments indispensables d'une application classique (Barre de menus, barre d'état, fenêtres, etc.). On trouve ensuite l'unité Views, où sont définis les objets TWindow, représentant une fenêtre classique, ou encore TListViewer, un objet parent de TListBox.

I-C-1-b. Les unités plus élaborées

L'unité Dialogs définit les objets TDialog représentant des boîtes de dialogue, mais également tous les objets visuels tels que les boutons (TButton), les champs de texte statiques (TStaticText), les champs de texte « configurables » (TLabel), les listes (TListBox), les boutons radio (TRadioButtons), les cases à cocher (TCheckBoxes)… Notons finalement l'existence des unités Menus (qui définit les objets utilisés lors de la construction de menus), Editors (qui contient des objets pour l'édition de fichiers par exemple), StdDlg (pour utiliser des boîtes de dialogue déjà construites) et Validate, qui définit des objets permettant le contrôle de données entrées par l'utilisateur.

I-C-2. Les objets « basiques »

Après une analyse légère de cette liste non exhaustive, on remarque que les principaux objets que nous allons manipuler, dans un premier temps, sont TApplication et TWindow. TApplication va nous permettre de mettre en place l'environnement du programme et TWindow sera utilisé pour afficher une fenêtre basique. Nous pourrons également utiliser TDialog, mais il sera préférable au tout début de bien explorer les possibilités de l'objet TApplication, afin d'avoir une base solide et agréable pour la suite du développement.

I-D. Exemple de programmation

I-D-1. Mise en place de l'application

Nous allons tout d'abord commencer par nous familiariser avec TApplication. Pour ce faire, nous allons créer une instance de cet objet. Une telle instance est initialisée ensuite à l'aide de trois méthodes : son constructeur Init, la procédure de boucle infinie Run, et enfin son destructeur Done. La méthode Run permet de gérer les événements déclenchés : c'est la boucle principale du programme. Observons le code pour cet exemple, et le résultat obtenu à l'écran :

 
Sélectionnez
program maPremiereApplication;

uses
  Objects, App;

var
  MonApplication: TApplication;

begin
  MonApplication.Init;
  MonApplication.Run; 
  MonApplication.Done;
end.

Image non disponible

Nous voyons donc, en haut de l'écran, une barre grise qui recevra plus tard notre barre de menu. De même, en bas de l'écran, on peut observer une barre d'état qui affiche un premier raccourci « ALT-X » permettant de quitter l'application. Ces deux barres sont par défaut initialisées lors de la construction de l'instance d'un objet TApplication. De la même manière, le fond (appelé « Bureau » sous Turbo Vision, « Desktop » en Anglais) est initialisé avec la création de l'instance en mémoire. Ceci prmet de définir un environnement de base pour un développement rapide des applications.

I-D-2. Définition de la barre de menus et de la barre d'état

Nous allons à présent définir nos propres barres. Notons dès à présent que les outils nécessaires à la création de barres se trouvent dans l'unité Menus : nous devrons donc ajouter Menus à la clause « uses ». Pour effectuer la modification des barres, nous devons surcharger les méthodes InitMenuBar et InitStatusLine, pour affecter nos propres valeurs aux variables de l'application. Notons qu'il existe deux variables spéciales, définies dans l'unité App, pour ces opérations : MenuBar, de type PMenuView, et StatusLine, de type PStatusLine. La méthode InitMenuBar affecte par défaut une valeur à la variable MenuBar, tout comme la méthode InitStatusLine qui définit la variable StatusLine. Lors de la surcharge de InitMenuBar et de InitStatusLine, nous n'aurons plus qu'à affecter nos propres valeurs à ces variables. Notons que InitMenuBar et Init StatusLine sont automatiquement appelées par le constructeur de l'objet TApplication, ce qui veut donc dire que nous n'aurons pas à appeler ces méthodes explicitement. Comme nous redéfinissons des méthodes de TApplication, nous devons déclarer un autre type descendant de TApplication : voyons à présent le code de cet exemple d'héritage :

 
Sélectionnez
type
  TMonApplication = object(TApplication)
  public
    procedure InitStatusLine; virtual;
    procedure InitMenuBar; virtual;
  end;

procedure TMonApplication.InitStatusLine;
var
  Bounds: TRect;
begin
  Bounds.Assign(0, 24, 80, 25);
  StatusLine:= New(PStatusLine, Init(Bounds,
                   NewStatusDef(0, 0,
                   NewStatusKey('~Alt+X~ Quitter', kbAltX, cmQuit,
                   NewStatusKey('Test', 0, 0, nil)), nil)));
end;

procedure TMonApplication.InitMenuBar;
var
  Bounds: TRect;
begin
  Bounds.Assign(0, 0, 80, 1);
  MenuBar := New(PMenuBar,Init(Bounds,
                 NewMenu(NewSubMenu('~F~ichier', 0,
                 NewMenu(
                 NewItem('~Q~uitter', 'Alt+X', kbAltX, cmQuit, 0, nil)),
                 NewItem('~A~Propos', 'Alt+A', kbAltA, cmQuit, 0, nil)
                ))));
end;

Image non disponible

Nous voyons à présent s'afficher en haut et en bas de l'écran la barre de menu et la barre d'état que nous avons définies dans les méthodes InitMenuBar et InitStatusLine. Comme on peut le constater, la construction de ces barres est assez complexe, car elle se fait en réalité en une seule instruction : le principe est d'emboîter des instructions de création de pointeurs (instructions commençant par « New »). On obtient ainsi une espèce de liste chaînée d'objets, qui définit un menu entier. Observons maintenant les noms utilisés pour les éléments des menus. Certaines lettres de ces intitulés sont entourées de tilde (~) : ceci permet de mettre en surbrillance ces lettres et d'y associer un raccourci clavier (équivalent à Alt+lettre). De même, on trouve des « kbAltX » ou des « kbAltA » : ceci correspond à un code clavier défini en constante, le nom de la constante définissant généralement le type de raccourci. Enfin, on trouve la constante cmQuit, qui définit une commande spécifique. Sachez qu'il existe plusieurs constantes comme celle-ci, chacune ayant un rôle particulier (nous étudierons tout ceci ultérieurement). Notons qu'il existe également des instructions spéciales permettant d'utiliser des menus prédéfinis. Ces instructions sont StdFileMenuItems, pour le menu Fichier, StdEditMenuItems, pour le menu d'édition, StdWindowMenuItems, pour le menu Fenêtre, et StdStatusKeys pour la barre d'état. Nous obtenons alors des barres très complètes, et dont les commandes et raccourcis ont déjà été définis, ce qui évite ainsi de recréer des menus de toute pièce.

I-D-3. Création d'une fenêtre

Il est à présent temps de créer notre première fenêtre. Comme nous l'avons vu précédemment, une fenêtre est définie par l'objet TWindow de l'unité Views (il ne faudra pas oublier d'ajouter l'unité Views à la clause « uses »). Dans notre propre objet descendant de TApplication, nous allons définir un attribut privé représentant la fenêtre : cet attribut sera de type PWindow, ce sera donc un pointeur vers un objet de type TWindow. Cette syntaxe est obligatoire, en vue de la préservation et de l'optimisation de l'espace mémoire. Enfin nous allons modifier le constructeur et le destructeur de notre objet TMonApplication, afin de prendre en compte la création de la fenêtre. Voici le code correspondant aux modifications apportées :

 
Sélectionnez
type 
  TMonApplication = object(TApplication)
  private
    MaFenetre: PWindow;
  public
    constructor Init;
    destructor Done; virtual;
    procedure InitStatusLine; virtual;
    procedure InitMenuBar; virtual;
  end;

constructor TMonApplication.Init;
var 
  Bounds: TRect;
begin
  if not inherited Init then Fail;
  Bounds.Assign(1, 1, 70, 20);
  MaFenetre := New(PWindow, Init(Bounds, 'Test', wnNoNumber));
  InsertWindow(maFenetre);
end;

destructor TMonApplication.Done;
begin
  MaFenetre^.Done;
  inherited Done;
end;

Image non disponible

Observons cette dernière version de l'objet TMonApplication : le constructeur et le destructeur ont été redéfinis selon notre désir. Nous obtenons ainsi quatre méthodes de l'objet TMonApplication qui ont été surchargées. On remarque, dans le constructeur Init, l'utilisation de l'instruction inherited : cette instruction permet d'appeler la méthode de l'objet parent. Dans notre cas, nous appelons donc le constructeur de l'objet parent, TApplication. A la suite de cette instruction se situe le code pour la création de la fenêtre. La variable Bounds de type TRect spécifie les bordures de la fenêtre, par rapport au coin supérieur gauche. On trouve ensuite l'instruction principale pour la création de la fenêtre : un appel à New permet, ici, de créer un objet PWindow, ou plus explicitement de créer en mémoire un objet de type TWindow. Le passage en paramètre à New du constructeur de l'objet va permettre d'initialiser la fenêtre avec les paramètres du constructeur (Bounds, « Test », wnNoNumber). L'instruction New retournant un pointeur, nous affectons cette valeur à notre fenêtre. Donc, pour résumer, New va créer un objet TWindow, va l'initialiser avec les paramètres transmis, et va ensuite retourner un pointeur vers l'objet TWindow, que nous affecterons à notre variable maFenêtre. À ce stade, l'objet est donc créé en mémoire, il ne reste plus qu'à l'afficher à l'écran et à lancer sa boucle de gestion d'événements. Ceci est fait avec l'appel de la méthode InsertWindow. Observons maintenant le destructeur de l'objet TMonApplication : son but étant de libérer l'espace mémoire associé à l'instance de l'objet, nous l'avons modifié afin qu'il désalloue tout d'abord la mémoire utilisée par la fenêtre avant d'appeler le destructeur de l'objet parent.

I-D-4. Précisions sur l'utilisation de l'instruction « inherited »

On peut remarquer à ce sujet, que l'utilisation de inherited n'est pas la même, car elle ne se fait pas au même endroit à l'intérieur des méthodes surchargées. Ainsi, dans le constructeur Init, l'appel se fait au début, car il est impératif que l'objet soit initialisé avant d'y faire toute opération dessus. Au contraire, pour le destructeur Done, il faudra que tous les objets dépendants d'un objet soient libérés avant la destruction. C'est pour cette raison, que l'appel à inherited Done est fait en dernier, après la libération des objets dépendants (dans notre cas, après la libération de l'objet maFenêtre).

II. Conclusion

À ce stade, nous pouvons donc voir à l'écran les résultats de nos travaux. Notre environnement de travail est en place : nous pouvons utiliser la barre de menus et la barre de d'état, et nous disposons d'une fenêtre « classique » pour afficher des informations. La base de la programmation est donc posée.

Ceci marque la fin de cette première approche de Turbo Vision, où nous avons vu comment cette librairie fonctionnait.

III. Turbo Vision plus en détail

III-A. Présentation

Cette page constitue la seconde partie du tutoriel concernant la programmation sous Turbo Vision, en Turbo Pascal. Nous allons ici analyser des possibilités plus poussées de Turbo Vision : il est conseillé de lire la première partie du tutoriel si vous ne vous sentez pas à l'aise avec la Programmation Orientée Objet sous Turbo Pascal, ou si vous connaissez pas ou peu Turbo Vision. Nous étudierons ici plus profondément les composants utilisables pour la construction d'interfaces graphiques, et nous analyserons la gestion des événements. Ceci sera suivi d'un exemple de programme pour illustrer le tout.

III-B. Programmation événementielle ?

III-B-1. Les différents types d'événements

III-B-1-a. Une gestion sommaire mais efficace

Comme nous l'avons brièvement vu dans la première partie du tutoriel, Turbo Vision offre des possibilités de gestion d'événements : cette gestion reste cependant basique car la programmation événementielle n'en était qu'à ses débuts lors de la sortie commerciale de Turbo Pascal 6 et 7 (rappelons que la dernière version de Turbo Pascal est sortie en 1992). Ainsi Turbo Vision permet de gérer les événements issus du clavier, de la souris, ainsi que les événements de « broadcast », c'est-à-dire de communication inter-composants, les commandes spéciales, et les événements définis par l'utilisateur.

III-B-1-b. Le type TEvent

La gestion de ces événements passe par plusieurs entités : tout d'abord, il existe un objet défini dans l'unité « Drivers », TEvent, qui permet de définir l'origine d'un événement. Cet objet est construit d'une façon assez étonnante, car il est considéré comme variant (les utilisateurs de Delphi sauront certainement imaginer le fonctionnement de cet objet). Ainsi la structure d'une instance de TEvent est modifiée selon le contenu d'un champ correspondant au type d'événement détecté. Voici la définition de l'objet TEvent, telle qu'on peut la trouver dans l'aide de Turbo Pascal :

 
Sélectionnez
type
  TEvent = record
    What: Word;
    case Word of
      evNothing: ();
      evMouse: (
        Buttons: Byte;
        Double: Boolean;
        Where: TPoint);
      evKeyDown: (
        case Integer of
          0: (KeyCode: Word);
          1: (CharCode: Char;
    ScanCode: Byte));
      evMessage: (
        Command: Word;
        case Word of
          0: (InfoPtr: Pointer);
          1: (InfoLong: Longint);
          2: (InfoWord: Word);
          3: (InfoInt: Integer);
          4: (InfoByte: Byte);
          5: (InfoChar: Char));
  end;

III-B-1-c. Analyse du fonctionnement de TEvent

Analysons tout d'abord la structure de cet objet. Le champ What, de type word, désigne le type d'événement détecté. On dispose ici de quatre possibilités : evNothing, qui désigne l'absence d'événement, evMouse, qui définit les événements de la souris, evKeyDown, pour les événements clavier, et enfin evMessage, pour la gestion des événements tels que les « broadcast », ou les commandes spéciales. Ensuite chaque sous-champ est défini seulement selon le type d'événement : ainsi, si l'événement est de type evMouse, les champs Buttons, Double et Where seront affectés en conséquence. Pour information, le champ Buttons définit quel bouton de souris a été pressé (ou relâché), Double est mis à « True » si il y a eu un double-clic, et Where donne les coordonnées de la souris au moment du clic au format TPoint. De la même façon, keycode donne le code de la touche qui a été pressée lors d'un événement clavier, et la champ Command donne le code de la commande associée à l'événement.

III-B-2. Les mnémoniques utilisables

Comme nous l'avons vu précédemment, il existe des mnémoniques spéciales, correspondant à des constantes, et définissant des cas particuliers d'événements. Nous avons déjà vu les mnémoniques evXXXX dans la définition de l'objet TEvent : ces constantes forment la liste des événements que l'on peut rencontrer sous Turbo Vision. Notons qu'il existe plusieurs autres catégories de mnémoniques : les kbXXXX qui définissent les constantes pour les touches clavier, les mbXXXX pour les boutons de la souris et certainement les plus importantes, les cmXXXX pour les messages de communication.

III-B-3. La gestion des événements

III-B-3-a. Création d'événements personnalisés

La gestion des événements prédéfinis est déjà présente dans la librairie Turbo Vision, et cette implémentation permet d'ores et déjà d'effectuer les opérations basiques de manière efficace. Il peut cependant arriver que l'utilisateur soit obligé de définir ses propres commandes et donc de définir ensuite un gestionnaire pour ses propres événements. Ceci peut s'illustrer par le fait de vouloir afficher par exemple sa propre boîte de dialogue : l'utilisateur crée ainsi un événement spécifique, avec la définition d'une nouvelle constante, ensuite définit son propre gestionnaire d'événements, pour mettre en œuvre par exemple l'affichage sa boîte de dialogue.

III-B-3-b. Le gestionnaire d'événements

Tout objet réactif aux événements (objets TApplication, TProgram, TWindow, TDialog, etc.) possède un gestionnaire d'événements : cette méthode spéciale, appelée HandleEvent, définit le comportement de l'objet après détection d'un événement. Pour définir son propre gestionnaire d'événements, il faut donc surcharger la méthode HandleEvent dans l'objet qui va recevoir l'événement : il faudra donc au besoin créer un héritage afin de surcharger HandleEvent. De manière générale, on préfèrera surcharger la méthode HandleEvent de l'objet TApplication, pour assurer la gestion de tous les messages, sachant qu'ils sont distribués par Turbo Vision aux objets susceptibles d'y répondre. Comme nous l'avons vu pour les constructeurs et destructeurs, la surcharge s'effectue impérativement avec l'utilisation d' « inherited », sinon les événements non gérés par le gestionnaire réécrit ne seront pas gérés. En conséquence, l'application ne pourra, par exemple, pas quitter ou une sortie vers le Dos Shell sera impossible.

III-C. Les « composants » disponibles

III-C-1. Un composant simple : le bouton

Dans la première partie du tutoriel, nous avions vu brièvement une liste non exhaustive de composants disponibles avec Turbo Vision. Ainsi, outre les objets définissant une application, tels que TProgram et TApplication, nous avons découvert les objets définis dans les unités « Views », « Dialogs » et « Menus ». Commençons tout d'abord par un des composants les plus simples : TButton. Cet objet permet de définir un bouton, tel qu'on en trouve dans toute interface. La programmation de cet objet est très simple et il n'est généralement pas nécessaire de surcharger ses méthodes : il s'utilise simplement en le posant dans la fenêtre, et en configurant le raccourci clavier et l'événement associé.

 
Sélectionnez
Bounds.Assign(19, 23, 31, 25);
OK := New(PButton, Init(Bounds, 'OK', cmCancel, bfDefault + bfGrabFocus));

Analysons le constructeur de l'objet TButton. Le premier paramètre Bounds définit les limites du bouton (Attention : un bouton tient sur deux lignes, car on considère également son ombre !). Le second paramètre donne l'intitulé qui apparaîtra sur le bouton. Le troisième paramètre est certainement le plus intéressant : il spécifie la commande qui sera envoyée à l'objet contenant le bouton. Dans notre cas, la commande sera cmCancel : l'envoi de cet événement à la fenêtre provoquera sa fermeture.

III-C-2. Informations supplémentaires sur les barres de menus

Revenons maintenant à la barre de menu que nous avions définie précédemment : elle constitue une bonne illustration de la gestion des événements. Voici l'implémentation que nous avons utilisée dans notre application :

 
Sélectionnez
Bounds.Assign(0, 0, 80, 1);
MenuBar := New(PMenuBar,Init(Bounds,
               NewMenu(NewSubMenu('~F~ichier', 0,
               NewMenu(
               NewItem('~Q~uitter', 'Alt+X', kbAltX, cmQuit, 0, nil)),
               NewItem('~A~ Propos', 'Alt+A', kbAltA, cmQuit, 0, nil)))));

Penchons-nous sur le constructeur : le paramètre Bounds donne les limites de la barre de menus, et on observe ensuite la liste d'instructions permettant de créer les menus et sous-menus. L'on peut ainsi observer l'utilisation, pour chaque élément du menu, des mnémoniques présentées auparavant, car la barre de menus est un composant susceptible de détecter et de gérer des événements clavier ou souris. Le point important se trouve dans le constructeur des sous-éléments du menu : on trouve ainsi, après la déclaration de l'intitulé (par exemple '~Q~uitter') et du paramètre ('Alt+X'), un code barbare puis la commande correspondant à l'événement. Ce code correspond à la séquence clavier créant l'événement : dans notre cas, la mnémonique kbAltX correspond à la combinaison Alt+X. Une liste de ces codes est disponible dans l'aide de Turbo Pascal. Enfin, on trouve dans l'ordre des paramètres du constructeur, la commande associée à l'événement. Dans notre cas, nous utiliserons la commande cmQuit, qui provoque la fermeture de l'application.

III-C-3. Un composant utile : TInputLine

Analysons à présent le comportement d'un autre composant : TInputLine. Cet objet définit une zone de texte où l'utilisateur peut entrer des données (cet objet est comparable au composant TEdit sous Delphi). Cet objet sert donc à obtenir des informations entrées par l'utilisateur et par la suite d'en afficher, de façon très simple. Voyons un exemple d'utilisation de ce composant :

 
Sélectionnez
Bounds.Assign(20, 3, 32, 4);
Input := New(PInputLine, Init(Bounds, 20));
Insert(Input);

Bounds.Assign(15, 6, 37,1 6);
Output := New(PInputLine, Init(Bounds, 255));
Insert(Output);

Voici la définition de deux objets de type TInputLine : la syntaxe classique avec utilisation de New et du constructeur est utilisée. On note également la présence de la variable Bounds de type TRect : son utilisation est obligatoire pour la définition des limites des composants. La récupération ou la modification de la valeur affichée se fait par l'intermédiaire de deux méthodes spéciales : getData et setData. GetData permet donc, comme son nom l'indique, de récupérer la valeur du champ, puis de l'exploiter sachant que les données récupérées sont de type string. SetData permet d'affecter une valeur, de type string, au champ d'édition. La surcharge de ces deux méthodes permet de définir le type des éléments récupérés lors de la saisie : on peut ainsi choisir de restreindre la saisie aux chiffres seulement, et n'utiliser que des entiers.

III-C-4. Principes généraux de programmation

Ceci est donc une liste non-exhaustive des composants que l'on peut utiliser sous Turbo Vision : il en existe évidemment bien d'autres, comme les labels (TLabel), les textes statiques (TStaticText), les listes déroulantes (TListBox), les boutons radio (TRadioButtons), les cases à cocher (TCheckBoxes). Notons cependant que la programmation de ces composants est, dans l'esprit, tout à fait semblable : nous utilisons toujours une variable de type TRect pour définir les limites du composant, un évènement peut être associé à l'objet, tout comme une commande. Ceci assure une certaine facilité de programmation, car les principes à appliquer sont les mêmes, dans la plupart des cas.

III-D. Boîtes de dialogue

III-D-1. Les boîtes de dialogue standards

Dans un système d'interfaces homme/machine, les boîtes de dialogue prennent une part importante, car ce sont généralement les seuls outils aisément malléables, permettant d'échanger des données avec l'utilisateur. Sous Turbo Vision, il existe deux boîtes de dialogue prédéfinies, dans l'unité « StdDlg » (qu'il faudra ajouter à la clause « uses »). On peut ainsi trouver les boîtes suivantes : TFileDialog, qui permet de chercher un fichier sur les disques durs ou d'en spécifier un nouveau, et TChDirDialog, qui permet de rechercher un répertoire ou d'en créer un nouveau. Ces deux boîtes de dialogue autorisent donc des opérations de recherche ou de création de fichiers, mais il est vrai que ceci peut s'avérer assez limité lors du développement de grosses applications.

III-D-2. Définition de boîtes de dialogue personnalisées

Pour contrer cette défaillance, il faut remonter dans la hiérarchie des classes, car les deux boîtes de dialogue citées ici ont un ancêtre commun, l'objet TDialog. TDialog définit toutes les méthodes nécessaires pour l'affichage et la gestion des événements, il nous suffira donc de surcharger les méthodes intéressantes pour notre application puis définir les attributs privés pour les traitements spécifiques de notre boîte de dialogue :

 
Sélectionnez
type 
  PMonDialog = ^TMonDialog;
  TMonDialog = object(TDialog)
  private
    Texte: PStaticText;
    OK: PButton;
  public
    constructor Init;
    destructor Done; virtual;
  end;

constructor TMonDialog.Init;
var
  Bounds: TRect;
  i: Integer;
begin
  Bounds.Assign(20, 2, 71, 10);
  if not inherited Init(Bounds, 'Test Dialogue') then Fail;
  Bounds.Assign(3, 2, 48, 3);
  Texte := New(PStaticText, Init(Bounds, 'Créé par Wormful_sickfoot pour developpez.com'));
  Insert(Texte);
  Bounds.Assign(20, 5, 30, 7);
  OK := New(PButton, Init(Bounds, '~O~K', cmCancel, bfDefault+bfGrabFocus));
  Insert(OK);
end;

destructor TMonDialog.Done;
var
  i: Integer;
begin
  Texte^.Done;
  OK^.Done;
  inherited Done;
end;

Nous voyons donc que nous avons surchargé le constructeur et le destructeur de notre propre objet : le constructeur nous permet ainsi d'initialiser le champ privé texte, qui affiche l'information voulue, et le destructeur libère la mémoire associé à l'objet. Ceci est l'archétype d'une programmation de boîte de dialogue personnalisée. Dans notre cas, cette boîte de dialogue possède un texte pour afficher nos informations et un bouton pour quitter la boîte : ce type de traitement est typique d'une boîte de dialogue « À Propos ».

III-D-3. Utilisation d'un éditeur spécialisé

Afin de faciliter la conception de boîtes de dialogue, un éditeur gratuit a été conçu par David Baldwin : TVDLG. Cet éditeur fonctionne comme un RAD, car il permet de placer des composants sur une boîte de dialogue, où évidemment tout est configurable. Ce programme constitue donc un excellent outil pour créer rapidement une interface en quelques clics, le code correspondant à la boîte de dialogue étant créé à partir du résultat à l'écran. Il suffit donc au programmeur d'implémenter les événements et les opérations de traitements ! Ce programme datant de 1994, il est à ce jour introuvable sur internet. Voici donc un lien pour le télécharger :

Télécharger TVDLG

III-E. Exemple de programme

Vous avez à présent tous les outils nécessaires pour développer une application avec Turbo Vision. Nous allons récapituler toutes les notions abordées avec un exemple de programme : notre but ici est de rassembler les parties de code que nous avons étudiées lors de ce tutoriel.

III-E-1. Définition de nos propres types

Commençons par la définition de nos objets propres. Nous allons utiliser une boîte de dialogue personnalisée, ce qui nous oblige à créer un héritage à partir de l'objet TDialog, et nous allons définir les propriétés de notre application, ce qui nous conduit à définir par héritage notre propre objet descendant de TApplication :

 
Sélectionnez
type 
  PMonDialog = ^TMonDialog;
  TMonDialog = object(TDialog)
  private
    Texte: PStaticText;
    OK: PButton;
  public
    constructor Init;
    destructor Done; virtual;
  end;

type
  TMonApplication = object(TApplication)
  private
    MaFenetre: PWindow;
    MonDialog: PMonDialog;
  public
    constructor Init;
    destructor Done; virtual;
    procedure InitStatusLine; virtual;
    procedure InitMenuBar; virtual;
    procedure HandleEvent(var event: TEvent); virtual;
  end;

III-E-2. Surcharge des méthodes impliquées

Nous allons à présent définir les méthodes des objets que nous avons créés. Il suffira de redéfinir les méthodes que nous avons surchargées : pour la boîte de dialogue, il s'agit du constructeur et du destructeur. Dans le constructeur de l'objet TMonDialog, nous initialiserons donc les attributs privés texte et OK que nous avons prévus pour la personnalisation. L'attribut texte est de type TStaticText, c'est-à-dire un texte non modifiable et non sélectionnable. Le bouton OK servira à fermer la boîte de dialogue.

 
Sélectionnez
constructor TMonDialog.Init;
var
  Bounds: TRect;
begin
  Bounds.Assign(20, 2, 71, 10);
  if not inherited Init(Bounds, 'Test Dialogue') then Fail;
  Bounds.Assign(3, 2, 48, 3);
  Texte := New(PStaticText, Init(Bounds, 'Créé par Wormful_sickfoot pour developpez.com'));
  Insert(Texte);
  Bounds.Assign(20, 5, 30, 7);
  OK := New(PButton, Init(Bounds, '~O~K', cmCancel, bfDefault + bfGrabFocus));
  Insert(OK);
end;

destructor TMonDialog.Done;
begin
  Texte^.Done;
  OK^.Done;
  inherited Done;
end;

Pour finir nous allons définir les méthodes de notre objet TMonApplication. Comme pour l'objet TMonDialog, le constructeur a été redéfini pour satisfaire nos besoins :

 
Sélectionnez
constructor TMonApplication.Init;
var
  Bounds: TRect;
begin
  if not inherited Init then Fail;
  SetScreenMode(smCO80);
  Redraw;
  Bounds.Assign(1, 1, 70, 20);
  MaFenetre := New(PWindow, Init(Bounds, 'Test', wnNoNumber));
  InsertWindow(maFenetre);
end;

procedure TMonApplication.InitStatusLine;
var
  Bounds: TRect;
begin
  Bounds.Assign(0, 24, 80, 25);
  StatusLine := New(PStatusLine, Init(Bounds,
                    NewStatusDef(0, 0,
                     NewStatusKey('~Alt+X~ Quitter', kbAltX, cmQuit,
                    NewStatusKey('Test', 0, 0, nil)), nil)));
end;

procedure TMonApplication.InitMenuBar;
var
  Bounds: TRect;
begin
  Bounds.Assign(0, 0, 80, 1);
  MenuBar := New(PMenuBar, Init(Bounds,
                 NewMenu(NewSubMenu('~F~ichier', 0,
                 NewMenu(
                 NewItem('~Q~uitter', 'Alt+X', kbAltX, cmQuit, 0, nil)),
                 NewItem('~A~ Propos', 'Alt+A', kbAltA, cmAPropos, 0, nil)))));
end;

procedure TMonApplication.HandleEvent(var Event: TEvent);
begin
  inherited HandleEvent(Event);
  if ((Event.What <> evNothing) and
      (Event.What = evCommand) and
      (Event.Command = cmAPropos)) then
  begin
    MonDialog := New(PMonDialog, Init);
    ExecuteDialog(monDialog, nil);
  end;
  ClearEvent(Event);
end;

destructor TMonApplication.Done;
begin
  MaFenetre^.Done;
  inherited Done;
end;

Observons le constructeur de notre objet TMonApplication. Après l'appel au constructeur de la classe parent TApplication, on trouve deux lignes d'initialisation, pour l'écran en l'occurrence : la méthode SetScreenMode permet de changer la résolution de l'écran (on utilise ici la mnémonique smCO80, afin d'utiliser l'écran en couleur avec la résolution 80*25. Pour basculer en 80*50, il faut rajouter la mnémonique smFont8x8). La méthode Redraw permet de redessiner les composants à l'écran, ce qui est indispensable après le changement du mode graphique. Après cette manipulation, on initialise l'attribut privé maFenetre en créant la fenêtre en mémoire, et nous l'affichons ensuite avec la méthode InsertWindow.

III-E-3. Structuration du programme

Il reste, à présent, à définir les variables et la boucle principale du programme, sans oublier la clause « Uses » :

 
Sélectionnez
uses
  App, Objects, Views, Drivers, Dialogs, Menus;

const
  cmAPropos = 101;

var
  MonApplication: TMonApplication;

begin
  MonApplication.Init;
  MonApplication.Run;
  MonApplication.Done;
end.

Télécharger l'exemple complet

La constante cmAPropos permet de définir un événement spécial : il correspond à l'appui du bouton « À Propos » dans la barre de menus. Cet événement est géré par la suite dans la méthode HandleEvent, afin d'afficher la boîte de dialogue correspondante. La variable monApplication de type TMonApplication permet d'utiliser notre objet : la boucle principale de notre programme permet ensuite d'initialiser l'application. La méthode Run lance la boucle de capture d'événement : on peut considérer que cette méthode est en réalité une boucle semi-infinie, qui ne se termine qu'avec le déclenchement de l'événement de sortie (correspondant à la commande cmQuit). À la sortie de cette boucle, nous trouvons le destructeur de l'application, qui permet de libérer la mémoire associée à l'instance monApplication et de quitter notre application proprement. Voici le résultat obtenu à l'écran :

Image non disponible

IV. Conclusion

Vous avez à présent en main toutes les cartes pour développer une application avec l'environnement Turbo Vision.

Je tiens à remercier tout spécialement HDD34 pour son aide très précieuse, ses commentaires toujours constructifs et pour le temps qu'il m'a accordé pour la finition de cet article. Je tiens également à remercier les personnes qui ont contribué à l'élaboration de cet article lors des successives relectures, en particulier Alacazam et mademoiselle Gaëlle Paillot.

Tout commentaire, suggestion, idée ou critique concernant cet article est le bienvenu, vous pouvez me contacter par mail ou message privé.