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.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 Box panneau = Box.createHorizontalBox();
   private ButtonGroup groupe = new ButtonGroup();
   private JRadioButton boutonFranc = new JRadioButton("Franc", true);
   private JRadioButton bouton€uro = new JRadioButton("€uro");
   
   public Conversion() {
      super("€uros <-> Francs"); 
      Dimension dim = new Dimension(100, 22);
      saisie.setPreferredSize(dim);
      saisie.setMaximumSize(saisie.getPreferredSize());
      panneau.add(Box.createHorizontalStrut(5));
      panneau.add(saisie);
      groupe.add(boutonFranc);
      groupe.add(bouton€uro);   
      panneau.add(boutonFranc);
      panneau.add(bouton€uro);
      panneau.add(Box.createGlue());
      conversion.setPreferredSize(dim);
      conversion.setMaximumSize(conversion.getPreferredSize());
      panneau.add(conversion);
      panneau.add(Box.createRigidArea(new Dimension(5, 30)));
      résultat.setBorder(BorderFactory.createEtchedBorder());
      panneau.add(résultat);
      panneau.add(Box.createHorizontalStrut(5));
      add(panneau, BorderLayout.SOUTH);
      saisie.addActionListener(this);
      conversion.addActionListener(this);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      pack();
      setVisible(true);
   }
   
   public static void main(String[] args) {
      new Conversion();  
   }

   public void actionPerformed(ActionEvent e) {
       final double TAUX = 6.55957;
       if (boutonFranc.isSelected()) {
          double calcul = Double.parseDouble(saisie.getText()) * TAUX;
          résultat.setText(calcul+" Francs");
       }
       if (bouton€uro.isSelected()) {
          double calcul = Double.parseDouble(saisie.getText()) / TAUX;
          résultat.setText(calcul+" €uros");
       }     
   }
}

 

Choix du chapitre Le gestionnaire SpringLayout

Le gestionnaire SpringLayout permet d'ajouter des ressorts à chaque composant. Un ressort est un appareil grâce auquel nous pouvons spécifier des positions de composant. Chaque ressort dispose :

  1. d'une valeur minimale ;
  2. d'une valeur préférée ;
  3. d'une valeur maximale ;
  4. d'une valeur réelle.

Lorsque le ressort est comprimé ou étendu pendant la phase de mise en forme, sa valeur réelle est fixée entre la valeur minimale et la valeur maximale et s'approche de la valeur préférée aussi près que le permettent les autres ressorts. La valeur réelle détermine ensuite la position du composant auquel il est attaché.


Contrainte sur l'ensemble des ressorts

La classe du ressort définit une opération de somme qui produit un nouveau ressort à partir de deux ressorts, lequel associe les caractéristiques de chaque ressort. Lorsque vous disposez plusieurs composants en ligne, vous attachez plusieurs ressorts à chacun de ces composants, de sorte que leur somme s'étende sur la totalité du conteneur.



Ce ressort de somme est maintenant compressé ou étendu de sorte que sa valeur égale la dimension du conteneur. Cette opération exerce une contrainte sur chaque ressort. Chaque valeur de ressort est définie de manière que la contrainte de chaque ressort égale la contrainte de la somme. Les valeurs de chaque ressort sont donc déterminées, et la disposition est fixée.

Exemple de mise en oeuvre de ressort élastique

Voyons maintenant comment mettre en place ces différents ressorts sur chacun des composants.

  1. Supposons que nous voulions disposer trois boutons horizontalement :
    private JButton ouest = new JButton("Ouest");
    private JButton centre = new JButton("Centre");
    private JButton est = new JButton("Est");
  2. Nous définissons d'abord le gestionnaire de mise en forme du cadre sur un SpingLayout et nous ajoutons ensuite les composants :
    SpringLayout layout = new SpringLayout();
    setLayout(layout);
    add(ouest);
    add(centre);
    add(est);
  3. Construisons maintenant un ressort avec une bonne compression. La méthode statique Spring.constant() produit un ressort avec les valeurs minimale, préférée et maximale données (bien sûr, le ressort n'est pas constant, il peut être comprimé ou étendu) :
    Spring ressort = Spring.constant(0, 500, 500);
  4. Attachons ensuite une copie du ressort du côté ouest du conteneur jusqu'au côté ouest du bouton "Ouest" :
    layout.putConstraint(SpringLayout.WEST, ouest, ressort, SpringLayout.WEST, getContentPane());

    La méthode putConstraint() ajoute le ressort spécifié, de sorte qu'il se termine au premier jeu de paramètres (le côté ouest du bouton "Ouest" dans notre cas) et commence au deuxième jeu de paramètres (le côté ouest du conteneur).

  5. Vous liez ensuite les autres ressorts :
    layout.putConstraint(SpringLayout.WEST, centre, ressort, SpringLayout.EAST, ouest);
    layout.putConstraint(SpringLayout.WEST, est, ressort, SpringLayout.EAST, centre);
  6. Enfin, pour finaliser la compression, nous accrochons un ressort au côté est du conteneur :
    layout.putConstraint(SpringLayout.EAST, getContentPane(), ressort, SpringLayout.EAST, est);
Résultat, les quatre ressorts sont comprimés à la même taille et les boutons, espacés de manières égale :
package gestionnaire;

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

public class Fenêtre extends JFrame {
   private JButton ouest = new JButton("Ouest");
   private JButton centre = new JButton("Centre");
   private JButton est = new JButton("Est");
   private SpringLayout layout = new SpringLayout();
   
   public Fenêtre() {
      setLayout(layout);
      add(ouest);
      add(centre);
      add(est);
      Spring ressort = Spring.constant(0, 500, 500);      
      layout.putConstraint(SpringLayout.WEST, ouest, ressort, SpringLayout.WEST, getContentPane());
      layout.putConstraint(SpringLayout.WEST, centre, ressort, SpringLayout.EAST, ouest);
      layout.putConstraint(SpringLayout.WEST, est, ressort, SpringLayout.EAST, centre);
      layout.putConstraint(SpringLayout.EAST, getContentPane(), ressort, SpringLayout.EAST, est);

      setTitle("Une fenêtre");
      setSize(350, 110);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }
   
   public static void main(String[] args) { new Fenêtre(); }
}

Ressort constant

Vous pouvez aussi faire varier les distances. Supposons que vous vouliez installer une distance fixe entre les boutons. Utilisez un Strut (un ressort qui ne peut être étendu ni compressé). Pour obtenir ce ressort avec la version à paramètre unique de la méthode Spring.constant(), inscrivez :

Spring espace = Spring.constant(10);

Si vous avez deux Strut entre les boutons, mais que vous laissiez les ressorts aux extrémités, le résultat est un groupe de boutons centré dans le conteneur :

public Fenêtre() {
   setLayout(layout);
   add(ouest);
   add(centre);
   add(est);
   Spring ressort = Spring.constant(0, 500, 500);     
   Spring espace = Spring.constant(10);
   layout.putConstraint(SpringLayout.WEST, ouest, ressort, SpringLayout.WEST, getContentPane());
   layout.putConstraint(SpringLayout.WEST, centre, espace, SpringLayout.EAST, ouest);
   layout.putConstraint(SpringLayout.WEST, est, espace, SpringLayout.EAST, centre);
   layout.putConstraint(SpringLayout.EAST, getContentPane(), ressort, SpringLayout.EAST, est);
   setTitle("Une fenêtre");
   setSize(350, 110);
   setDefaultCloseOperation(EXIT_ON_CLOSE);
   setVisible(true);
}

Alignements verticaux des composants

Bien sûr, la mise en forme du ressort n'est pas vraiment nécessaire pour un agencement aussi simple. Dans ce cas là, un FlowLayout serait même préférable. Etudions un exemple plus complexe, en reprenant notre jeu implémenté plus haut, et en faisant en sorte que les composants soient alignées verticalement.

  1. Nous avons un certain nombre d'étiquettes qui possèdent des dimensions différentes. Il faudrait que les zones de saisie soient toutes alignées les unes envers les autres. Ceci appelle une autre opération de ressort. Vous pouvez former un maximum de deux ressorts avec la méthode statique Spring.max(). Résultat : un ressort aussi long que la plus longue de deux composants. Nous obtenons le maximum de deux côtés :
    Spring intitulés = Spring.max(layout.getConstraint(SpringLayout.EAST, ouest), layout.getConstraint(SpringLayout.EAST, centre));

    Notez que la méthode getContraint() produit un ressort qui part du côté ouest du conteneur pour aboutir aux côtés donnés du composant.
    .

  2. Ajoutons un Strut de sorte qu'il y ait un espace entre les libellés et les zones de saisie :
    Spring saisies = Spring.sum(intitulés, espace);
  3. Nous attachons maintenant ce ressort au côté ouest des zones de saisie. Le point de départ est le départ du conteneur, car c'est là que commence le ressort saisies :
    layout.putConstraint(SpringLayout.WEST, étiquette1, saisies, SpringLayout.EAST, panneau);
    layout.putConstraint(SpringLayout.WEST, étiquette2, saisies, SpringLayout.EAST, panneau);
    ...

    Désormais, les zones de saisie s'alignent car elles sont contenues dans le même ressort.
    .

Justification à droite

Il existe toutefois un léger problème. Nous préférerions que les intitulés soient alignés à droite. Pour obtenir cet effet, il convient de très bien comprendre les attachements des ressorts. Etudions les ressorts horizontaux en détail, car les ressorts verticaux suivent la même logique.

Nous pouvons attacher trois ressorts horizontaux de trois manières :

  1. relier le côté ouest du composant au côté ouest de l'autre composant ;
  2. traverser la largeur du composant ;
  3. relier le côté ouest du composant au côté est du composant.
  1. Vous récupérez ces ressorts comme suit :
    Spring ouest = layout.getConstraints(composant).getX();
    Spring largeur = layout.getConstraints(composant).getWidth();
    Spring est = layout.getConstraint(SpringLayout.EAST, composant);   

    La méthode getContraints() produit un objet SpringLayout.Constraints. Il ressemble à un rectangle, mais les valeurs x, y, width et height sont des ressorts, et non des nombres. La méthode getContraint() produit un seul ressort qui atteint l'une des quatre limites du composant.

  2. Vous pouvez également récupérer le ressort ouest sous la forme :
    Spring ouest = layout.getConstraint(SpringLayout.WEST, composant);
    

    Bien sûr, les trois ressorts sont liés : la somme des ressorts ouest et largeur doit être égale à est.
    .

  3. A la première définition des contraintes du composant, la largeur est définie sur un ressort dont les paramètres sont la largeur minimale, préférée et maximale du composant. En réalité, le côté ouest est définie sur 0.

    Si vous ne définissez pas le ressort ouest (et nord) d'un composant, le composant s'arrête au décalage 0 du conteneur.
    .

  4. Si un composant possède deux jeux de ressorts et que vous en ajoutiez un troisième, il devient surcontraint (overconstraint). l'un des ressorts existants est alors supprimé et sa valeur est calculée comme la somme ou la différence des autres ressorts.

    La différence entre deux ressorts peut ne pas être intuitive, mais elle est logique dans l'algèbre des ressorts. Il n'existe pas de méthode Java pour la soustraction du ressort. Si vous devez calculer la différence entre deux ressorts, utilisez :
    Spring.sum(s, Spring.minus(t));

  5. Nous en savons maintenant suffisamment sur les ressorts pour résoudre le problème de la justification à droite. Il faut calculer le maximum des largeurs des deux intitulés. Il faut définir ensuite le ressort est des deux intitulés sur ce maximum.
    Spring intitulés = Spring.sum(Spring.max(layout.getConstraints(label1).getWidth(), layout.getConstraints(label2).getWidth());
    layout.putConstraint(SpringLayout.EAST, label1, intitulés, SpringLayout.WEST, panneau);
    layout.putConstraint(SpringLayout.EAST, label2, intitulés, SpringLayout.WEST, panneau);

Récapitulation des classes et des méthodes utilisées

Nous avons utilisés respectivement les classes SpringLayout, SpringLayout.Constraints et Spring. Voici donc les tableaux récapitulatifs de ces classes là avec leurs méthodes usuelles :

javax.swing.SpringLayout
SpringLayout.Constraints getContrains(Component composant)
Cette méthode récupère les contraintes du composant passé en argument géré par ce gestionnaire de mise en forme.
void putConstraint(String côté, Component fin, Spring ressort, Component côté, Component début)
void putConstraint(String côté, Component fin, int taille, Component côté, Component début)
Ces méthodes définissent le côté donné du composant fin sur un ressort obtenu par l'ajout du ressort ressort, ou une taille, au ressort qui va de l'extrémité gauche du conteneur au côté donné du conteneur début.
javax.swing.SpringLayout.Constraints
Constrains(Component composant)
Construit un objet Constraints dont les positions, la largeur et les ressorts correspondent au composant passé en argument..
Spring getX()
Spring getY()
Ces méthodes renvoient le ressort qui part du début du conteneur jusqu'à l'ouest ou jusqu'au nord du composant contraint.
Spring getWidth()
Spring getHeight()
Ces méthodes renvoient le ressort qui s'étire sur la largeur ou la hauteur du composant contraint.
Spring getConstraint(String côté)
void setConstraint(String côté, Spring ressort)
Ces méthoes récupèrent ou définissent un ressort partant du début du conteneur jusqu'au côté donné du composant.
javax.swing.Spring
static Spring constant(int préférée)
Construit un Strut avec la taille préférée donnée. Les tailles minimale et maximale sont définies sur la taille préférée.
static Spring constant(int minimum, int préférée, int maximum)
Construit un ressort avec les tailles minimale, préférée et maximale données.
static Spring sum(Spring s, Spring t)
Renvoie la somme des ressorts s et t.
static Spring max(Spring s, Spring t)
Renvoie le ressort maximal de s et t.
static Spring minus(Spring s)
Renvoie l'opposé du ressort s.
static Spring scale(Spring s, float multiplicateur)
Echelonne les tailles minimales, préférée et maxiamle de s du multiplicateur donné. Si le facteur multiplicateur est négatif, l'opposé échelonne de s est renvoyée.
static Spring width(Component composant)
static Spring height(Component composant)
Renvoie un ressort dont les tailles minimale, préférée et maximale sont égales aux largeurs ou hauteurs minimale, préférées et maximale du composant donné.
int getMinimumValue()
int getPreferredValue()
int getMaximumValue()
Ces méthodes renvoient la valeur minimale, préférée et maximale de ce ressort.
int getValue()
void setValue(int valeur)
Ces méthodes récupèrent et définissent la valeur du ressort. Lors de la définition de la valeur d'une chaîne composée, les valeurs des composants sont aussi définies.

Exemple récapitulatif

Afin de valider tout ce qui vient d'être dit, je vous proprose de reprendre l'exemple de la conversion entre les €uros et les Francs. Les composants sont ainsi parfaitement alignés les uns envers les autres. Par contre, vous remarquez que ce gestionnaire impose d'écrire pas mal de lignes de code, mais le résultat est plus souple que ceux que nous avons vus précédemment.

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 ButtonGroup groupe = new ButtonGroup();
   private JRadioButton boutonFranc = new JRadioButton("Franc", true);
   private JRadioButton bouton€uro = new JRadioButton("€uro");
   private SpringLayout dispo = new SpringLayout();
   
   public Conversion() {
      super("€uros <-> Francs"); 
      setSize(350, 100);
      setLayout(dispo);
      groupe.add(boutonFranc);
      groupe.add(bouton€uro);
      add(boutonFranc);
      add(bouton€uro);
      add(saisie);
      add(conversion);
      résultat.setBorder(BorderFactory.createEtchedBorder());
      add(résultat);
      // réglages des dispositons de base
      Spring rien = Spring.constant(0);
      Spring marge = Spring.constant(5);
      Spring centre = Spring.sum(marge, 
              Spring.sum(dispo.getConstraints(boutonFranc).getWidth(), dispo.getConstraints(bouton€uro).getWidth()));
      Spring hauteur = dispo.getConstraints(boutonFranc).getHeight();
      Spring deuxième = Spring.sum(marge, Spring.sum(marge, hauteur));
      // disposition du bouton radio Franc
      dispo.putConstraint(SpringLayout.NORTH, boutonFranc, marge, SpringLayout.NORTH, getContentPane());
      dispo.putConstraint(SpringLayout.WEST, boutonFranc, marge, SpringLayout.WEST, getContentPane());
      // disposition du bouton radio €uro
      dispo.putConstraint(SpringLayout.NORTH, bouton€uro, marge, SpringLayout.NORTH, getContentPane());
      dispo.putConstraint(SpringLayout.WEST, bouton€uro, rien, SpringLayout.EAST, boutonFranc);
      // disposition du composant saisie
      dispo.putConstraint(SpringLayout.NORTH, saisie, marge, SpringLayout.NORTH, getContentPane());
      dispo.putConstraint(SpringLayout.SOUTH, saisie, Spring.sum(marge, hauteur), SpringLayout.NORTH, getContentPane());
      dispo.putConstraint(SpringLayout.WEST, saisie, Spring.sum(centre, marge), SpringLayout.WEST, getContentPane());
      dispo.putConstraint(SpringLayout.EAST, saisie, Spring.minus(marge), SpringLayout.EAST, getContentPane());
      // disposition du composant conversion
      dispo.putConstraint(SpringLayout.NORTH, conversion, deuxième, SpringLayout.NORTH, getContentPane());
      dispo.putConstraint(SpringLayout.SOUTH, conversion, Spring.sum(deuxième, hauteur), SpringLayout.NORTH, getContentPane());
      dispo.putConstraint(SpringLayout.WEST, conversion, marge, SpringLayout.WEST, getContentPane());
      dispo.putConstraint(SpringLayout.EAST, conversion, centre, SpringLayout.WEST, getContentPane());
      // disposition du composant résultat
      dispo.putConstraint(SpringLayout.NORTH, résultat, deuxième, SpringLayout.NORTH, getContentPane());
      dispo.putConstraint(SpringLayout.SOUTH, résultat, Spring.sum(deuxième, hauteur), SpringLayout.NORTH, getContentPane());
      dispo.putConstraint(SpringLayout.WEST, résultat, rien, SpringLayout.WEST, saisie);
      dispo.putConstraint(SpringLayout.EAST, résultat, rien, SpringLayout.EAST, saisie);
      
      saisie.addActionListener(this);
      conversion.addActionListener(this);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }
   
   public static void main(String[] args) { new Conversion();  }

   public void actionPerformed(ActionEvent e) {
       final double TAUX = 6.55957;
       if (boutonFranc.isSelected()) {
          double calcul = Double.parseDouble(saisie.getText()) * TAUX;
          résultat.setText(calcul+" Francs");
       }
       if (bouton€uro.isSelected()) {
          double calcul = Double.parseDouble(saisie.getText()) / TAUX;
          résultat.setText(calcul+" €uros");
       }     
   }
}

 

Choix du chapitre Le gestionnaire GroupLayout

Le gestionnaire GroupeLayout 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. Lorsque vous posséder plusieurs groupes, vous avez le choix de laisser une certaine liberté entre eux, ou au contraire, et c'est ce qui fait l'avantage de ce gestionnaire, de gérer les alignements des composants entre les différents groupes.

Par contre, quelle que soit la situation, vous êtes obligé, même pour une structure simple, d'expliquer systématiquement le comportement des composants à la fois horizontalement et verticalement pour un groupe donné, ce qui rajoute d'autant le nombre de lignes de code pour traiter la disposition de l'ensemble des composants sur tous les groupes utilisés.

Ce gestionnaire correspond en réalité au constructeur d'interface graphique Matisse dans NetBeans qui est utilisé depuis longtemps et qui est maintenant intégré à la JDK depuis la version 6.0. Si donc, vous travaillez avec l'environnement de développement NetBeans, vous n'aurez pas à écrire tout le code correspondant à ce gestionnaire. Tout ce fait à l'aide de la souris, ce qui est un sérieux avantage puisque c'est certainement l'un des meilleurs gestionnaire. Il permet, en effet, de réaliser des alignements entre les différents composants du conteneur.

 

Création d'un groupe

Comme d'habitude, vous vous servez de la méthode setLayout() du conteneur pour changer de disposition, mais vous devez au préalable créer un objet qui représente votre gestionnaire GroupLayout. Lors de la création de l'objet, vous devez également spécifier le conteneur qui s'occupe de ce gestionnaire. Cet objet servira par la suite pour placer les composants ou des sous-groupes de composants.

GroupLayout groupe = new GroupLayout(conteneur);
conteneur.setLayout(groupe);

 

Organisation des composants dans un seul groupe de composants

Pour comprendre l'ensemble des mécanismes mis en jeu, je vous propose de commencer par placer des composants sur un seul groupe. Nous verrons par la suite comment gérer plusieurs groupes soit avec une gestion indépendante, soit en proposant une collaboration entre les groupes.

Lorsque vous utilisez le gestionnaire GroupLayout, vous devez systématiquement spécifier l'organisation des composants suivant l'axe horizontal, mais également suivant l'axe vertical. Pour tous les groupes que vous allez mettre en oeuvre, cette description est obligatoire afin que le système sache comment procéder sur la disposition des composants dans les surfaces proposées.

On entend par groupe de composants, un ensemble de composants qui sont en réalité alignés soit suivant l'axe horizontal, soit suivant l'axe vertical. Il est possible, si vous le désirez, d'aligner les composants suivant une diagonale, mais cette disposition est très rarement utilisée dans la pratique. Si vous avez à placer des composants sur plusieurs lignes (ou plusieurs colonnes), vous devez alors mettre en oeuvre plusieurs groupes (sous-groupes du groupe principal).

Puisque vous devez spécifier l'ordonnancement des composants suivant les deux axes, généralement vous aller préciser que dans un cas (suivant un axe), les composants sont placés les uns à la suite des autres, c'est-à-dire séquentiellement, alors que suivant l'autre axe, ils sont sur une même ligne, c'est-à-dire en parallèle.

Ainsi, lorsque vous décrivez un alignement de composants, vous devez créer des objets relatifs aux classes internes du gestionnaire GroupLayout, correspondant à l'alignement choisi, respectivement GroupLayout.SequentialGroup (composants placés les uns à la suite des autres) et GroupLayout.ParallelGroup (composants en parallèle). Ces objets sont créés à l'aide des méthodes respectives createSequentialGroup() et createParallelGroup() issues du gestionnaire GroupLayout. Vous placez ensuite l'ensemble des composants à l'aide de la méthode addComponent() sur chacun de ces objets.

Remarquez que, pour une fois, c'est le gestionnaire qui place les composants et non plus le conteneur.
.

Afin de mieux comprendre et maîtriser le fonctionnement du gestionnaire GroupLayout et de la mise en oeuvre d'un groupe, nous allons traiter plusieurs cas de figure :

Composants alignés horizontalement

Pour le premier exemple, je vous propose de placer trois boutons dans un même groupe, alignés suivant l'axe horizontal. Cela sous-entend que les boutons sont placés séquentiellement suivant l'axe horizontal et donc en parallèle suivant l'axe vertical.

Code correspondant
 1 package gestionnaire;
 2 
 3 import java.awt.*;
 4 import javax.swing.*;
 5 
 6 public class Fenêtre extends JFrame {
 7    private JButton premier = new JButton("Premier");
 8    private JButton deuxième = new JButton("Deuxième");
 9    private JButton troisième = new JButton("Troisième");
10    
11    public Fenêtre() {
12       super("Gestion des groupes");
13       
14       GroupLayout groupe = new GroupLayout(getContentPane());
15       getContentPane().setLayout(groupe);
16       GroupLayout.SequentialGroup horzGroupe = groupe.createSequentialGroup();          
17       GroupLayout.ParallelGroup vertGroupe = groupe.createParallelGroup();
18       horzGroupe.addComponent(premier).addComponent(deuxième).addComponent(troisième);  
19       vertGroupe.addComponent(premier).addComponent(deuxième).addComponent(troisième);
20       groupe.setHorizontalGroup(horzGroupe);
21       groupe.setVerticalGroup(vertGroupe);      
22       
23       setSize(300, 130);
24       setDefaultCloseOperation(EXIT_ON_CLOSE);
25       setVisible(true);
26    }
27    
28    public static void main(String[] args) { new Fenêtre(); }
29 }

Vous voyez que pour un exemple aussi simple, vous devez écrire quelques lignes de code. Dans ce cas précis, il aurait été préférable de prendre le gestionnaire BoxLayout. Il faut toutefois bien comprendre que lorsque nous possèderons plusieurs groupes avec une certaine liaison entre eux, nous possèderons énormément plus de souplesse de placement.

Voici la procédure à suivre pour décrire le comportement de votre groupe :

  1. Lignes 14 et 15 : création du groupe principal groupe.
  2. Lignes 16 et 17 : description de l'alignement des composants suivant les deux axes en créant les objets correspondants : horzGroupe et vertGroupe.
  3. Lignes 18 et 19 : ajout de tous les composants figurant dans le groupe suivant ces deux types d'alignement.
  4. Lignes 20 et 21 : précision de l'orientation des objets d'alignement suivant les deux axes.

Ce qui est génial dans cette écriture, c'est que vous pouvez enchaîner les méthodes addComponent() pour une même orientation.
.

Composants alignés verticalement

Dans cet exemple, nous plaçons les boutons verticalement, c'est-à-dire en parallèle suivant l'axe horizontal et séquentiellement suivant l'axe vertical. Seules, les lignes 16 et 17 sont à changer.

Code correspondant
...
16       GroupLayout.ParallelGroup horzGroupe = groupe.createParallelGroup();          
17       GroupLayout.SequentialGroup vertGroupe = groupe.createSequentialGroup();
...
Composants placés en diagonale dans le groupe

Juste pour un cas d'école, voici comment placer les composants en diagonale dans le groupe. Cette fois-ci, nous plaçons les composants séquentiellement à la fois suivant l'axe horizontal et suivant l'axe vertical.

Code correspondant
...
16       GroupLayout.SequentialGroup horzGroupe = groupe.createSequentialGroup();          
17       GroupLayout.SequentialGroup vertGroupe = groupe.createSequentialGroup();
...

 

Espacement sur le bord du groupe et entre les composants

Pour l'instant, lorsque les composants sont placés, ils sont accolés les uns à côté des autres et ils touchent le bord du cadre. Nous pouvons préciser si nous désirons un espacement entre les composants au travers de la méthode setAutoCreateGaps(true). De même, nous pouvons introduire un espacement par rapport au bord du conteneur au moyen de la méthode setAutoCreateContainerGap(true).

Ces méthodes imposent alors un espacement prédéfini, qu'il n'est donc pas possible de régler. Par contre, pour chaque objet d'alignement GroupLayout.SequentialGroup et GroupLayout.ParallelGroup, il est possible de préciser un alignement spécifique, comme nous le verrons ultérieurement, au moyen de la méthode addGap(valeur).

Codage correspondant
14   GroupLayout groupe = new GroupLayout(getContentPane());
15   getContentPane().setLayout(groupe);
16   groupe.setAutoCreateContainerGaps(true);
17   groupe.setAutoCreateGaps(true);
18   GroupLayout.SequentialGroup horzGroupe = groupe.createSequentialGroup();          
19   GroupLayout.ParallelGroup vertGroupe = groupe.createParallelGroup();
20   horzGroupe.addComponent(premier).addComponent(deuxième).addComponent(troisième);  
21   vertGroupe.addComponent(premier).addComponent(deuxième).addComponent(troisième);
22   groupe.setHorizontalGroup(horzGroupe);
23   groupe.setVerticalGroup(vertGroupe);   

La classe GroupLayout.SequentialGroup possède également des méthodes plus spécifiques à la gestion des intervalles :

  1. addContainerGap() : qui place un intervalle entre le bord du conteneur et le composant qui touche le bord.
  2. addPreferredGap() : qui propose une gestion d'intervalle plus fine entre deux composants (relatif, non relatif ou indenté)
Codage correspondant
package matisse;

import javax.swing.*;

public class Authentification extends JFrame {
   private JLabel _utilisateur = new JLabel("Utilisateur");
   private JTextField utilisateur = new JTextField("Utilisateur");
   private JLabel _motDePasse = new JLabel("Mot de passe");
   private JPasswordField motDePasse = new JPasswordField("Mot de passe");
   
   public Authentification() {
      super("Authentification");
      GroupLayout disposition = new GroupLayout(getContentPane());
      getContentPane().setLayout(disposition);
      disposition.setHorizontalGroup(
         disposition.createParallelGroup(GroupLayout.Alignment.TRAILING)
            .addGroup(disposition.createSequentialGroup()
              .addContainerGap()
              .addGroup(disposition.createParallelGroup(GroupLayout.Alignment.TRAILING)
                .addGroup(disposition.createSequentialGroup()
                  .addComponent(_utilisateur)
                  .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(utilisateur))
                .addGroup(disposition.createSequentialGroup()
                  .addComponent(_motDePasse)
                  .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(motDePasse)))
              .addContainerGap()));
      disposition.setVerticalGroup(
        disposition.createParallelGroup(GroupLayout.Alignment.LEADING)
          .addGroup(disposition.createSequentialGroup()
            .addContainerGap()
            .addGroup(disposition.createParallelGroup(GroupLayout.Alignment.BASELINE)
              .addComponent(_utilisateur)
              .addComponent(utilisateur))
            .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(disposition.createParallelGroup(GroupLayout.Alignment.BASELINE)
              .addComponent(_motDePasse)
              .addComponent(motDePasse))
            .addContainerGap()));
      setSize(300, 110);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }

   public static void main(String[] args) { new Authentification(); }
}

 

Forcer un jeu de composant à avoir la même taille

Vous pouvez forcer un jeu de composant à avoir la même taille. Ainsi, nous pouvons nous assurer que les champs de texte et Mot de passe aient une largeur identique. Il suffit pour cela de faire appel à la méthode linkSize() de la classe GroupLayout :

disposition.linkSize(SwingConstants.HORIZONTAL, new Component[]{utilisateur, motDePasse});

Codage correspondant
package matisse;

import javax.swing.*;

public class Authentification extends JFrame {
   private JLabel _utilisateur = new JLabel("Utilisateur");
   private JTextField utilisateur = new JTextField(15);
   private JLabel _motDePasse = new JLabel("Mot de passe");
   private JPasswordField motDePasse = new JPasswordField();
   
   public Authentification() {
      super("Authentification");
      GroupLayout disposition = new GroupLayout(getContentPane());
      getContentPane().setLayout(disposition);
      disposition.setHorizontalGroup(
         disposition.createParallelGroup(GroupLayout.Alignment.TRAILING)
            .addGroup(disposition.createSequentialGroup()
              .addContainerGap()
              .addGroup(disposition.createParallelGroup(GroupLayout.Alignment.TRAILING)
                .addGroup(disposition.createSequentialGroup()
                  .addComponent(_utilisateur)
                  .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(utilisateur))
                .addGroup(disposition.createSequentialGroup()
                  .addComponent(_motDePasse)
                  .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(motDePasse)))
              .addContainerGap()));
      disposition.setVerticalGroup(
        disposition.createParallelGroup(GroupLayout.Alignment.LEADING)
          .addGroup(disposition.createSequentialGroup()
            .addContainerGap()
            .addGroup(disposition.createParallelGroup(GroupLayout.Alignment.BASELINE)
              .addComponent(_utilisateur)
              .addComponent(utilisateur))
            .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(disposition.createParallelGroup(GroupLayout.Alignment.BASELINE)
              .addComponent(_motDePasse)
              .addComponent(motDePasse))
            .addContainerGap()));
      disposition.linkSize(SwingConstants.HORIZONTAL, new Component[]{utilisateur, motDePasse});
pack
(300, 110); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Authentification(); } }

 

Les différentes classes qui gèrent le groupement des composants dans un conteneur

Avant de rentrer dans les différentes situations qui permettent de bien organiser le placement des composants entre eux dans le même conteneur, je vous propose tout simplement de recenser les méthodes utiles des classes gérant le ou les groupes.

javax.swing.GroupLayout
GroupLayout(Container hôte)
Construit un GroupLayout pour disposer les composants dans le conteneur hôte (vous devrez toujours appeler ensuite la méthode setLayout() du conteneur hôte).
void setHorizontalGroup(GroupLayout.Group groupe)
void setVerticalGroup(GroupLayout.Group groupe)
Définissent le groupe qui contrôle la mise en page horizontale et verticale.
void linkSize(Component ... composant)
void linkSize(int axe, Component ... composant)
Obligent les composants donnés à avoir la même taille ou à afficher la même taille le long de l'axe donné (parmi SwingConstants.HORIZONTAL ou SwingConstants.VERTICAL).
GroupLayout.SequentialGroup createSequentialGroup()
crée un groupe qui dispose ses enfants les uns à la suite des autres.
GroupLayout.ParallelGroup createParallelGroup()
GroupLayout.ParallelGroup createParallelGroup(GroupLayout.Alignment alignement)
GroupLayout.ParallelGroup createParallelGroup(GroupLayout.Alignment alignement, boolean retaillable)
crée un groupe qui dispose ses enfants en parallèle.

- alignement : Une constante parmi BASELINE, LEADING (début - par défaut), TRAILING (fin) ou CENTER.
- retaillable : Vaut true (par défaut) lorsque le groupe peut être redimensionné, false si la taille préférée est aussi la taille minimale et maximale.
boolean getHonorsVisibility()
void setHonorsVisibility(boolean invisible)
Récupèrent ou définissent la propriété honorsVisibility. Losrqu'ils valent true (par défaut), les composants invisibles ne sont pas disposés. Lorsqu'ils valent false, ils sont disposés comme s'ils étaient visisbles. C'est utile lorsque vous masquez temporairement certains composants et que la mise en page ne doit pas changer.
boolean getAutoCreateGaps()
void setAutoCreateGaps(boolean intervalle)
boolean getAutoCreateContainerGaps()
void setAutoCreateContainerGaps(boolean intervalle)
Récupèrent ou définissent les propriétés autoCreateGaps et autoCreateContainerGaps. Losrqu'ils valent true, des intervalles sont automatiquement ajoutés entre les composants ou aux limites du conteneur. Le paramètre par défaut vaut false. Une valeur true est utile lorsque vous produisez manuellement un GroupLayout.
javax.swing.GroupLayout.Group
GroupLayout.Group addComponent(Component composant)
GroupLayout.Group addComponent(Component composant, int minimum, int préférée, int maximum)
Ajoute un composant à ce groupe. Les paramètres de taille peuvent être des valeurs réelles (non négatives) ou les constantes spéciales GroupLayout.DEFAULT_SIZE ou GroupLayout.PREFERRED_SIZE. Lorsque nous utilisons DEFAULT_SIZE, les méthodes getMinimumSize(), getPreferredSize() ou getMaximumSize() du composant sont appelées. Lorsque nous utilisons PREFERRED_SIZE, la méthode getPreferredSize() est appelée.
GroupLayout.Group addGap(int taille)
GroupLayout.Group addGap(int minimum, int préférée, int maximum)
Ajoutent un intervalle de la taille, rigide ou flexible.
GroupLayout.Group addGroup(GroupLayout.Group groupe)
Ajoute le groupe spécifié à ce groupe.
javax.swing.GroupLayout.ParallelGroup
GroupLayout.ParallelGroup addComponent(Component composant, GroupLayout.Alignment alignement)
GroupLayout.ParallelGroup addComponent(Component composant, GroupLayout.Alignment alignement, int minimum, int préférée, int maximum)
GroupLayout.ParallelGroup addComponent(GroupLayout.Group groupe, GroupLayout.Alignment alignement)
Ajoutent un composant ou un groupe à l'aide de l'alignement donné (parmi BASELINE, LEADING (début - par défaut), TRAILING (fin) ou CENTER).
javax.swing.GroupLayout.SequentialGroup
GroupLayout.SequentialGroup addContainerGap()
GroupLayout.SequentialGroup addContainerGap(int préférée, int maximum)
Ajoutent un intervalle pour séparer un composant du bord du conteneur.
GroupLayout.SequentialGroup addPreferredGap(LayoutStyle.ComponentPlacement type)
Ajoute un intervalle pour séparer les composants. Le type est soit :

- LayoutStyle.ComponentPlacement.RELATED : le composants précédent et suivant sont attachés.
- LayoutStyle.ComponentPlacement.UNRELATED : les composants précédent et suivant n'ont aucun lien et sont disjoints.
- LayoutStyle.ComponentPlacement.INDENT : propose une indentation par rapport un un composant de référence.

 

Organisation des composants dans deux goupes, chacun ayant son indépendance l'un vis à vis de l'autre

L'intérêt de prendre ce gestionnaire GroupLayout, c'est d'avoir plusieurs groupes de composant. Nous allons justement voir maintenant comment mettre en place ces différents groupes. Pour l'instant, afin de simplifier, chaque groupe aura sa propre indépendance vis à vis de l'autre.

Nous allons mettre en oeuvre une application qui possède deux groupes de composants. Le premier disposé verticalement est composée de trois libellés, chacun ayant sa propre bordure. Le deuxième groupe est également disposé verticalement à la droite du premier et est composé de trois champs de texte.

Lorsque vous visualisez le résultat de cette application, vous remarquez que les composants sont alignés verticalement. Les groupes ne se chevauchent donc pas. Ils sont placés l'un à côté de l'autre.

Par contre, dans le sens horizontal, les composants du premier groupe ne sont pas du tout alignés sur le deuxième groupe. Les groupes sont donc autonomes.

Pour finir, dans le deuxième groupe, les champs de texte prennent à la fois toute la largeur restante et toute la hauteur restante. Le deuxième groupe se développe alors pour prendre toute la surface qui n'est pas prise par le premier groupe. En fait, tout dépend des composants qui sont placés dans un groupe.

Analyse de la disposition

Analysons la disposition des groupes et des composants. Un schéma est souvent nécessaire et nous aide à tout comprendre d'un coup. Voici, ce que nous pouvons constater :

  1. Le groupe principal est donc formé de deux groupes.
  2. Ces deux groupes sont placés séquentiellement suivant l'axe horizontal, alors qu'ils sont en parallèle suivant l'axe vertical.
  3. Dans chacun de ces groupes, les composants sont placés en parallèle suivant l'axe horizontal et séquentiellement suivant l'axe vertical.

La disposition proposée et ces différentes remarques amènent le code suivant :

Codage correspondant
 1 package gestionnaire;
 2 
 3 import java.awt.*;
 4 import javax.swing.*;
 5 import javax.swing.border.*;
 6 
 7 public class Fenêtre extends JFrame {
 8    private Etiquette un = new Etiquette("Premier");
 9    private Etiquette deux = new Etiquette("Deuxième");
10    private Etiquette trois = new Etiquette("Troisième");
11    private JTextField premier = new JTextField("Premier");
12    private JTextField deuxième = new JTextField("Deuxième");
13    private JTextField troisième = new JTextField("Troisième");
14    
15    public Fenêtre() {
16       super("Gestion des groupes");
17       
18       GroupLayout groupe = new GroupLayout(getContentPane());
19       getContentPane().setLayout(groupe);
20       GroupLayout.SequentialGroup horzGroupe = groupe.createSequentialGroup();     
21       GroupLayout.ParallelGroup vertGroupe = groupe.createParallelGroup();
22       horzGroupe.addGroup(groupe.createParallelGroup().addComponent(un).addComponent(deux).addComponent(trois));  
23       horzGroupe.addGroup(groupe.createParallelGroup().addComponent(premier).addComponent(deuxième).addComponent(troisième));  
24       vertGroupe.addGroup(groupe.createSequentialGroup().addComponent(un).addComponent(deux).addComponent(trois));
25       vertGroupe.addGroup(groupe.createSequentialGroup().addComponent(premier).addComponent(deuxième).addComponent(troisième));
26       groupe.setHorizontalGroup(horzGroupe);
27       groupe.setVerticalGroup(vertGroupe);      
28       
29       setSize(280, 120);
30       setDefaultCloseOperation(EXIT_ON_CLOSE);
31       setVisible(true);
32    }
33    
34    public static void main(String[] args) { new Fenêtre(); }
35    
36    private class Etiquette extends JLabel {
37       public Etiquette(String libellé) { 
38          super(libellé);  
39          setBorder(new CompoundBorder(new EtchedBorder(), new EmptyBorder(2,5,2,5))); 
40       }
41    }
42 }

Pour ajouter un groupe (sous-groupe) au groupe principal, il suffit d'utiliser la méthode addGroup() sur les objets d'alignement SequentialGroup et ParallelGroup de la classe GroupLayout (Lignes 22 à 25).

 

Empêcher le redimensionnement automatique et alignement sur les composants adjacents

Si nous agrandissons la fenêtre de l'application précédente, voici ce que nous obtenons :

La hauteur des champs de texte s'agrandissent pour remplir la totalité du conteneur, ce qui n'est pas agréable d'un point de vue visuel.

D'autre part, les libellés ne sont pas du tout alignés sur les champs de texte qu'ils sont censer représenter.

Pour ces deux raisons, nous allons voir qu'il est possible de faire en sorte que les composants de groupes différents essayent de s'aligner sur leurs lignes de base communes.

Lorsque vous placez des groupes de composants en parallèle, il est effectivement possible d'empêcher que les composants d'un groupe soient automatiquement redimensionner pour prendre toute la capacité du conteneur. Pour cela, vous devez préciser que tous les composants du groupe doivent s'aligner suivant les lignes de base usuelles des composants adjacents se trouvant dans l'autre groupe.

Cela se réalise au moyen de la méthode createBaselineGroup(false, false) en lieu et place de la méthode createParallelGroup().

21   GroupLayout.ParallelGroup vertGroupe = groupe.createBaselineGroup(false, false);
Les champs de texte n'arrive pas à s'aligner sur les libellés de gauche. Effectivement, les champs de texte ont une hauteur minimale qu'il est impossible de contracter. Du coup, la hauteur de chaque champ reste sur cette hauteur minimale.

La méthode createBaseLineGroup() prend deux paramètres. Le premier indique si nous désirons que les composants peuvent être retaillés. Il faut justement empêcher que cette redimension se fasse, sinon ils vont prendre toute la hauteur. Le deuxième paramètre indique que la ligne de base s'effectue à partir du haut. Dans notre cas, nous pouvons éventuellement valider ce paramètre.

codage correspondant
package gestionnaire;

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

public class Fenêtre extends JFrame {
   private JLabel un = new JLabel("Premier : ");
   private JLabel deux = new JLabel("Deuxième : ");
   private JLabel trois = new JLabel("Troisième : ");
   private JTextField premier = new JTextField("Premier");
   private JTextField deuxième = new JTextField("Deuxième");
   private JTextField troisième = new JTextField("Troisième");
   
   public Fenêtre() {
      super("Gestion des groupes");
      
      GroupLayout groupe = new GroupLayout(getContentPane());
      getContentPane().setLayout(groupe);
      GroupLayout.SequentialGroup horzGroupe = groupe.createSequentialGroup();     
      GroupLayout.ParallelGroup vertGroupe = groupe.createBaselineGroup(false, false);
      horzGroupe.addGroup(groupe.createParallelGroup().addComponent(un).addComponent(deux).addComponent(trois));  
      horzGroupe.addGroup(groupe.createParallelGroup().addComponent(premier).addComponent(deuxième).addComponent(troisième));  
      vertGroupe.addGroup(groupe.createSequentialGroup().addComponent(un).addComponent(deux).addComponent(trois));
      vertGroupe.addGroup(groupe.createSequentialGroup().addComponent(premier).addComponent(deuxième).addComponent(troisième));
      groupe.setHorizontalGroup(horzGroupe);
      groupe.setVerticalGroup(vertGroupe);      
      
      setSize(280, 120);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }
   
   public static void main(String[] args) { new Fenêtre(); }
}
Par contre si les libellés posssèdent une hauteur qui dépasse la hauteur minimale des champs de texte, ces derniers s'alignent automatiquement sur les libellés et réglent donc leur hauteur en conséquence.

modification du code correspondant
package gestionnaire;

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

public class Fenêtre extends JFrame {
   private Etiquette un = new Etiquette("Premier");
   private Etiquette deux = new Etiquette("Deuxième");
   private Etiquette trois = new Etiquette("Troisième");

...

   private class Etiquette extends JLabel {
      public Etiquette(String libellé) { super(libellé);   setBorder(new EmptyBorder(5, 7, 5, 7)); }
   }
}  

Avec cette démarche, nous obtenons un alignement des composants d'un groupe à l'autre dans le sens vertical, ce qui devient très intéressant.

 

Mise en place d'un groupe séquentiel vertical avec un autre groupe séquentiel horizontal

Nous pouvons, à titre d'exercice, faire en sorte que le deuxième groupe soit placé, cette fois-ci, horizontalement, en faisant en sorte que les champs de texte soient alignés sur la ligne de base du premier groupe, comme ceci :

Pour cela, il suffit juste de spécifier une autre orientation pour les sous-groupes.

codage correspondant
package gestionnaire;

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

public class Fenêtre extends JFrame {
   private JLabel un = new JLabel("Premier : ");
   private JLabel deux = new JLabel("Deuxième : ");
   private JLabel trois = new JLabel("Troisième : ");
   private JTextField premier = new JTextField("Premier");
   private JTextField deuxième = new JTextField("Deuxième");
   private JTextField troisième = new JTextField("Troisième");
   
   public Fenêtre() {
      super("Gestion des groupes");
      
      GroupLayout groupe = new GroupLayout(getContentPane());
      getContentPane().setLayout(groupe);
      GroupLayout.SequentialGroup horzGroupe = groupe.createSequentialGroup();     
      GroupLayout.ParallelGroup vertGroupe = groupe.createBaselineGroup(false, false);
      horzGroupe.addGroup(groupe.createParallelGroup().addComponent(un).addComponent(deux).addComponent(trois));  
      horzGroupe.addGroup(groupe.createSequentialGroup().addComponent(premier).addComponent(deuxième).addComponent(troisième));  
      vertGroupe.addGroup(groupe.createSequentialGroup().addComponent(un).addComponent(deux).addComponent(trois));
      vertGroupe.addGroup(groupe.createParallelGroup().addComponent(premier).addComponent(deuxième).addComponent(troisième));
      groupe.setHorizontalGroup(horzGroupe);
      groupe.setVerticalGroup(vertGroupe);      
      
      setSize(280, 120);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }
   
   public static void main(String[] args) { new Fenêtre(); }
}

 

Imposer l'alignement des composants aussi bien horizontalement que verticalement

Nous avons adopté une première démarche pour aligner les composants. Pour cela, nous avons été obligé de créer une nouvelle classe, appelée Etiquette qui permettait d'avoir une hauteur des libellés qui soit suffisamment grande pour que les champs de texte soient capable de s'aligner au moyen de la méthode createBaselineGroup(). L'idéal serait de proposer un alignement sous forme de grille sans pour cela créer de nouvelles classes adaptatrices.

Pour mettre en place une grille de composants (la seule solution qui impose un alignement suivant les deux axes), vous devez mettre en place une référence croisée de vos groupes. Ainsi, pour le groupe principal, vous ajoutez les groupes systématiquement de façon séquentielle.

Pour l'axe horizontal, les groupes constituerons alors les colonnes de la grille, et pour l'axe verticale, les différentes lignes de la grille. Ainsi, à chaque colonne et à chaque grille va correspondre un ensemble de composants qui finalement ne peuvent être considérés que comme mis en parallèle.

Après toutes ces remarques, voici le codage correspondant qui finalement rajoute très peu de code par rapport aux études précédentes. Il s'agit juste d'une approche différente.

codage correspondant
package gestionnaire;

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

public class Fenêtre extends JFrame {
   private JLabel un = new JLabel("Premier : ");
   private JLabel deux = new JLabel("Deuxième : ");
   private JLabel trois = new JLabel("Troisième : ");
   private JTextField premier = new JTextField("Premier");
   private JTextField deuxième = new JTextField("Deuxième");
   private JTextField troisième = new JTextField("Troisième");
   
   public Fenêtre() {
      super("Gestion des groupes");
      
      GroupLayout groupe = new GroupLayout(getContentPane());
      getContentPane().setLayout(groupe);
      GroupLayout.SequentialGroup horzGroupe = groupe.createSequentialGroup();     
      GroupLayout.SequentialGroup vertGroupe = groupe.createSequentialGroup();
      horzGroupe.addGroup(groupe.createParallelGroup().addComponent(un).addComponent(deux).addComponent(trois));  
      horzGroupe.addGroup(groupe.createParallelGroup().addComponent(premier).addComponent(deuxième).addComponent(troisième));  
      vertGroupe.addGroup(groupe.createParallelGroup().addComponent(un).addComponent(premier));
      vertGroupe.addGroup(groupe.createParallelGroup().addComponent(deux).addComponent(deuxième));
      vertGroupe.addGroup(groupe.createParallelGroup().addComponent(trois).addComponent(troisième));
      groupe.setHorizontalGroup(horzGroupe);
      groupe.setVerticalGroup(vertGroupe);      
      
      setSize(280, 130);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }
   
   public static void main(String[] args) { new Fenêtre(); }
}

Vous pouvez aussi faire en sorte que les champs de texte concervent leur tailles préférées, et donc imposer que les libellés s'ajustent en conséquence. Utilisez pour cela la ligne de base au moyen de la méthode createBaselineGroup() sur chacune des lignes de la grille.

Utilisation de la ligne de base
package gestionnaire;

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

public class Fenêtre extends JFrame {
   private JLabel un = new JLabel("Premier : ");
   private JLabel deux = new JLabel("Deuxième : ");
   private JLabel trois = new JLabel("Troisième : ");
   private JTextField premier = new JTextField("Premier");
   private JTextField deuxième = new JTextField("Deuxième");
   private JTextField troisième = new JTextField("Troisième");
   
   public Fenêtre() {
      super("Gestion des groupes");
      
      GroupLayout groupe = new GroupLayout(getContentPane());
      getContentPane().setLayout(groupe);
      GroupLayout.SequentialGroup horzGroupe = groupe.createSequentialGroup();     
      GroupLayout.SequentialGroup vertGroupe = groupe.createSequentialGroup();
      horzGroupe.addGroup(groupe.createParallelGroup().addComponent(un).addComponent(deux).addComponent(trois));  
      horzGroupe.addGroup(groupe.createParallelGroup().addComponent(premier).addComponent(deuxième).addComponent(troisième));  
      vertGroupe.addGroup(groupe.createBaselineGroup(false, false).addComponent(un).addComponent(premier));
      vertGroupe.addGroup(groupe.createBaselineGroup(false, false).addComponent(deux).addComponent(deuxième));
      vertGroupe.addGroup(groupe.createBaselineGroup(false, false).addComponent(trois).addComponent(troisième));
      groupe.setHorizontalGroup(horzGroupe);
      groupe.setVerticalGroup(vertGroupe);      
      
      setSize(280, 130);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }
   
   public static void main(String[] args) { new Fenêtre(); }
}

 

Prévoir des justifications à gauche, à droite, centrée ou en ligne de base ainsi que des espaces personnalisés

Lorsque les composants sont placés en parallèle, nous pouvons prévoir une justification à gauche (valeur par défaut), à droite, centrée ou en ligne de base. La méthode createParallelGroup() accepte éventuellement un argument qui spécifie la justification souhaitée. Pour cela, vous devez indiquez les constantes suivantes :

  1. Alignment.LEADING : tous les composants en parallèle sont placés à gauche (ou en haut).
  2. Alignment.TRAILING : tous les composants en parallèle sont placés à droite (ou en bas).
  3. Alignment.CENTER : tous les composants en parallèle sont centrés.
  4. Alignement.BASELINE : tous les composants sont sur la même ligne de base que les composants adjacents (équivalent à l'utilisation de la méthode createBaselineGroup().

La méthode createParallelGroup() accepte éventuellement un argument supplémentaire, de type booléen, pour accepter éventuellement un redimmensionnement automatique du composant.

Reprenons notre exemple en proposant un alignement à droite de tous mes libellés. Profitons-en pour placer des espaces personnalisés au moyen de la méthode addGap() prévue par les classes SequentialGroup et ParallelGroup.

codage correspondant
package gestionnaire;

import java.awt.*;
import javax.swing.*;
import javax.swing.GroupLayout.Alignment;
import javax.swing.border.*;

public class Fenêtre extends JFrame {
   private JLabel un = new JLabel("Premier");
   private JLabel deux = new JLabel("Deuxième");
   private JLabel trois = new JLabel("Troisième");
   private JTextField premier = new JTextField("Premier");
   private JTextField deuxième = new JTextField("Deuxième");
   private JTextField troisième = new JTextField("Troisième");
   
   public Fenêtre() {
      super("Gestion des groupes");
      
      GroupLayout groupe = new GroupLayout(getContentPane());
      getContentPane().setLayout(groupe);
      GroupLayout.SequentialGroup horzGroupe = groupe.createSequentialGroup().addGap(5);     
      GroupLayout.SequentialGroup vertGroupe = groupe.createSequentialGroup().addGap(5);
      horzGroupe.addGroup(groupe.createParallelGroup(Alignment.TRAILING).addComponent(un).addComponent(deux).addComponent(trois)).addGap(5);  
      horzGroupe.addGroup(groupe.createParallelGroup().addComponent(premier).addComponent(deuxième).addComponent(troisième)).addGap(15);  
      vertGroupe.addGroup(groupe.createParallelGroup(Alignment.BASELINE).addComponent(un).addComponent(premier)).addGap(3);
      vertGroupe.addGroup(groupe.createParallelGroup(Alignment.BASELINE).addComponent(deux).addComponent(deuxième)).addGap(3);
      vertGroupe.addGroup(groupe.createParallelGroup(Alignment.BASELINE).addComponent(trois).addComponent(troisième));
      groupe.setHorizontalGroup(horzGroupe);
      groupe.setVerticalGroup(vertGroupe);      
      
      setSize(280, 120);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }
   
   public static void main(String[] args) { new Fenêtre(); }
}

 

Exemple récapitulatif

Afin de récapituler tout ce que nous venons de voir, voici un exemple de synthèse qui intègre un sous-groupe

Nous pouvons établir un certain nombre de remarques :

  1. Globalement cette application gère deux groupes verticaux de composants 2 et 3.
  2. Les composants du groupe 2 (à gauche) sont alignés sur les composants du groupe 3 (ceux de droite).
  3. Il existe un sous-groupe 1 incluant les radios boutons qui se trouve placé à l'intérieur du groupe 2.
  4. Les composants du groupe 2 sont centrés horizontalement.
  5. Les composants du groupe 3 sont justifiés à droite.
  6. La taille du groupe 2 est fixée par la taille du sous-groupe. La largeur s'ajuste automatiquement.
  7. La taille du groupe 3 dépend de la taille du groupe 2 et s'ajuste automatiquement.
Codage correspondant
package conversion;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.GroupLayout.Alignment;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;

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 ButtonGroup groupeBoutons = new ButtonGroup();
   private JRadioButton boutonFranc = new JRadioButton("€ => F", true);
   private JRadioButton bouton€uro = new JRadioButton("F => €");
   
   public Conversion() {
      super("€uros <-> Francs"); 
      setSize(350, 100);
      GroupLayout groupe = new GroupLayout(getContentPane());
      gestionGroupes(groupe);
      groupeBoutons.add(boutonFranc);
      groupeBoutons.add(bouton€uro);      
      résultat.setBorder(new CompoundBorder(new EtchedBorder(), new EmptyBorder(2, 5, 2, 5)));
      saisie.addActionListener(this);
      conversion.addActionListener(this);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }
   
   public void gestionGroupes(GroupLayout groupe) {
      setLayout(groupe);
//      groupe.setAutoCreateContainerGaps(true);
//      groupe.setAutoCreateGaps(true);

      // création d'un groupe séquentiel suivant l'axe horizontal
      GroupLayout.SequentialGroup hGroupe = groupe.createSequentialGroup().addGap(5);
      GroupLayout.SequentialGroup hSousGroupe = groupe.createSequentialGroup();
      groupe.setHorizontalGroup(hSousGroupe);
      hSousGroupe.addComponent(boutonFranc).addComponent(bouton€uro);
      hGroupe.addGroup(groupe.createParallelGroup(Alignment.CENTER).addGroup(hSousGroupe).addComponent(conversion)).addGap(5);
      hGroupe.addGroup(groupe.createParallelGroup(Alignment.TRAILING).addComponent(saisie).addComponent(résultat)).addGap(5);     
      groupe.setHorizontalGroup(hGroupe);

      // Create d'un groupe séquentiel suivant l'axe vertical
      GroupLayout.SequentialGroup vGroupe = groupe.createSequentialGroup().addGap(5);
      GroupLayout.SequentialGroup vSousGroupe = groupe.createSequentialGroup();
      vSousGroupe.addGroup(groupe.createParallelGroup().addComponent(boutonFranc).addComponent(bouton€uro));
      vGroupe.addGroup(groupe.createParallelGroup(Alignment.CENTER).addGroup(vSousGroupe).addComponent(saisie)).addGap(5);
      vGroupe.addGroup(groupe.createParallelGroup(Alignment.CENTER).addComponent(conversion).addComponent(résultat)).addGap(5);
      groupe.setVerticalGroup(vSousGroupe);
      groupe.setVerticalGroup(vGroupe);
   }
   
   public static void main(String[] args) { new Conversion();  }

   public void actionPerformed(ActionEvent e) {
       final double TAUX = 6.55957;
       if (boutonFranc.isSelected()) {
          double calcul = Double.parseDouble(saisie.getText()) * TAUX;
          résultat.setText(calcul+" Francs");
       }
       if (bouton€uro.isSelected()) {
          double calcul = Double.parseDouble(saisie.getText()) / TAUX;
          résultat.setText(calcul+" €uros");
       }     
   }
}

 

Choix du chapitre Création sans gestionnaire de mise en forme

Vous souhaiterez parfois ne pas utiliser de gestionnaire de mise en forme et pouvoir placer un composant à un endroit fixe (appelé position absolue). Pour des applications indépendantes de la plate-forme, ce n'est pas une très bonne idée. En revanche elle est adaptée à l'élaboration rapide d'un prototype.

Voici comment procéder pour placer un composant à un endroit fixe :

  1. Définissez le gestionnaire de mise en forme avec la valeur null.
  2. Ajoutez le composant de votre choix dans le conteneur.
  3. Spécifiez ensuite la position et la taille voulue.
JButton validation = new JButton("Ok");
setLayout(null);
add(validation);
validation.setBounds(10, 10, 30, 15);