Gestionnaires de mise en forme

Chapitres traités   

Dans l'introduction de la partie "Programmation graphique", nous avons vu que Swing utilise des gestionnaires de placement (appelé aussi gestionnaire de disposition ou même gestionnaire de mise ne forme) pour disposer des composants à l'intérieur de conteneurs en contrôlant ainsi leur taille et leur position.

Effectivement, pour chaque conteneur (fenêtre, panneau, boîte de dialogue, etc.), Java permet de choisir un gestionnaire de mise en forme responsable de la disposition automatique des composants. Dans mes différents exemple, j'ai déjà eu l'occasion d'employer des gestionnaires de type FlowLayout ou BorderLayout, sans toutefois approfondir toutes leurs propriétés. Nous profitons de cette étude pour examiner en détail les différents gestionnaires de mise en forme proposés par Java.

Choix du chapitre L'ensemble des gestionnaires de mise en forme

Les gestionnaires de placement définissent une stratégie de disposition des composants au lieu de spécifier des positions absolues. Par exemple, vous pouvez définir une interface utilisateur avec plusieurs boutons et zones de texte et supposer raisonnablement qu'ils s'affichent toujours correctement, même si l'utilisateur redimensionne la fenêtre de l'application. Peu importe la plate-forme ou l'apparence de l'interface utilisateur utilisé ; le gestionnaire de placement les conditionnera toujours intelligemment les uns par rapport aux autres. Ainsi en prenant comme exemple le gestionnaire FlowLayout, les composants placés dans le conteneur, seront automatiquement et systématiquement centrés.

Un gestionnaire de placement est un objet contrôlant le placement et le dimensionnement des composants situés à l'intérieur de la zone d'affichage d'un conteneur. Il ressemble au gestionnaire de fenêtres d'un système d'affichage ; il gouverne l'emplacement et la taille des composants. Chaque conteneur possède un gestionnaire de placement par défaut, mais il est possible d'en installer un nouveau en appelant sa méthode setLayout().

Liste des gestionnaires de mise en forme avec quelques exemples de codage

FlowLayout
Ce gestionnaire dispose les composants les uns à la suite des autres, sur une même ligne. Lorsqu'une ligne ne possède plus suffisamment de place, l'affichage se poursuit sur la ligne suivante. Chaque composant conserve sa taille préférée. Par exemple, le gestionnaire de placement par défaut d'un JPanel est un FlowLayout ; il essaie de placer les objets sous leur taille préférée de gauche à droite et de haut en bas :

package gestionnaire;

import javax.swing.*;

public class Fenêtre extends JFrame {
   private JButton nord = new JButton("Nord");
   private JButton ouest = new JButton("Ouest");
   private JButton sud = new JButton("Sud");
   private JButton centre = new JButton("Centre");
   private JButton est = new JButton("Est");
   private JPanel panneau = new JPanel();
    
   public Fenêtre() {
      setTitle("Une fenêtre");
      setSize(300, 250);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      panneau.add(nord);
      panneau.add(ouest);
      panneau.add(sud);
      panneau.add(centre);
      panneau.add(est);
      add(panneau);
      setVisible(true);
   }
   public static void main(String[] args) {
      new Fenêtre();  
   }
}
BorderLayout
Ce gestionnaire dispose les composants suivant les quatre bords du conteneur ou au centre. BorderLayout dispose donc cinq composants maximum dans un conteneur, deux au bords supérieur et inférieur à leur hauteur préférée, deux aux bords gauche et droit à leur largeur préféré, et un au centre qui occupe le reste de l'espace. Par exemple, le gestionnaire de placement par défaut de JFrame est un BorderLayout, qui place des objets à des emplacements particuliers désignés de la fenêtre, comme NORTH, SOUTH et CENTER :

package gestionnaire;
import java.awt.BorderLayout;
import javax.swing.*;

public class Fenêtre extends JFrame {
   private JButton nord = new JButton("Nord");
   private JButton ouest = new JButton("Ouest");
   private JButton sud = new JButton("Sud");
   private JButton centre = new JButton("Centre");
   private JButton est = new JButton("Est");
    
   public Fenêtre() {
      setTitle("Une fenêtre");
      setSize(300, 250);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      add(nord, BorderLayout.NORTH);
      add(ouest, BorderLayout.WEST);
      add(sud, BorderLayout.SOUTH);
      add(centre, BorderLayout.CENTER);
      add(est, BorderLayout.EAST);
      setVisible(true);
   }
   public static void main(String[] args) {
      new Fenêtre();  
   }
}  
GridLayout
Ce gestionnaire permet de disposer les composants suivant une grille régulière, chaque composant ayant alors la même taille. Il est tout à fait possible de changer de gestionnaire de placement par défaut et d'imposer celui qui vous convient. Par exemple, nous pouvons choisir le gestionnaire GridLayout en lieu et place du gestionnaire BorderLayout prévu par défaut pour un cadre de fenêtre :

package gestionnaire;

import java.awt.*;
import javax.swing.*;

public class Fenêtre extends JFrame {
   private JButton nord = new JButton("Nord");
   private JButton ouest = new JButton("Ouest");
   private JButton sud = new JButton("Sud");
   private JButton centre = new JButton("Centre");
   private JButton est = new JButton("Est");
   
   public Fenêtre() {
      setTitle("Une fenêtre");
      setSize(300, 250);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setLayout(new GridLayout(3, 2));
      add(nord);
      add(ouest);
      add(sud);
      add(centre);
      add(est);
      setVisible(true);
   }
   public static void main(String[] args) {
      new Fenêtre();  
   }
}
CardLayout
Ce gestionnaire permet de disposer des composants suivant une pile, à la manière d'un paquet de cartes, un seul composant étant visible à la fois. Ce gestionnaire est pratique pour créer des panneaux contextuels qui apparaissent suivant l'évolution du programme ou suivant les choix de l'utilisateur. Avec ce gestionnaire, lorsque vous placez votre composant dans le conteneur, vous devez identifiez la carte à l'aide d'une chaîne de caractère.

package gestionnaire;

import java.awt.*;
import javax.swing.*;

public class Fenêtre extends JFrame {
   private JButton nord = new JButton("Nord");
   private JButton ouest = new JButton("Ouest");
   private JButton sud = new JButton("Sud");
   private JButton centre = new JButton("Centre");
   private JButton est = new JButton("Est");
   
   public Fenêtre() {
      setTitle("Une fenêtre");
      setSize(300, 250);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setLayout(new CardLayout());
      add(nord, "Nord");
      add(ouest, "Ouest");
      add(sud, "Sud");
      add(centre, "Centre");
      add(est, "Est");
      setVisible(true);
   }
   public static void main(String[] args) {
      new Fenêtre();  
   }
}
  
BoxLayout
Ce gestionnaire permet de disposer des composants suivant une même ligne, avec leur hauteur préférée, ou une même colonne, avec leur hauteur préférée, mais avec plus de souplesse (moins de contrainte) que le gestionnaire GridLayout.

package gestionnaire;

import java.awt.*;
import javax.swing.*;

public class Fenêtre extends JFrame {
   private JButton nord = new JButton("Nord");
   private JButton ouest = new JButton("Ouest");
   private JButton sud = new JButton("Sud");
   private JButton centre = new JButton("Centre");
   private JButton est = new JButton("Est");
   
   public Fenêtre() {
      setTitle("Une fenêtre");
      setSize(300, 250);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
      add(nord);
      add(ouest);
      add(sud);
      add(centre);
      add(est);
      setVisible(true);
   }
   public static void main(String[] args) {
      new Fenêtre();  
   }
}
GridBagLayout
Comme GridLayout, ce gestionnaire permet de disposer les composants suivant une grille, mais ceux-ci peuvent éventuellement occuper plusieurs cellules ; en outre, nous pouvons disposer diverses contraintes indiquant comment la taille des cellules peut être modifié au fil de l'exécution. Avec ce gestionnaire, vous pouvez réellement disposer vos composants comme vous l'entendez, mais c'est aussi le plus délicat à mettre en oeuvre. (ce gestionnaire ne sera pas étudié, parce que trop complexe et trop long à mettre en oeuvre).
SpringLayout
Ce gestionnaire dispose les composants en fonction de leur taille préférée et de contraintes qui spécifient comment ces composants sont rattachés les uns par rapport aux autres.
GroupLayout
Ce gestionnaire permet de définir plusieurs groupes de composants à l'intérieur d'un même conteneur, ce qui évite de placer des panneaux intermédiaires.

 

Choix du chapitre Relation entre un conteneur et son gestionnaire de placement

Un gestionnaire de placement est un objet qui dispose les composants fils d'un conteneur, comme vous l'avez découvert dans les exemples ci-dessus. Il déplace et retaille les composants à l'intérieur de la zone d'affichage, suivant la politique de placement choisie. Le rôle du gestionnaire de disposition est de placer les composants dans la zone disponible, et de maintenir les relations dans le plan entre les différents composants intégrés.

Chaque conteneur possède son propre gestionnaire de placement. Donc, lorsque vous créez un nouveau conteneur, il est créé avec un objet LayoutManager du type approprié. Vous pouvez installer un nouveau gestionnaire de placement à tout moment en utilisant pour cela la méthode setLayout(). Par exemple, il est possible d'initialiser le gestionnaire de placement du panneau de contenu à BorderLayout, comme suit :

setLayout(new BorderLayout());

Remarquez que nous appelons le constructeur BorderLayout, mais nous ne conservons aucune référence sur le gestionnaire de placement. C'est typique : une fois que vous avez installé un gestionnaire de placement, il fonctionne tout seul, en interagissant avec le conteneur. Nous nous servons rarement des méthodes du gestionnaire directement, il est donc inutile de garder une référence.

La seule exception concerne le gestionnaire CardLayout. Il est quand même nécessaire de savoir ce que ce gestionnaire va faire avec les composants lorsque vous l'utilisez.
.

Appel automatique des différentes méthodes qui s'occupe de la gestion d'affichage

Le gestionnaire LayoutManager est consulté chaque fois que la méthode doLayout() d'un conteneur est appelé pour réorganiser le contenu (par exemple, lorsque l'utilisateur provoque un réaffichage ou un changement de taille sur la fenêtre). Ce principe consiste à appeler les méthodes setLocation() et setBounds() sur les composants enfants individuels pour les placer dans la zone d'affichage du conteneur. Cela se produit au premier affichage d'un conteneur, puis à chaque fois que la méthode revalidate() du conteneur est appelée. Effectivement, les conteneurs dérivés de la classe Window (Frame, JFrame et JWindow) sont automatiquement revalidés lorsqu'ils sont assemblés ou retaillés (au démarrage de l'application ou lorsque l'utilisateur change l'apparence de la fenêtre).

Dimensionnement automatique de la fenêtre en correspondance avec ses composants internes

Nous pouvons appeler explicitement le méthode pack() de Window (donc JFrame, JDialog et JWindow) pour démarrer la gestion de disposition de la fenêtre et de régler ainsi sa taille initiale. Cette méthode pack() est importante. Elle permet, en effet, de se passer de la méthode setSize(), où il est toujours difficile de spécifier la bonne dimension de la fenêtre, compte tenu de l'ensemble des composants dont elle dispose. Le fait d'appeler la méthode pack() fixe automatiquement la taille de la fenêtre, en aménageant ses dimensions pour qu'elles correspondent à la taille préférée des composants contenues par la fenêtre.

package conversion;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Conversion extends JFrame implements ActionListener {
   private JTextField saisie = new JTextField("0");
   private JButton conversion = new JButton("Conversion");
   private JLabel résultat = new JLabel("0 Franc");
   private JPanel panneau = new JPanel();
   
   public Conversion() {
      super("€uros -> Francs");
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      saisie.setHorizontalAlignment(JTextField.RIGHT);
      saisie.addActionListener(this);
      saisie.setPreferredSize(new Dimension(125, 25));
      conversion.addActionListener(this);
      panneau.setLayout(new BorderLayout());
      panneau.add(saisie);
      panneau.add(conversion, BorderLayout.EAST);
      add(panneau, BorderLayout.NORTH);
      résultat.setHorizontalAlignment(JLabel.RIGHT);  
      résultat.setBorder(BorderFactory.createEtchedBorder());
      add(résultat, BorderLayout.SOUTH);
      getContentPane().setBackground(Color.GREEN);
      pack();
      setVisible(true);
   }
   
   public static void main(String[] args) {
      new Conversion();  
   }

   public void actionPerformed(ActionEvent e) {
       final double TAUX = 6.55957;
       double €uro = Double.parseDouble(saisie.getText());
       double franc = €uro * TAUX;
       résultat.setText(franc+" Francs");
   }
}

Cette fois-ci, la méthode setSize() n'est pas appelée dans le constructeur. Nous préférons prendre à la place la méthode pack(). Par contre, il est nécessaire de spécifier la méthode setPreferredSize() de saisie afin d'avoir une dimension acceptable pour la zone de saisie.

La taille du conteneur par rapport aux composants internes

Chaque composant possède trois informations importantes qui sont utilisées par le gestionnaire de disposition pour le placer et le mettre à la bonne taille : une taille souhaitée, une taille maximale et une taille minimale. Elles sont accessibles via les méthodes getPreferredSize(), getMaximumSize() et getMinimumSize() de la classe Component.

Par exemple, un objet JButton peut être retaillé suivant n'importe quelle proportion. Toutefois, la conception du bouton peut indiquer une taille souhaitée pour que le bouton ait un aspect agréable. Le gestionnaire de placement peut utiliser cette taille lorqu'il n'y a pas de contraintes ou peut l'ignorer suivant le gestionnaire utilisé. Cela dépend effectivement de la politique de placement choisie.

Si donc le gestionnaire de placement le permet, voici le rôle des différentes méthodes de gestion des tailles :

  1. getMinimumSize() : Maintenant, si nous proposons un libellé au bouton, il peut avoir besoin d'une taille minimale pour s'afficher correctement. Le gestionnaire de placement peut donc accorder plus d'importance à cette donnée pour lui garantir cet espace minimum.
  2. getMaximumSize() : De même, il se peut qu'un composant trop grand ne puisse pas s'afficher correctement (peut-être doit-il adapter la taille d'une image) ; il peut utiliser getMaximumSize() pour indiquer la taille maximum qu'il considère acceptable.
  3. getPreferredSize() : La taille préférée d'un objet Container a la même signification que pour tout autre type de composant. Toutefois, comme un objet Container peut contenir ses propres composants et les disposer un utilisant son propre gestionnaire de placement, cette taille dépend de ce gestionnaire. Celui-ci demande aux composants de son conteneur la taille qu'ils souhaitent avoir (ou la taille minimum) pour les disposer. En se basant sur ces valeurs, il calcule la taille préférée de son propre conteneur (qui peut être communiquée au parent du conteneur).

Calculs nécessaires pour placer les composants

Un gestionnaire de placement appelé pour disposer ses composants travaille dans une zone délimitée. Il commence par regarder les dimensions de son conteneur et les tailles souhaitées ou minimum des composants fils. Il divise alors les zones de l'écran et définit les tailles des composants. Vous pouvez redéfinir les méthodes getMinimumSize(), gatMaximumSize() et getPreferredSize() d'un composant, mais il est conseillé de n'effectuer une telle opération que si vous spécialisez le composant et s'il en a vraiment besoin.

Si vous vous débattez avec un gestionnaire de placement modifiant la taille de l'un de vos composants, c'est que vous utilisez le mauvais type de gestionnaire ou que vous construisez mal votre interface. Rappelez-vous qu'il est possible d'utiliser plusieurs objets JPanel dans un affichage, où chacun possède son propre LayoutManager. Essayez de contourner le problème : placez les composants dans leurs propres JPanel et insérez ceux-ci dans le conteneur.




Lorsque cela devient trop compliqué, vous pouvez utiliser un gestionnaire de placement contraignant comme GridBagLayout, que nous étudierons d'ailleurs ultérieurement.
.

 

Choix du chapitre Le gestionnaire FlowLayout

Le gestionnaire FlowLayout dispose les composants les uns à la suite des autres, sur une même ligne. Lorsqu'une ligne ne possède plus suffisamment de place, l'affichage se poursuit sur la ligne suivante. Un FlowLayout peut éventuellement spécifier une justification LEFT, CENTER ou RIGHT et un espacement entre deux composants verticaux ou horizontaux.

Par défaut, un FlowLayout utilise la justification CENTER, ce qui signifie que tous les composants sont centrés dans la zone qui leur est alloué. Nous l'avons vu, FlowLayout est aussi le gestionnaire par défaut des composants JPanel.

Chaque composant conserve sa taille préférée

Contrairement à bien d'autres gestionnaires de disposition, la taille des composants est donc respectée lorsque nous utilisons un FlowLayout. Celle-ci est définie par défaut suivant la nature et le contenu du composant. Par exemple, la longueur des libellés ainsi que la police utilisée pourront influer sur la taille d'un bouton, d'une étiquette ou d'une boîte de liste.

Nous pouvons toujours imposer une taille à un composant en utilisant la méthode setPreferredSize(). Nous lui fournissons un objet de type Dimension dont le constructeur reçoit en argument deux entiers correspondant à la largeur et à la hauteur voulue.

Pour ce gestionnaire, vous remarquerez que les méthodes setMinimumSize() et setMaximumSize() n'ont aucun intérêt.
.

Alignements des composants

Lors de la construction d'un gestionnaire FlowLayout, nous pouvons spécifier un paramètre d'alignement d'une ligne de composants par rapport aux bords verticaux de la fenêtre. Pour cela, nous utilisons l'une des composantes entières suivantes :

Constante symbolique Valeur Alignement de la ligne de composants
FlowLayout.LEFT "Left" A gauche
FlowLayout.RIGHT "Right" A droite
FlowLayout.CENTER "Center" Au centre (valeur par défaut)

Notez que ce choix est fait une fois pour toute à la construction : toutes les lignes de composants suivront toujours le même alignement :

conteneur.setLayout(new FlowLayout(FlowLayout.RIGHT));

Intervalle entre les composants

Enfin, toujours lors de la construction, nous pouvons également spécifier un intervalle entre les composants (par défaut, il est de 5 pixels, dans les deux directions). Dans ce cas, nous devons aussi spécifier le paramètres d'alignement en premier argument :

conteneur.setLayout(new FlowLayout(FlowLayout.RIGHT, 10, 15));

Les méthodes setHgap() et setVgap() peuvent également être utilisées, après la construction, pour changer les intervalles en cours, respectivement en horizontal et en vertical. Dans ce cas, il est nécessaire de conserver la référence au gestionnaire.

FlowLayout layout = new FlowLayout();
conteneur.setLayout(layout);
layout.setHgap(10);
layout.setVgap(15);

Exemple d'application

package conversion;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Conversion extends JFrame implements ActionListener {
   private JTextField saisie = new JTextField("0");
   private JButton conversion = new JButton("Conversion");
   private JLabel résultat = new JLabel("0 Franc");
   private JPanel panneau = new JPanel();
   
   public Conversion() {
      super("€uros -> Francs");
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      saisie.setHorizontalAlignment(JTextField.RIGHT);
      saisie.addActionListener(this);
      conversion.addActionListener(this);
      panneau.setLayout(new FlowLayout(FlowLayout.RIGHT, 1, 1));
      panneau.setBackground(Color.ORANGE);
      panneau.add(conversion, BorderLayout.WEST);
      panneau.add(résultat);
      add(saisie, BorderLayout.NORTH);
      résultat.setHorizontalAlignment(JLabel.RIGHT);  
      résultat.setBorder(BorderFactory.createEtchedBorder());
      saisie.setPreferredSize(new Dimension(265, 25));
      résultat.setPreferredSize(new Dimension(145, 25));
      conversion.setPreferredSize(new Dimension(100, 24));      
      add(panneau, BorderLayout.SOUTH);
      getContentPane().setBackground(Color.GREEN);
      pack();
      setVisible(true);
   }
   
   public static void main(String[] args) {
      new Conversion();  
   }

   public void actionPerformed(ActionEvent e) {
       final double TAUX = 6.55957;
       double €uro = Double.parseDouble(saisie.getText());
       double franc = €uro * TAUX;
       résultat.setText(franc+" Francs");
   }
}

 

Choix du chapitre Le gestionnaire BorderLayout

Le gestionnaire BorderLayout dispose les composants suivant l'un des quatre bords (positions géographiques : nord - sud - est - ouest) du conteneur ou au centre. Les composants déposés sur l'un des bords ont une épaisseur fixe, et le composant déposé au centre occupe tout l'espace laissé libre. Tant qu'un bord n'est pas occupé par un composant, l'espace correspondant est utilisable par le composant central.

BorderLayout est le gestionnaire par défaut des panneaux de contenu JWindow et JFrame. Chaque composant étant associé à une direction, BorderLayout peut gérer au maximum cinq composants. Il réduit ou étire ces composants pour respecter ses propres contraintes. Effectivement, à la différence du gestionnaire FlowLayout qui préserve la taille des composants, BorderLayout en augmente la taille pour remplir l'espace disponible.

Ainsi, comment la zone d'affichage est-elle exactement divisée ? Les objets placés au nord ou au sud prennent la hauteur qu'ils souhaitent et sont par contre étendus pour remplir complètement la zone d'affichage dans le sens horizontal. Les composants à l'est ou à l'ouest obtiennent la largeur qu'ils veulent et sont alors étirés pour remplir la place restante entre les positions nord et sud. Enfin, l'objet situé au centre prend tout simplement la place restante

Choisir l'emplacement des composants internes

Lorsque nous ajoutons un composant au gestionnaire de placement, nous devons indiquer le composant et sa position. Nous utilisons pour cela une version surchargée de la méthode add() avec un argument supplémentaire. Celui-ci spécifie le nom de la position à l'intérieur de BorderLayout. Vous précisez alors l'une des constantes entières suivantes (nous pouvons utiliser indifféremment le nom de la constante ou sa valeur) :

Constante symbolique Valeur Emplacement correspondant
BorderLayout.NORTH "North" En haut
BorderLayout.SOUTH "South" En bas
BorderLayout.EAST "East" A droite
BorderLayout.WEST "West" A gauche
BorderLayout.CENTER "Center" Au centre (valeur par défaut)

Si aucune valeur de position n'est précisée à la méthode add(), le composant est automatiquement placé au centre.

conteneur.add(panneau, BorderLayout.NORTH);
conteneur.add(éditeur);
conteneur.add(commentaires, BorderLayout.EAST);

Espacement entre les régions

A l'instar des gestionnaires FlowLayout, si vous souhaitez spécifier un intervalle entre les différentes régions, vous pouvez le faire dans le constructeur de la classe BorderLayout. Par défaut, les composants ne sont pas espacés. Nous pouvons donc demander un intervalle particulier au moment de la construction :

conteneur.setLayout(new BorderLayout(10, 15));

Exactement comme le gestionnaire FlowLayout, les méthodes setHgap() et setVgap() peuvent également être utilisées, après la construction, pour changer les intervalles en cours, respectivement en horizontal et en vertical. Dans ce cas, il est nécessaire de conserver la référence au gestionnaire.

BorderLayout layout = new BorderLayout();
conteneur.setLayout(layout);
layout.setHgap(10);
layout.setVgap(15);

Exemple d'application

package conversion;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Conversion extends JFrame implements ActionListener {
   private JTextField saisie = new JTextField("0");
   private JButton conversion = new JButton("Conversion");
   private JLabel résultat = new JLabel("0 Franc");
   
   public Conversion() {
      super("€uros -> Francs");
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      saisie.setHorizontalAlignment(JTextField.RIGHT);
      saisie.addActionListener(this);
      conversion.addActionListener(this);
      résultat.setHorizontalAlignment(JLabel.RIGHT);  
      résultat.setBorder(BorderFactory.createEtchedBorder());
      saisie.setPreferredSize(new Dimension(135, 25));      
      getContentPane().setBackground(Color.GREEN);
      setLayout(new BorderLayout(3, 3));
      add(saisie);
      add(conversion, BorderLayout.EAST);
      add(résultat, BorderLayout.SOUTH);     
      pack();
      setVisible(true);
   }
   
   public static void main(String[] args) {
      new Conversion();  
   }

   public void actionPerformed(ActionEvent e) {
       final double TAUX = 6.55957;
       double €uro = Double.parseDouble(saisie.getText());
       double franc = €uro * TAUX;
       résultat.setText(franc+" Francs");
   }
}

En combinant judicieusement ces deux premiers gestionnaires aussi simples que FlowLayout et BorderLayout, nous pouvons aboutir à des présentations très élaborées. En effet, parmi les composants disposés par un gestionnaire, nous pourrons trouver un ou plusieurs conteneurs (généralement des panneaux) qui pourront posséder leur propre gestionnaire.

 

Choix du chapitre Le gestionnaire GridLayout

Le gestionnaire GridLayout permet de disposer les différents composants suivant une grille régulière, un peu à la manière des lignes et des colonnes d'un tableur, chaque composant individuel occupant alors une cellule. Toutes les lignes et les colonnes de la grille sont de taille identique. Le gestionnaire de placement GridLayout convient donc particulièrement bien aux objets de même dimension.

Les composants sont arbitrairement redimensionnés pour tenir dans la grille : leurs tailles minimales et souhaitées sont donc éventuellement ignorées.
.

Création d'une grille

A la construction, nous spécifions le nombre de lignes et de colonnes désirés avec, éventuellement, des intervalles entre les composants.

conteneur.setLayout(new GridLayout(5, 4)); // 5 lignes, 4 colonnes
conteneur.setLayout(new GridLayout(5, 4, 10, 3)); // 5 lignes, 4 colonnes, espacement horizontal de 10, espacement vertical de 3

Les composants sont affectés aux différentes cases, en fonction de l'ordre dans lequel nous les ajoutons au conteneur (le parcours se fait d'abord suivant les lignes). Il est possible que les dernières cases soient vides. Toutefois, si vous laissez plus d'une ligne vide, le gestionnaire réorganisera la grille, de façon à éviter une perte de place. De la même façon, si vous donnez trop d'objets à gérer, GridLayout ajoute des colonnes supplémentaires pour faire entrer les objets.

Vous pouvez également définir le nombre de colonnes ou de lignes à zéro, ce qui signifie que le nombre d'éléments inséré dans telle ou telle direction par les gestionnaire de placement ne vous importe pas. Par exemple GridLayout(2, 0) demande la mise en forme de deux lignes et un nombre illimité de colonnes : si vous placez dix composants, vous obtenez deux lignes de cinq colonnes.

Exemple d'application

Télécharger l'applicationpackage calendrier;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;

public class Calendrier extends JFrame {
   private static enum JourSemaine {Dim, Lun, Mar, Mer, Jeu, Ven, Sam};
   private Semaine semaine = new Semaine();
   private Mois jourMois = new Mois();
   private Sélection sélection = new Sélection();
   
   public Calendrier() {
       super("Calendrier");
       add(semaine, BorderLayout.NORTH);
       add(jourMois);
       add(sélection, BorderLayout.SOUTH);
       pack();
       setDefaultCloseOperation(EXIT_ON_CLOSE);
       setResizable(false);
       setVisible(true);
   }
            
   public static void main(String[] args) {
        new Calendrier();
   }
   
   private class Semaine extends JPanel {      
      Semaine() {
         setLayout(new GridLayout(1, 0));
         setBackground(Color.PINK);         
         for (JourSemaine jour : JourSemaine.values())
            add(new Jour(jour));      
      }
   }
   
   private class Jour extends JLabel {
      Jour(JourSemaine jour) {
         super(jour.name());
         init();
      }
      Jour(int i) {
         super(""+i);
         init();  
      }
      private void init() {
         setHorizontalAlignment(CENTER);
         setBorder(BorderFactory.createEtchedBorder());         
         setPreferredSize(new Dimension(37, 20));
      }
   }  
   
   private class Mois extends JComponent {
      Calendar aujourdhui = Calendar.getInstance();
      Calendar calendrier = Calendar.getInstance();
      int jour = calendrier.get(Calendar.DAY_OF_MONTH);
      int mois;
      int année;
      int premierJourDuMois;
      int nombreJour;
      
      Mois() {
         init();
         setLayout(new GridLayout(0, 7));
      }
      
      private void init() {
         removeAll();
         mois = calendrier.get(Calendar.MONTH);
         année = calendrier.get(Calendar.YEAR);
         Calendar calcul = calendrier;
         calcul.set(Calendar.DAY_OF_MONTH, 1);
         premierJourDuMois = calcul.get(Calendar.DAY_OF_WEEK);    
         nombreJour = calendrier.getActualMaximum(Calendar.DAY_OF_MONTH);
         for (int i=1; i<premierJourDuMois; i++) add(new JLabel());
         for (int i=1; i<=nombreJour; i++) {
            Jour jourDuMois = new Jour(i);
            if (i==jour && mois==aujourdhui.get(Calendar.MONTH) && année==aujourdhui.get(Calendar.YEAR)) 
               jourDuMois.setForeground(Color.RED);
            add(jourDuMois);
         }
         revalidate();
         repaint();
         pack();
      }
      
      void setMois(int mois) {
         calendrier.set(Calendar.MONTH, mois);
         init();        
      }
      
      void setAnnée(int année) {
         calendrier.set(Calendar.YEAR, année);
         init();   
      }
   }
   
   private class Sélection extends JComponent implements ActionListener, ChangeListener {
      private JComboBox mois = new JComboBox(new String[] {"Janvier", "Février", "Mars", 
         "Avril", "Mai","Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"});
      private JSpinner année = new JSpinner(new SpinnerNumberModel(jourMois.année, 0, 2100, 1));
      
      Sélection() {
         setLayout(new BorderLayout());
         mois.setSelectedIndex(jourMois.mois);         
         mois.addActionListener(this);
         année.setFont(new Font("Arial", Font.BOLD+Font.ITALIC, 16));
         année.addChangeListener(this);
         add(mois);
         add(année, BorderLayout.EAST);
      }

      public void actionPerformed(ActionEvent e) {
         jourMois.setMois(mois.getSelectedIndex());
      }

      public void stateChanged(ChangeEvent e) {
         jourMois.setAnnée((Integer)année.getValue());
      }
   }
}

Bien sûr, peu d'applications possèdent une mise en forme aussi rigide que la façade d'un calendrier. Dans la pratique, de petites grilles (contenant généralement une seule ligne ou une seule colonne) permettent d'organiser des zones partielles d'une fenêtre. Si vous disposez d'une ligne de bouton de taille identique, vous pouvez les placer à l'intérieur d'un panneau géré par un gestionnaire GridLayout avec une seule ligne.

 

Choix du chapitre Le gestionnaire CardLayout

Le gestionnaire CardLayout permet de disposer des composants suivant une pile, à la manière d'un jeu de carte, de telle façon que seul le composant supérieur soit visible à un moment donné. Des méthodes sont alors disponibles afin de permettre de parcourir cette pile ou encore de se placer sur le composant souhaité.

Création de la pile

A la création d'un tel gestionnaire, nous pouvons préciser des retraits entre le composant et le conteneur. Quel que soit le choix que vous faites, avec ou sans retrait, il est généralement souhaitable, pour une fois, de créer une instance de ce gestionnaire afin de pouvoir retrouver par la suite le composant désiré (la carte) afin que nous puissions le placer au dessus de la pile :

GridLayout pile = new GridLayout(); // Création d'une gestion de pile
ou
GridLayout
pile = new GridLayout(5, 4)); // Création d'une gestion de pile avec une marge de part et d'autre de 5 pixels, et de 4 pixels de haut en bas.
conteneur.setLayout(pile); // mise en place de ce gestionnaire sur le conteneur souhaité

Ajout des composants sur ce gestionnaire

Pour ajouter un composant au conteneur qui prend en compte le gestionnaire CardLayout, utilisez une version à deux arguments de la méthode add(). Le second argument de cette méthode add() est une chaîne qui sert à identifier la carte présente sur la pile. Cette identification sera éventuellement utile par la suite pour y faire référence et placer ainsi la carte souhaitée au-dessus de la pile :

conteneur.add(composant, "Un composant"); // ajout d'un composant sur le conteneur avec son identification obligatoire.

Notez que, même si cette identification n'est éventuellemnt pas nécessaire à la suite du programme, l'argument correspondant doit quand même être fourni (nous pouvons utiliser une chaîne vide). Dans le cas contraire, nous obtenons une erreur d'exécution.

Choisir la carte à afficher

Par défaut, le composant visible est le premier ajouté au conteneur. Pour amener une carte particulière en haut de la pile, appelez la méthode show() de CardLayout avec deux arguments : le conteneur parent et la chaîne d'identification que nous avons mis en oeuvre au travers de la méthode add(). D'autres méthodes comme first(), last(), next() et previous(), permettent également de manipuler la pile de cartes. Elles sont toutes des méthodes d'instances de CardLayout. Donc, pour les invoquer, vous êtes obligé de faire référence à l'objet CardLayout proprement dit, non au conteneur qui le gère. Chaque méthode ne prend alors qu'un argument qui correspond au conteneur parent.

pile.show(conteneur, "Un composant"); // Placer la carte désignée au dessus de la pile
pile.next(conteneur); // Placer la carte suivante au dessus de la pile. Si il n'y en a plus, c'est la première carte qui se place de nouveau.
pile.previous(conteneur); // Placer la carte précédente au dessus de la pile. Si il n'y en a plus, c'est la dernière carte qui se place de nouveau.
pile.first(conteneur); // Placer la première carte au dessus de la pile
pile.last(conteneur); // Placer la dernière carte au dessus de la pile

Exemple d'application

Nous allons, à titre d'exemple, mettre en oeuvre un jeu qui permet de déterminer un nombre qui a été choisi aléatoirement avec un nombre de coups limité. Ce jeu comporte trois phases qui seront implémentées par trois cartes différentes.

  1. La première phase : consiste à la configuration du jeu qui permet de choisir la valeur maximale limite du nombre aléatoire et du nombre de coups accepté.



  2. La deuxième phase : est le jeu proprement dit. L'utilisateur propose les nombres et un petit message d'avertissement indique si le nombre est plus grand ou plus petit.



  3. La dernière phase : indique le résultat, soit l'utilisateur a trouvé ou alors le programme indique le nombre à rechercher et affiche l'historique de l'ensemble des nombres proposés.

package jeu;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import javax.swing.event.*;

public class Aléatoire extends JFrame {   
   private int nombre, nombreAléatoire, tentative, maximum = 10, coup = 3;
   private ArrayList<Integer> historique = new ArrayList<Integer>();
   private boolean gagné = false;
   
   private Configuration configuration = new Configuration();
   private Jeu jeu = new Jeu();
   private Résultat résultat = new Résultat();
   private CardLayout pile = new CardLayout();
   
   public Aléatoire() {
      super("Nombre aléatoire");
      setLayout(pile);
      add(configuration, "configuration");
      add(jeu, "jeu");
      add(résultat, "résultat");      
      pack();
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setResizable(false);
      setVisible(true);
   }
   
   public static void main(String[] args) { new  Aléatoire(); }
   
   private class Etiquette extends JLabel {
      public Etiquette(String intitulé) {
         super(intitulé);
         setHorizontalAlignment(RIGHT);
         setBorder(BorderFactory.createEtchedBorder());         
         setPreferredSize(new Dimension(112, 22));
      }
   }
   
   abstract private class Panneau extends JPanel implements ActionListener, FocusListener {
      protected JPanel panneau = new JPanel();
      protected JButton continuer = new JButton("Continuer");
      
      public Panneau() {
         setLayout(new BorderLayout());
         panneau.setBackground(Color.ORANGE);
         panneau.setLayout(new GridLayout(0, 2));
         add(panneau);
         add(continuer, BorderLayout.SOUTH);
         continuer.addActionListener(this);
         addFocusListener(this);
         setFocusable(true);
      }

      public void focusGained(FocusEvent e) { }
      public void focusLost(FocusEvent e) {}
   }
   
   private class Configuration extends Panneau implements ChangeListener, ActionListener {
      private JSpinner saisieMaximum = new JSpinner(new SpinnerNumberModel(10, 7, 100, 5));
      private JSpinner saisieCoup = new JSpinner(new SpinnerNumberModel(3, 3, 10, 1));
            
      public Configuration() {        
         panneau.add(new Etiquette("Maximum : "));
         panneau.add(saisieMaximum);
         panneau.add(new Etiquette("Coup : "));
         panneau.add(saisieCoup);        
         saisieMaximum.addChangeListener(this);        
         saisieCoup.addChangeListener(this);
      }

      @Override
      public void actionPerformed(ActionEvent e) {
         pile.next(getContentPane());
         jeu.requestFocus();
      }

      public void stateChanged(ChangeEvent e) {
         if (e.getSource() == saisieMaximum) maximum = (Integer)saisieMaximum.getValue();
         if (e.getSource() == saisieCoup) coup = (Integer)saisieCoup.getValue();
      }  
   }
   
   private class Jeu extends Panneau {
      private JTextField saisieNombre = new JTextField(nombre);
      private JLabel afficheRésultat = new JLabel("Tentez votre chance...");
      private JTextField lectureTentative = new JTextField("0");
      
      public Jeu() {
         panneau.add(new Etiquette("Valeur : "));
         panneau.add(saisieNombre);
         panneau.add(new Etiquette("Tentatives : "));
         lectureTentative.setEditable(false);
         panneau.add(lectureTentative);
         add(afficheRésultat, BorderLayout.SOUTH);
         saisieNombre.addActionListener(this);
      }

      @Override
      public void actionPerformed(ActionEvent e) {
         historique.add(nombre = Integer.parseInt(saisieNombre.getText()));
         tentative++;
         gagné = nombre == nombreAléatoire;
         if (gagné || tentative==coup) {
            pile.next(getContentPane());
            résultat.requestFocus();
         }      
         else {
            lectureTentative.setText(""+tentative);
            afficheRésultat.setText(" Le nombre est "+(nombreAléatoire>nombre ? "plus grand" : "plus petit"));
         }
      }
      
      @Override
      public void focusGained(FocusEvent e) {
         nombreAléatoire = (int)(Math.random()*maximum)+1;
         tentative = 0;
         afficheRésultat.setText("Tentez votre chance...");
         lectureTentative.setText("0");
         saisieNombre.setText("0");
         historique.clear();      
         pack();
      }
   }
   
   private class Résultat extends Panneau {
      @Override
      public void actionPerformed(ActionEvent e) {
         continuer.setText("Continuer");
         pile.next(getContentPane());
      }
      
      public void focusGained(FocusEvent e) {
         continuer.setText("Recommencer");
         panneau.removeAll();
         if (gagné) panneau.add(new JLabel(" Bravo ..."));
         else {
            panneau.add(new Etiquette("Nombre à trouver : "));
            panneau.add(new Etiquette(nombreAléatoire+" "));
            for (int i=0; i<historique.size(); i++) {
               panneau.add(new Etiquette("Coup n°"+(i+1)+" : "));
               panneau.add(new Etiquette(historique.get(i)+" "));
            } 
         } 
         pack();
      }
   }
}

 

Choix du chapitre Le gestionnaire BoxLayout

Le gestionnaire BoxLayout permet de disposer des composants suivant une seule ligne ou une seule colonne. Associé au conteneur particulier qu'est Box, il permet une certaine souplesse que n'offrirait pas un GridLayout à une seule ligne ou une seule colonne. C'est donc uniquement dans ce contexte que nous utilisons ce gestionnaire.

Il existe effectivement un conteneur spécifique, la classe Box, dont le gestionnaire par défaut est BoxLayout, alors que celui d'un JPanel est un FlowLayout. Bien sûr, vous pouvez aussi affecter un BoxLayout pour qu'il soit le gestionnaire d'un JPanel, mais il est généralement plus simple de partir d'un conteneur Box. L'intérêt ici, c'est que la classe Box contient un certain nombre de méthodes statiques utiles à la gestion d'un BoxLayout.

Attention : bien que ce soit un composant swing, Box (et non JBox) ne dérive pas de JComponent. Il ne possède donc pas de méthode paintComponent(). Il est donc préférable d'éviter de dessiner sur un tel conteneur.

Création d'une ligne ou d'une colonne de composants

Vous avez donc la possibilité de placer des composants, tout en concervant leurs tailles préférées, soit sur une seule ligne, soit sur une seule colonne. Nous obtenons ainsi une certaine souplesse par rapport à un GridLayout, puisque ce dernier place tous les composants avec une même dimension commune. Le choix de l'orientation se fait au travers des méthodes statiques createHorizontalBox() et createVerticalBox().

Box ligne = Box.createHorizontalBox(); // box horizontal - ligne de composants
ou
Box colonne = Box.createVerticalBox(); // box vertical - colonne de composants

Placer les composants sur la ligne ou la colonne

Une fois que le box est créé, il suffit de faire appel, comme d'habitude, à la méthode add() du conteneur (ici donc un Box) :

ligne.add(new JTextField()); // Place une zone d'édition à gauche sur la ligne de composants.
ligne.add(new JRadioButton("Franc", true)); // Place un bouton radio à la suite sur la même ligne.
ligne.add(new JRadioButton("€uro")); // Place un autre bouton radio à la suite sur la même ligne.
ligne.add(new JButton("Conversion")); // Place ensuite un bouton de lancement de la conversion.
ligne.add(new JLabel("0 Franc")); // Place enfin une étiquette de résultat.

Gestion de placement des composants

Pour fixer les idées, supposons que nous avons affaire à un Box horizontal - ligne - comme l'exemple ci-dessus. Les composants, ajoutés classiquement par add(), sont disposés de gauche à droite ; ils sont contigus et occupent toute la largeur et toute la hauteur du conteneur. A cet effet, ils sont étirés ou retrécis dans la mesure du possible. Si tous les composants ne peuvent pas tenir dans la largeur de la fenêtre, certains ne seront pas visibles.

Stratégie suivie pour le placement des composants

Ainsi, dans un conteneur Box horizontal, les composants sont placés de la gauche vers la droite. Dans un box vertical, ils sont disposés du haut vers le bas. Reétudions la mise en forme horizontale de plus près. Rappelons avant tout que chaque composant possède trois valeurs :

  1. la taille préférée, la largeur et la hauteur préférées auxquelles afficher le composant ;
  2. la taille maximale, la largeur et la hauteur maximales auxquelles afficher le composant ;
  3. la taille minimale, la largeur et la hauteur minimales auxquelles afficher le composant.

Voici en détail ce que le gestionnaire BoxLayout réalise :

  1. Il calcule la taille maximale du composant le plus grand en hauteur.
  2. Il tente d'agrandir verticalement tous les composants jusqu'à cette hauteur.
  3. Si un composant n'atteint pas cette taille lorsque cela est demandé, son alignement y est requis en appelant sa méthode getAlignmentY(). Celle-ci renvoie un nombre à virgule flottante entre 0 (alignement en haut) et 1 (alignement en bas). La valeur par défaut dans la classe Component est 0,5 (centré). La valeur est utilisée pour aligner le composant verticalement.
  4. La largeur préférée de chaque composant est extraite. Toutes les largeurs préférées sont additionnées.
  5. Si la largeur totale est inférieure à la largeur d'un Box, les composants sont étirés, en les laissant s'agrandir jusqu'à leur largeur maximale. Ils sont ensuite placés de la gauche vers la droite, sans intervalles supplémentaires de séparation. Si la largeur totale préférée est supérieure à celle d'un Box, les composants sont réduits, éventuellement jusqu'à leur largeur minimale, mais pas d'avantage. S'il ne tiennent pas dans un Box avec leur largeur minimale, certains ne seront pas affichés.
Il est dommage que BoxLayout tente d'agrandir les composants au-delà de la taille préférée. En particulier, les champs de texte ont une largeur et une hauteur maximales définies par Integer.MAX_VALUE ; c'est-à-dire qu'ils ne tentent de s'agrandir autant que nécessaire. Si vous placez un champ de texte dans un objet BoxLayout, il atteindra des proportions monstrueuses.



Le remède consiste à affecter la taille préférée à la taille maximale :

saisie.setMaximumSize(saisie.getPreferredSize()); // La taille maximale est réglée sur la taille préférée.


Proposer un espacement entre les composants

Par défaut, il n'y a pas d'espace entre les composants avec un gestionnaire BoxLayout. Effectivement, à la différence de FlowLayout, il n'y a pas de notion d'intervalle entre les composants. BoxLayout joue sur les dimensions de certains composants afin d'occuper tout l'espace disponible. Mais, d'une part, tous les composants ne sont pas adaptables, et d'autre part, la disposition obtenue en utilisant une telle "élasticité" n'est pas toujours satisfaisante (pour ne pas dire inhestétique).

Heureusement, Java offre des outils supplémentaires pour agir sur cette disposition. Tout d'abord, vous pouvez créer des composants virtuels de taille donnée, ce qui permet de fixer des espaces entre certains composants. D'autre part, vous pouvez forcer certains composants à s'éloigner au maximum les uns des autres.

Pour espacer vos composants entre eux, vous pouvez rajouter trois types de composants virtuels :

  1. Strut (traverse) : ajoute un espace fixe entre les composants adjacents, mais n'agit pas sur l'autre dimension (par exemple, la hauteur sur un Box horizontal). Utilisez la méthode statique Box.createHorizontalStrut(valeur) pour un Box horizontal, et Box.createVerticalStrut(valeur) pour un Box vertical. A titre d'exemple, nous allons rajouter un espacement entre le bouton de conversion et l'étiquette de résultat :

    ligne.add(Box.createHorizontalStrut(10));



    Ainsi, quoi qu'il arrive, il subsistera toujours un espace de 10 pixels entre les composants situés de part et d'autre de ce composant virtuel (ou entre un composant et le bord du Box). Vous pouvez aussi ajouter un Strut vertical dans un conteneur Box horizontal, mais cela n'influe pas sur la mise en forme horizontale. Cela définit à la place la hauteur minimale du Box.
  2. RigidArea : analogue à Strut, il sépare des composants adjacents, mais ajoute aussi une hauteur ou une largeur maximale dans l'autre direction. Pour cela, utilisez la méthode statique Box.createRigidArea(dimension). Ainsi dans l'instruction suivante :

    ligne.add(Box.createRigidArea(new Dimension(5, 30)));



    ajoute un espace invisible avec une largeur minimale, préférée et maximale de 5 pixels, une hauteur de 30 pixels et un alignement centré. S'il est ajouté dans un conteneur Box horizontal, il agit comme un Strut d'une largeur de 5 et force aussi une hauteur minimale de 30 pixels pour un Box.
  3. Glue (colle) : espace virtuel de taille entièrement ajustable. L'ajout d'un Glue sépare les composants autant que possible. Un Glue (invisible) s'agrandit au maximum de l'espace disponible, en éloignant les composants les uns des autres. Pour cela, utilisez la méthode statique Box.createGlue(). A titre d'exemple, je propose de placer un Glue entre les boutons radios et le bouton de conversion :

    ligne.add(Box.createGlue());


Codage définitif du logiciel de conversion avec la prise en compte d'un BoxLayout

package conversion;

import java.<