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être distinctes. Vous trouverez ce projet dans le répertoire Exemple-ProgObjet1/MultiFenetrage.

Multi-Fenetrage-Presentation.jpg, 49kB

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 menu, une liste de fichiers et deux boutons radios :

Multi-Fenetrage-Presentation-Fenetre-Principale.jpg, 26kB

Les deux entrées du menu Voir permerttent de faire apparaitre les fenêtres (elles sont invisibles au démarrage de l'application). La liste de fichier permet de sélectioner une image à affoicher. Les boutons radio quand à eux définissent dans quelle fenêtre sera afficher 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:

Multi-Fenetrage-Etape0.jpg, 74kB

Par défaut, la classe représentant cette fenêtre est TForm1, sous classe de 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 apparaitre une nouvelle fenêtre nommée Form2:

Multi-Fenetrage-Etape1.jpg, 63kB

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.

La 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 ... etc, 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 boite de dialogue vous demandant d'enregistrer Unit1.pas:

RenommageUnite.jpg, 62kB

Il suffit alors de remplacer Unit1.pas par UnitePrincipale.pas:

RenommageUnite2.jpg, 62kB

et l'unité Unit1 sera alors automatiquement renommée UnitePrincipale dans l'éditeur de source:

RenommageUnite3.jpg, 15kB
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 entraine automatiquement le renommage des classes associées. Dans notre exemple, Form1 a été renommé Frm_Principal ce qui a automatiquement entrainé 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 , menus, les liste de fichiers, les boutons radios et les composants image.

Précisons que toutes les 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 des 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 apparaitre 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;