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.
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().
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(); } }
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(); } }
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(); } }
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(); } }
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(); } }
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.
.
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).
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.
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 :
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.
.
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.
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.
.
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) |
conteneur.setLayout(new FlowLayout(FlowLayout.RIGHT));
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));
FlowLayout layout = new FlowLayout();
conteneur.setLayout(layout);
layout.setHgap(10);
layout.setVgap(15);
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"); } }
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
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) |
conteneur.add(panneau, BorderLayout.NORTH);
conteneur.add(éditeur);
conteneur.add(commentaires, BorderLayout.EAST);
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));
BorderLayout layout = new BorderLayout();
conteneur.setLayout(layout);
layout.setHgap(10);
layout.setVgap(15);
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.
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.
.
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.
package 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.
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é.
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é
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.
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
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.
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(); } } }
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.
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
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.
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.
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 :
Voici en détail ce que le gestionnaire BoxLayout réalise :
saisie.setMaximumSize(saisie.getPreferredSize()); // La taille maximale est réglée sur la taille préférée.
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 :
ligne.add(Box.createHorizontalStrut(10));
ligne.add(Box.createRigidArea(new Dimension(5, 30)));
ligne.add(Box.createGlue());
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"); } } }
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 :
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é.
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.
Voyons maintenant comment mettre en place ces différents ressorts sur chacun des composants.
private JButton ouest = new JButton("Ouest"); private JButton centre = new JButton("Centre"); private JButton est = new JButton("Est");
SpringLayout layout = new SpringLayout(); setLayout(layout); add(ouest); add(centre); add(est);
Spring ressort = Spring.constant(0, 500, 500);
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).
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);
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(); } }
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); }
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.
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.
.
Spring saisies = Spring.sum(intitulés, espace);
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.
.
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 :
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.
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.
.
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.
.
Spring.sum(s, Spring.minus(t));
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);
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 :
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"); } } }
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.
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);
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 :
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.
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 :
Ce qui est génial dans cette écriture, c'est que vous pouvez enchaîner les méthodes addComponent() pour une même orientation.
.
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.
... 16 GroupLayout.ParallelGroup horzGroupe = groupe.createParallelGroup(); 17 GroupLayout.SequentialGroup vertGroupe = groupe.createSequentialGroup(); ...
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.
... 16 GroupLayout.SequentialGroup horzGroupe = groupe.createSequentialGroup(); 17 GroupLayout.SequentialGroup vertGroupe = groupe.createSequentialGroup(); ...
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).
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 :
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(); } }
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});
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(); } }
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.
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.
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 :
La disposition proposée et ces différentes remarques amènent le code suivant :
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).
Si nous agrandissons la fenêtre de l'application précédente, voici ce que nous obtenons :
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.
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.
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. |
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.
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.
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(); } }
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.
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.
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(); } }
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 :
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.
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(); } }
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 :
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"); } } }
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 :
JButton validation = new JButton("Ok"); setLayout(null); add(validation); validation.setBounds(10, 10, 30, 15);