Fiche secondaire
Il est dans les bonnes pratiques de développement sous Delphi de ne pas instancier toutes les fiches de l'application au démarrage (ce que propose cependant Delphi par défaut). En effet, l'application se contentera généralement d'une fiche principale et peut-être d'un DataModule, mais toutes les autres fiches sont instanciées dynamiquement.
Dans cet article nous allons étudier plusieurs méthodes pour permettre l'accès depuis une fiche secondaire à la fiche principale, ou plus généralement à tout autre fiche de l'application.
La méthode directe
Pour rappel, par défaut, les fiches nouvellement créées sont instanciées automatiquement au lancement de l'application car Delphi insère une ligne
Application.CreateForm(TForm2, Form2);
dans le fichier DPR. Cela permet d'avoir accès à cette fiche via la variable globale Form2.Si je veux faire référence à la fiche principale (Form1 pour l'exemple), il suffit d'ajouter une référence à Unit1 dans la partie Implementation de Unit2 et d'y faire référence.
// Unit2.pas implementation uses Unit1; procedure TForm2.Button1Click(Sender: TObject); begin Form1.Edit1.Text := Edit1.Text; // copie de la zone Edit1 de Form2 dans la zone Edit1 de Form1 end;
NB: quand je fais référence à Edit1 depuis la fiche Form2, je ne préfixe pas son nom de
Form2
. Ce serait une erreur de le faire car Form2
est une variable qui pointe sur une instance particulière de TForm2, si je crée deux instances de cette fiche, la variable ne pourra au mieux en désigner qu'une des deux. Si je veux absoluement utiliser un préfixe, il faut utiliser tout simplement Self
(moi-même).La méthode dynamique
La méthode précédente fonctionne également si la fiche Form2 est crée dynamiquement.
// Unit1.pas procedure TForm1.Button1Click(Sender: TObject); begin TForm2.Create(Self); end;
Notez qu'en créant une instance de TForm2 de la sorte, il n'existe aucun moyen d'y accéder directement (cela reste possible via Screen.Forms)...et si cela avait été le cas pour
TForm1
? n'est-il finalement pas un peu risqué de faire référence à Form1.Edit1
?Si
Form1
est la fiche principale de votre application et que c'est un singleton (une seule instance dans l'application), non...mais de façon générale, ce n'est pas forcément une bonne pratique.Alors comment faire ?
Les helpers à la rescousse
Depuis quelques versions maintenant, Delphi propose des
class helper
qui permettent d'enrichir une classe existante de nouvelles méthodes. Nous allons exploiter cette particularité et le fait que le constructor de TForm2 reçoive en paramètre le "Self" de TForm1 pour retrouver son instance de façon détournée.// Unit2.pas implementation uses Unit1; type TForm2Helper = class helper for TForm2 function Form1: TForm1; end; function TForm2Helper.Form1: TForm1; begin Result := Owner as TForm1; end; procedure TForm2.Button1Click(Sender: TObject); begin Form1.Edit1.Text := Edit1.Text; // copie de la zone Edit1 de Form2 dans la zone Edit1 de Form1 end;
Le code semble ici similaire à ce que j'ai donné plus haut, mais en fait, en ajoutant
TForm2Helper
, Form1
dans Button1Click
n'est plus une référence directe à la variable de Unit1
mais un appel à la méthode TForm2Helper.Form1
. Evidemment si le propriétaire de l'instance de TForm2
n'est pas un TForm1
, le code déclenchera une exception...mais cela peut très bien être un prérequis dont on s'assurera par une assertion.function TForm2Helper.Form1: TForm1; begin Assert(Owner is TForm1); Result := TForm1(Owner); end;
Découplage via des Interfaces
Tout cela fonctionne fort bien, mais que se passe-t-il si Form2 peut être invoquée depuis différentes formes parentes ? Sauf à leur donner un ancêtre commun cela devient compliqué. De plus, il pourrait être intéressant de pouvoir utiliser notre fiche Form2 dans différents projets (une fenêtre de saisie de mot de passe par exemple).
Pour répondre à tout cela, les Interfaces sont une solution intéressante.
// Unit2.pas unit Unit2; type IForm2Interface = interface procedure SetForm2Text(const Str: string); end; TForm2 = class(TForm) Edit1: TEdit; Button1: TButton; procedure Button1Click(Sender: TObject) private FIntf: IForm2Interface; constructor Create(AOwner: TComponent; AIntf: IForm2Interface); end; implementation {$R *.DFM} constructor TForm2.Create(AOwner: TComponent; AIntf: IForm2Interface); begin inherited Create(AOwner); FIntf := AIntf; end; procedure TForm2.Button1Click(Sender: TObject); begin FIntf.SetForm2Text(Edit1.Text); end;
Notez, qu'ici Form2 ignore tout de la fiche Form1 ! Il ne reste plus qu'à l'utiliser dans Form1
// Unit1.pas unit Unit1; interface uses Unit2; type TForm1 = class(TForm, IForm2Interface) Edit1: TEdit; Button1: TButton; procedure Button1Click(Sender: TObject); private // IForm2Interface procedure SetForm2Text(const Str: string); end; implementation {$R *.dfm} procedure Tform1.Button1Click(Sender: TObject); begin TForm2.Create(Self, Self); end; procedure TForm1.SetForm2TExt(const Str: string); begin Edit1.Text := Str; end;
Et voilà, le tour est joué !
Il est également possible de passer par un
Owner as IForm2Interface
, (ou même via supports), mais dans ce cas, il ne faudra pas oublier d'attribuer un GUID à l'interface (Ctrl+Shift+G).
Date de dernière modification : 19/06/2017