Gestion de fenêtres



Dans Swing, les fenêtres sont représentées par la classe JFrame. Lorsque vous ajoutez une fenêtre à votre projet, NetBeans génère automatiquement la déclaration d'une sous-classe de JFrame dans un fichier source java.

Nous avons déjà parlé de cette classe dans le premier cours de programmation objet: il s'agit de la classe fenêtre qui représente votre fenêtre spécifique. En tant que sous-classe de JFrame, elle en hérite tous les attributs et méthodes.

Dans cette partie du cours nous verrons comment utiliser certaines de ces méthodes pour gérer des problèmes pratiques comme la fermeture de fenêtre, l'affichage d'une image dans une fenêtre ou la réalisation d'un projet à fenêtres multiples.

Les exemples présentés ici font des accès fichiers et utilisent des fonctions de la librairie JavaETBib. Il est donc préférable de lire au paravant les sections du cours intitulées "Compléments sur les fichiers" et "Compléments sur JavaETBib".



Quelques méthodes de la classe JFrame

Les méthodes suivantes seront utilisées dans cette partie du cours ou dans les exercices:

Fermeture de fenêtre

La fermeture d'une fenêtre peut provoquer la perte de données. C'est en particulier le cas dans les applications mettant à jour des fichiers: si l'utilisateur quitte le programme avant d'avoir sauvegardé les dernières mises à jour, tout son travail sera perdu !

Pour réduire le risque que cela arrive par mégarde, les logiciels demandent généralement à l'utilisateur de confirmer sa décision de quitter l'application via une boite de dialogue.

Nous allons voir ici comment mettre cette protection en place avec Swing et NetBeans.


Modification des propriétés d'une fenêtre

Pour modifier les propriétés d'une fenêtre en mode Design, sélectionnez JFrame dans la fenêtre Navigator:

SelectionJFrame.jpg, 11kB

Vous pouvez ensuite modifier les propriétés de la fenêtre dans la fenêtre Properties de NetBeans:

PropertiesJFrame.jpg, 25kB

La propriété defaultCloseOperation

La propriété defaultCloseOperation d'une fenêtre défini son comportement lorsque l'utilisateur tente de la fermer. Par défaut, sa valeur est EXIT_ON_CLOSE. Avec cette valeur, la fermeture de la fenêtre provoque l'arrêt de l'application.

Pour éviter que la fenêtre se ferme, sélectionnez la valeur DO_NOTHING. Mais dans ce cas, il deviendra impossible de quitter l'application ! A moins de définir un gestionnaire d'évènement comme nous allons le voir au paragraphe suivant.


L'évènement windowClosing

Lorsque l'utilisateur clique sur la croix de fermeture d'une fenêtre, un évènement windowClosing est généré. Vous pouvez générer un gestionnaire pour cet évènement dans le code source de la fenêtre, via la propriété windowClosing dans l'onglet Events de la fenêtre Properties:

formWindowClosing.jpg, 35kB

En sélectionnant formWindowClosing, vous générez un gestionnaire d'évènement du même nom dans le code source:

private void formWindowClosing(...) {                                   
   
}    

C'est donc à l'intérieur de ce gestionnaire d'évènement que devront figurer les instructions demandant à l'utilisateur de confirmer la fermeture de la fenêtre et le cas échéant, de la fermer.


Exemple

Vous trouverez un exemple de gestion de fermeture de fenêtre dans le projet Exemple-Swing/BarreOutils (éditeur de texte avec barre d'outils). Voici le code du gestionnaire de fermeture:

 private void formWindowClosing(....) {                                   
   if (es.Confirmation("Confirmation de fermeture",
      "Voulez vous vraiment quitter l'application?", false))
      dispose(); 
} 

Nous utilisons ici la fonction Confirmation de la librairie JavaETBib pour afficher une boite de dialogue. Si l'utilisateur confirme son choix, la méthode dispose est exécutée. Cette méthode de la classe JFrame, a pour effet de fermer la fenêtre.

Affichage d'image

Dans cette partie du cours nous allons voir comment afficher une image dans une fenêtre à l'aide de la procédure AfficherImage de la librairie JavaETBib, de la méthode paint et d'un nouveau composant: le panneau (classe JPanel).

Un panneau est un simple conteneur: c'est un composant rectangulaire qui ne sert qu'à contenir d'autres composants. On s'en sert souvent pour afficher des images dans une fenêtre.

Panneau-Ds-Palette.jpg, 40kB

Remarque importante: si vous souhaitez que le panneau soit automatiquement redimensionné lorsque la fenêtre est redimensionnée, il suffit de coller les bords du panneau sur les bords de la fenêtre lors du design.


La méthode paint

La méthode paint est une méthode de la classe Component dont JFrame est une sous-classe. Dans la classe JFrame, elle sert à afficher la fenêtre. Cette méthode est exécutée de manière automatique, dès qu'il se produit une modification dans l'apparence graphique d'une fenêtre.

Par exemple, la méthode paint est automatiquement appliquée à une fenêtre lorsque vous la redimensionnez.

Mais il y a des situations plus complexes dans lesquelles paint est exécutée. Supposons qu'une fenêtre A masque en partie ou totalement une fenêtre B. Dans ce cas, la méthode paint sera automatiquement appliquée à la fenêtre A, si en déplacant la fenêtre B, vous modifiez la partie visible de la fenêtre A.

Appel-Paint.jpg, 30kB

Pour afficher une image dans une fenêtre, il faudrait donc qu'elle soit réaffichée chaque fois que la méthode paint est exécutée. L'instruction d'affichage de l'image doit donc obligatoirement être placée dans la méthode paint, ou plus exactement, dans une redéfinition de cette méthode. Tout ceci sera plus clair avec un exemple.


Exemple

L'exemple de projet présenté ici se trouve dans le dossier Exemple-Swing/Image. La fenêtre du projet est composée d'une barre d'outils et d'un panneau nommé PA_Image. La barre d'outils contient un bouton (BT_Ouvrir) permettant de sélectionner un fichier image à afficher. Une fois sélectionnée, l'image s'affiche dans le panneau. Voici par exemple l'affichage de l'image Cacatoes.jpg que vous trouverez dans le dossier Images/Oiseaux :

Visionneur.jpg, 24kB

Voici le code de la procédure évènementielle associée au bouton BT_Ouvrir:

private void BT_OuvrirActionPerformed(...) {                                          
    NomFichier = es.ChoisirFichierAOuvrir(Chemin_Images); 
    es.Rafraichir(PA_Image);
}   

NomFichier et Chemin_Images sont deux attributs de la classe fenêtre intialisés comme suit:

String Chemin_Images = "../../../Images/";
    
String NomFichier = null;  

Chemin_Images contient le chemin relatif du dossier Images par rapport au projet. NomFichier est initialisé à null. Dès qu'une image est sélectionnée, il contient son chemin absolu.

L'appel de la méthode Rafraichir (procédure de la librairie JavaETBib) juste après la sélection de l'image, sert à forcer l'appel de la version redéfinie de la méthode paint, dans laquelle figure l'instruction d'affichage de l'image. Voici le code de cette méthode:

public void paint (Graphics g)
{
  if (NomFichier == null) { super.paint(g); }
  else {
           super.paint(g);
           es.AfficherImage(NomFichier,PA_Image);
       }
       
 } 

Si aucune image n'a été sélectionnée (NomFichier == null), on appelle la méthode paint de la classe mère, c'est à dire celle de JFrame. Cela permet simplement d'afficher la fenêtre sans aucune image à l'intérieur du panneau.

Si une image a été sélectionnée, on affiche tout d'abord le "fond", c'est à dire la fenêtre sans image avec un appel de la méthode paint de JFrame, puis l'image est affichée dans le panneau PA_Image grâce à la procédure AfficherImage de la librairie JavaETBib.

En exécutant ce projet, vous remarquerez que l'image est automatiquement redimensionnée lorsque la fenêtre change de dimension. Cela s'explique d'une part, par le fait que la procédure AfficherImage remplit toujours au maximum le panneau et d'autre part, par le fait que le panneau lui même suit automatiquement les dimensions de la fenêtre (car nous avons ajusté les bords du panneau à ceux de la fenêtre lors du design).

Multi-fenetrage

Cette section concerne la réalisation d'application avec plusieurs fenêtres. Dans une telle application, vous aurez donc plusieurs fichiers sources Java représentant des classes fenêtre.

Prenons le cas de trois fenêtres. Chaque fenêtre aura son propre fichier source Java définissant une sous-classe de JFrame:

Trois-Fenetres.jpg, 20kB

Chacune de ces classes a sa propre méthode main, qui sert comme nous l'avons déjà vu de point d'entrée dans une application. Dans ce cas, comment la JVM va t-elle choisir son point d'entrée ? C'est ici qu'intervient la notion de classe principale.


La classe principale

Dans un projet Java, il doit toujours y avoir une classe définie comme étant la classe principale (ou main class en anglais) du projet. C'est la classe qui sera "exécutée en premier". Cette classe doit forcément posséder une méthode main.

Sous NetBeans, la définition de la classe principale peut se faire à différents moments de la conception du projet:

  1. Lorsque vous créez une nouvelle application, une classe principale est crée par défaut. Sauf si vous décochez la case Create Main Class (voir notice d'utilisation de NetBeans). Dans ce cas, il vous faudra définir la classe principale ultérieurement.
  2. Si aucune classe n'a été définie comme étant la classe principale, lorsque vous tentez d'exécuter une classe du projet, NetBeans vous demandera si vous souhaitez que cette classe soit la classe principale.
  3. Vous pouvez également redéfinir à n'importe quel moment la classe principale du projet:

    • faites un clic droit sur le nom du projet dans la fenêtre Projects et sélectionnez Properties. Dans la fenêtre qui apparait, cliquez sur run. La fenêtre suivante apparait alors avec le nom de la classe principale (Fenetre2 dans la copie d'écran suivante) :

      ProjectPropertiesRun.jpg, 70kB
    • en cliquant sur Browse, vous pouvez sélectionner une autre classe principale:

      BrowseMainClass.jpg, 63kB

Exemple

Nous allons voir ici à travers un exemple, comment déclencher des actions sur une fenêtre à partir d'une autre. Cet exemple se trouve dans le dossier Exemple-Swing/Multi-Fenetrage. Il s'agit d'une application permettant d'afficher des images.

Les fenêtres du projet
MultiFenetrage.jpg, 83kB

Le projet comporte trois fenêtres: une fenêtre principale (associée à la classe principale du projet) et deux fenêtres auxilliaires intitulées fenetre 1 et fenetre 2. Les trois fenêtres apparaissent dès le démarrage de l'application.

La fenêtre principale

La fenêtre principale comporte une combo box, deux boutons radio et un menu. Les actions sur ces différents composants permettent de commander l'affichage d'une image dans une des deux fenêtres auxillaires.

La combo box contient tous les noms de fichier du répertoire Images/Oiseaux:

MultiF-ComboBox.jpg, 24kB

La sélection d'une de ces images provoque son affichage dans la fenêtre définie par les deux boutons radio.

Le menu Voir contient deux entrées cochables pour masquer ou afficher une des deux fenêtres:

MultiF-Menu.jpg, 14kB
Les fenêtres auxilliaires

Chaque fenêtre auxillaire contient un panneau: PA_F1 pour la fenêtre 1 et PA_F2 pour la fenêtre 2.

Code des fenêtres auxillaires

Le code des deux fenêtres auxilliaires reprend le principe du visionneur d'image à fenêtre simple (voir l'exemple de projet illustrant l'affichage d'image dans une fenêtre).

L'attribut NomFichier sert à mémoriser le chemin du fichier sélectionné. Nous l'avons rendu publique afin de pouvoir y accéder depuis la fenêtre principale:

   public String NomFichier = null;

La méthode paint est redéfinie, de manière à appeler la procédure AfficherImage lorsqu'une image a été selectionnée. Voici celle de la fenêtre 1:

public void paint (Graphics g)
{
   if (NomFichier == null) { super.paint(g); }
   else {
           super.paint(g);
           es.AfficherImage(NomFichier,PA_F1);
   }
}

Pour pouvoir afficher une image dans une fenêtre auxillaire depuis la fenêtre principale, nous avons besoin de pouvoir accéder à son panneau. Le problème, est qu'il est défini (comme tous les composants d'une classe fenêtre) comme un attribut privé !

Pour contourner ce problème, nous avons introduit, dans chaque fenêtre auxillaire, un accesseur qui retourne le panneau image. Voici celui de la fenêtre 1:

 public JPanel getPanel() {
   return PA_F1;
}
Code de la fenêtre principale

Nous avons définit la classe de la fenêtre principale comme classe principale du projet. Par conséquent, les premières instructions exécutées au démarrage de l'application devront figurer dans le constructeur de cette classe.

Constructeur de la classe principale
public FenetrePrincipale() {
    initComponents();
    setLocation(500,200);
    ConstruireListeImage ();
        
    f1 = new Fenetre1();
    f1.setLocation(200,200);
    f1.setVisible(true);
        
    f2 = new Fenetre2();
    f2.setLocation(900,200);
    f2.setVisible(true);
} 

Les instructions en blanc sont celles que nous avons ajoutées (rappelons que le reste est automatiquement généré par NetBeans, en particulier l'appel de la méthode initComponent).

Dans ces instructions, f1 et f2 représentent deux attributs de la classe principale déclarés comme suit:

    Fenetre1 f1;
    Fenetre2 f2;

Ils nous servent à mémoriser les références aux fenêtres auxillaires lors de leur instanciation:

    f1 = new Fenetre1();
    ...
    f2 = new Fenetre2();

Ces deux attributs nous permettront d'agir sur ces fenêtres par la suite. En particulier, dans le constructeur de la fenêtre principale, nous les utilisons pour les positionner à l'aide de la méthode setLocation, puis pour les afficher en leurs appliquant la méthode setVisible.

La méthode ConstruireListeImage est également appelée dans le constructeur. Elle sert à initialiser la combo box (CB_ListeImage) avec les noms des fichiers contenus dans le répertoire Images/Oiseaux. Voici son code:

public void ConstruireListeImage () {
int i; String []  lst_img = es.ListeFichier (CheminDesImages);
    
  CB_ListeImage.removeAllItems();
     
  for (i=0; i < lst_img.length; i++) {
    CB_ListeImage.addItem(lst_img[i]);
   }
}

Nous utilisons ici la fonction ListeFichier de la librairies JavaETBib.

Sélection d'une image

La sélection d'une image via la combo box CB_ListeImage déclenche la procédure évènementielle suivante:

private void CB_ListeImageActionPerformed(...) {                                              
String fichier;
       
  fichier = CheminDesImages+
     CB_ListeImage.getSelectedItem().toString();
       
  if (BR_Fenetre1.isSelected()) {
    if (f1 != null)
      {
        f1.NomFichier=fichier;
        es.Rafraichir(f1.getPanel());
      }
    }
    else
      if (f2 != null)
       {
         f2.NomFichier= fichier;
         es.Rafraichir(f2.getPanel()); 
       }
}       

La première instruction construit le chemin du fichier à partir de l'élément sélectionné. Ensuite, tout dépend de l'état des boutons radios.

Si le bouton radio BR_Fenetre1 (celui qui est libellé Fenêtre 1) est sélectionné, l'image est affichée dans la fenêtre 1 en déclenchant indirectement un appel de la méthode paint via la procédure Rafraichir de la librairie JavaETBib. Si l'autre bouton radio est sélectionné, elle sera affichée dans la fenetre 2.

Remarquez l'utilisation de l'accesseur getPanel pour récupérer le panneau de la fenêtre auxilliaire.

Le lecteur se demandera peut être pourquoi nous testons si f1 et f2 sont différents de null. En principe, lorsque la fenêtre principale est affichée, les deux fenêtres auxilliaires existent, puisqu'elles ont été créées dans le constructeur de la fenêtre principale. A ce stade, f1 et f2 devrait donc nécessairement contenir les références aux deux fenêtres auxilliaires.

En fait, la procédure évènementielle CB_ListeImageActionPerformed est appelée avant même que les fenêtres auxilliaires soient créées à cause d'un bug de Swing que nous avons déjà signalé (voir méthodes de la classe JComboBox): lorsque l'on ajoute un élément dans une combo box, cela déclenche un évènement ActionPerformed et tout se passe donc comme si l'utilisateur avait cliqué sur cet élément. Donc, lorsque nous initialisons la combo box avec la procédure ConstruireListeImage nous déclenchons une multitudes d'évènement ActionPerformed qui vont déclencher à leur tour l'appel de cette procédure évènementielle, alors que les fenêtres auxillaires n'existent pas encore !

Utilisation du menu voir

Rappelons que le menu Voir contient deux entrées cochables pour masquer ou afficher une des deux fenêtres:

MultiF-Menu.jpg, 14kB

En mémoire, la première entrée est représentée par l'objet EM_VoirFenetre1 et la seconde, par l'objet EM_VoirFenetre2. Lorsque la première entrée est cochée ou décochée, la procédure évènementielle suivante est déclenchée:

private void EM_VoirFenetre1ActionPerformed(...) {                                                
  if (EM_VoirFenetre1.isSelected()) f1.setVisible(true);
  else f1.setVisible(false);
}                                               
 

La méthode isSelected est utilisée pour savoir si l'entrée est cochée ou décochée. Si elle est cochée, la fenêtre 1 est rendue visible en lui appliquant l'appel de méthode setVisible(true). Dans le cas contraire, elle est rendue invisible en lui appliquant l'appel de méthode setVisible(false).

La procédure évènementielle de la seconde entrée suit le même principe.