Créer une fenêtre « Assistant » (ou « Wizard »)

Bloon

Cet article propose une solution d'implémentation des fenêtres de type Assistant ♪

- Titre : Créer une fenêtre « Assistant » (ou « Wizard »)

- Auteur : Bloon

- Parution : 14 février 2005

- Licence : Copyright ® 2005 Nicolas Bruyère. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Présentation

- II. TFrame, cette classe mystérieuse

- III. La frame TFrameBaseAssistant

- IV. Utilisation de la fenêtre

- IV-A. Afficher l'assistant

- IV-B. Créer une application Assistant

- V. Partager des données entre les frames

- V-A. Accès aux autres frames

- V-B. Accès aux données de l'assistant

- VI. Téléchargement

I - Présentation

Un assistant est une fenêtre composée de plusieurs écrans qui s'enchainent en cliquant sur des boutons « Précédent » et « Suivant ». Généralement, il y a également un bouton « Terminer » qui amène directement au dernier écran et valide la fenêtre.

Il n'est pas très facile de faire une fenêtre composée de plusieurs fenêtres, à part avec un TPageControl (onglets), mais ça ne répond pas tout à fait au besoin : dans un assistant, on souhaite contrôler l'enchainement des écrans, éventuellement en sauter certains (en fonction des choix de l'utilisateur) et valider chaque écran avant de passer au suivant. Tout ceci n'est pas possible avec un TPageControl, à moins de bricoler avec ses événements et ceux de ses TTabSheet.

La solution proposée ici est basée sur une fenêtre (TDlgAssistant) qui manipule une liste de frames. La fenêtre comporte les boutons de navigation et chaque frame représente un écran de l'assistant. Les frames devront répondre à un certain nombre de contraintes (validation de la saisie, présence ou non des boutons « précédent » et « terminer », frame suivante à afficher…), nous allons donc créer une frame qui servira de classe de base à toutes les frames de l'écran et qu'on appellera TFrameBaseAssistant.

Ensuite, pour chaque fenêtre assistant que nous devrons créer, il suffira simplement de créer les frames basées sur TFrameBaseAssistant et d'en passer la liste à TDlgAssistant.

II - TFrame, cette classe mystérieuse

Une frame (ou un cadre si votre Delphi parle français) est un conteneur de composants, c'est-à-dire qu'elle peut contenir d'autres composants, y compris d'autres frames. La frame ressemble à la form (fiche ou fenêtre) dans sa façon de gérer et détenir les composants, en revanche elle n'a pas le comportement d'une fenêtre : elle n'a pas de bordure, on ne peut pas l'afficher par une méthode « Show » et elle n'a pas de menu. C'est juste un « intérieur de fenêtre ».

L'utilisation des frames permet d'obtenir une interface plus souple, car les frames peuvent être mises n'importe où, dans des fenêtres évidemment, mais aussi dans des onglets ou bien dans d'autres frames. Par exemple, on peut passer très facilement d'un affichage par onglets à un affichage en fenêtres MDI en changeant simplement la propriété Parent de la frame. Ça facilite la séparation entre la logique des écrans et la logique d'enchainement des écrans.

Lorsque l'on conçoit une application, on a tendance à créer tous les écrans dans des TForm. Idéalement, il faudrait créer uniquement des frames et n'utiliser les forms que comme un moyen de visualiser les frames.

Par exemple, si votre application contient un écran avec un DBGrid et des boutons « ajouter », « modifier », « supprimer » (écran très classique), le fait de concevoir cet écran dans une frame vous permet de l'utiliser :

- simplement dans une fenêtre, afin de visualiser une table ;

- dans la « fiche détail » d'un enregistrement, afin de visualiser une relation maître/détail

Pour terminer sur les frames, voici un exemple de code qui affiche une frame dans une fenêtre.

Affichage d'une frame dans une fenêtre

interface
TFrame2 = class(TFrame)
Label1: TLabel;
MainMenu1: TMainMenu;
Fichier1: TMenuItem;
Fermer1: TMenuItem;
public
procedure AfficheToi;
end;

implementation

procedure TFrame2.AfficheToi;
var
f : TForm;
begin
// On crée une fenêtre vide dans laquelle on va mettre la frame (self) le temps de l'affichage
f := TForm.Create(nil);
with f do
try
caption := self.Caption;
Menu := MainMenu1;
clientWidth := self.Width;
clientHeight := self.Height;
self.Top := 0;
self.Height := 0;
self.Parent := f;
self.Align := alClient;
Position := poScreenCenter;
showModal;
self.Parent := nil;
finally
f.Free;
end;
end;

Pour aller plus loin avec les frames, je vous invite à regarder le code fourni avec cet article et à utiliser la touche F1 sur TFrame dans Delphi, l'aide est très fournie sur ce sujet. Notamment, comment enregistrer une frame dans la palette de composants et l'ajouter au référentiel d'objets.

L'exemple fourni avec mon article Exécuter une requête dans un thread est également à base de frame, mais avec un TPageControl.

III - La frame TFrameBaseAssistant

La création d'un assistant consiste à créer les frames qui le composent en les faisant hériter de TFrameBaseAssistant. En fonction des besoins, il faudra redéfinir certaines méthodes de cette classe.

Méthode

Description

beforeShow

Cette méthode est appelée juste avant que la frame ne soit affichée. On redéfinira la méthode afin d'initialiser certaines zones de l'écran. Attention : si au cours de la navigation on passe plusieurs fois sur la frame, beforeShow sera appelée plusieurs fois (la propriété showCount permet de savoir combien de fois la frame a été affichée).

beforeNext

Cette méthode est appelée avant de passer à la frame suivante. Si la frame suivante n'est pas celle qui suit immédiatement dans la liste des frames, il est possible de l'indiquer en modifiant un des paramètres nextFrameName ou nextFrameClass.

validationFiche

Cette méthode est appelée par beforeNext afin de valider la frame, avant de passer à la frame suivante. Pour ne pas valider la frame, il faut déclencher une exception EValidationFrameException.

beforePrec

Cette méthode est appelée avant de revenir sur la frame précédente. Par défaut, elle ne fait rien (il n'y a pas besoin de valider la frame si on revient sur la frame précédente).

OnCancel

Cette méthode est appelée si l'utilisateur clique sur « Annuler ». Par défaut, elle ne fait rien.

Les deux méthodes importantes sont validationFiche et beforeNext, et il n'est pas forcément facile de savoir où mettre quoi.

- Dans validationFiche :

- on vérifie que les zones ont été correctement saisies

- si la frame ne peut être validée, on déclenche une EValidationFrameException, par exemple : raise EValidationFrameException.Create('La zone nom est obligatoire',editNom), (le focus sera donné au contrôle editNom) ;

- et c'est tout !

- Dans beforeNext :

- si l'assistant sert à modifier un objet, on transfère les zones de l'écran dans les zones de l'objet ;

- si la frame suivante n'est pas celle qui suit immédiatement dans la liste, on l'indique à ce niveau :

- si on connait le nom de la frame suivante, on renseigne le paramètre nextFrameName,

- si on connait la classe de la frame suivante, on renseigne le paramètre nextFrameClass.

IV - Utilisation de la fenêtre

IV-A - Afficher l'assistant

Sauf besoin très particulier, il n'est pas utile de redéfinir TDlgAssistant, on peut utiliser le code fourni directement.

Voici un exemple de code issu de l'exemple qui accompagne cet article : la fonction affiche un assistant qui permet de saisir un objet de type TMonFichier et renvoie le résultat de l'assistant (OK ou Cancel).

Affichage d'une fenêtre Assistant

function TFormMain.assistant(f: TMonFichier): integer;
var
tmp : TMonFichier;
begin
tmp := TMonFichier.Create;
try
{ 01 }     with TDlgAssistant.Create(nil) do
try
{ 02 }       Caption := 'Assistant fichier';
{ 03 }       tmp.copyFrom(f);
{ 04 }       ajouterObjet('fichier', tmp, false);
{ 05 }       ajouterFrame(TFrameWizFichierNomType.Create(nil));
{ 06 }       ajouterFrame(TFrameWizFichierContenu.Create(nil));
{ 07 }       ajouterFrame(TFrameWizFichierOptionTxt.Create(nil,[aoComeback, aoIsLast, aoGotoEnd]),'optionstxt');
{ 08 }       ajouterFrame(TFrameWizFichierOptionHTML.Create(nil),'optionshtml');
{ 09 }       EndFrame := TFrameWizFichierFin.Create(nil);
{ 10 }       result := MyShowModal;
{ 11 }       if (result = mrOK) then
{ 12 }         f.copyFrom(tmp);
finally
{ 13 }       Free;
end;
finally
{ 14 }     tmp.Free;
end;
end;

Ligne

Explication

01

Création de la boîte de dialogue Assistant.

02

Titre de la fenêtre assistant.

03

Initialisation de l'objet temporaire avec les données de l'objet à modifier. On utilise un objet temporaire au cas où l'utilisateur annulerait l'assistant afin que l'objet à éditer ne soit pas impacté par les éventuelles modifications faites avant l'annulation.

04

Ajout de l'objet dans les objets de l'assistant, les frames pourront ainsi y accéder.

05, 06

Ajout des deux premières frames.

07

Ajout d'une frame avec options : par défaut, on a seulement l'option aoComeback.

- aoComeback : indique qu'on peut revenir sur cet écran via le bouton « Précédent ».

- aoGotoend : indique que le bouton « Terminer » est actif sur cet écran.

- aoIsLast : indique que la frame est la dernière, même si elle n'est pas la dernière de la liste.

08

Ajout d'une frame nommée optionshtml. Pour aller à cette frame depuis une autre, il suffit de donner la valeur optionshtml à nextFrameName dans beforeNext

09

Frame qui sera affichée à la fin, si l'utilisateur clique sur « Terminer ».

10

Affichage de l'assistant et récupération du résultat (mrOK ou mrCancel).

11

Si l'utilisateur a validé l'assistant (en cliquant sur « Terminer »)…

12

On copie l'objet temporaire (modifié par l'assistant) dans l'objet passé en paramètre.

13

Libération de l'assistant.

14

Libération de l'objet temporaire.

Les frames sont créées avec le owner à Nil, car l'assistant les détruit automatiquement.

IV-B - Créer une application Assistant

Il n'est pas possible d'avoir TDlgAssistant comme fenêtre principale de l'application, car elle n'a pas été prévue pour ça. Il faut donc avoir une fenêtre principale invisible dont le rôle va se limiter à afficher l'assistant.

Dans le OnCreate de la fenêtre principale, on la cache :

procedure TFrmMain.FormCreate(Sender: TObject);
begin
BorderStyle := bsNone;
ClientWidth := 0;
ClientHeight := 0;
end;

dans le OnShow, on affiche l'assistant, puis on sort :

procedure TFrmMain.FormShow(Sender: TObject);
begin
// Affichage de l'assistant
with TDlgAssistant.Create(nil) do
try
Caption := 'Le titre...';
ajouterFrame(...);
ajouterFrame(...);
ajouterFrame(...);
MyShowModal;
finally
Free;
end;
// Sortie de l'application
Close;
end;

V - Partager des données entre les frames

V-A - Accès aux autres frames

Les frames d'un assistant peuvent accéder aux autres frames de l'assistant via leur attribut listeFrames, de type TListeFramesAssistant (qui hérite de TObjectList) :

- pour retrouver une frame par son nom, il faut utiliser FindFrame(nom de la frame) ;

- pour retrouver une frame par sa classe, il faut utiliser FindInstanceOf. Voir l'aide de Delphi pour cette méthode.

V-B - Accès aux données de l'assistant

TDlgAssistant dispose d'une liste d'objets à laquelle les frames peuvent accéder via leur attribut listeObjets, de type TListeObjetParNom. Il suffit d'utiliser la méthode getObjet(nom) afin de retrouver l'objet. Le remplissage de la liste des objets de la fenêtre se fait au moment de la création de celle-ci, comme dans l'exemple du paragraphe précédent (ligne 04).

Les plus observateurs d'entre vous auront remarqué que les frames et les objets se trouvent dans TdlgAssistant, mais qu'on y accède directement via une propriété de la frame. C'est simplement que lorsqu'on ajoute une frame à TDlgAssistant, on lui indique en même temps où se trouvent les listes des frames et des objets (voir le code de la méthode setFrameProps dans DgAssistant).

Dans l'exemple téléchargeable ci-dessous, les frames de l'assistant héritent toutes d'une frame commune, héritant elle-même de TFrameBaseAssistant. Cette frame commune ajoute simplement une propriété permettant d'accéder à l'objet stocké dans les objets de l'assistant. Ainsi, plutôt que de faire des getObjet('fichier') dans les frames, on accède à la propriété objetFichier, ce qui rend le code plus lisible.

VI - Téléchargement

Télécharger l'exemple complet réalisé avec Delphi 6 (17 Ko).
Cet exemple montre un assistant permettant de créer et modifier des objets TMonFichier. L'exemple illustre l'utilisation des différentes méthodes et propriétés de TFrameBaseAssistant ainsi que la navigation conditionnelle (la frame option est affichée en fonction du choix de l'utilisateur). Les classes de l'assistant se trouvent dans le sous-répertoire fichier. Les classes réutilisables se trouvent dans Assistant. Le fichier Util.pas est également à récupérer.