Le GridBagLayout expliqué

bbclone

Le GridBagLayout est un des gestionnaires de placement les plus flexibles qu'offre en standard la plateforme Java. De par son énorme flexibilité, le GridBagLayout est un peu complexe à prendre en main, et est souvent mis de coté par les développeurs débutants. Nous allons voir, en tout cas c'est le but de ce tutoriel, que c'est une grosse erreur car le GridBagLayout permet de résoudre la plupart des problèmes de positionnement.
La liste complète des codes sources accompagnant ce tutoriel peut être téléchargée ici.

- Titre : Le GridBagLayout expliqué

- Auteur : bbclone

- Parution : 8 avril 2007

- Mise à jour : 14 avril 2007

- Licence : Copyright ® 2010 bbclone. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- 1. Quelques rappels.

- 2. Le GridBagLayout pas à pas.

- 2-1. Définir la taille des composants.

- 2-2. Définir des contraintes.

- a. Les propriétés gridx et gridy.

- b. Les propriétés gridwidth et gridheight.

- c. La propriété insets.

- d. La propriété anchor.

- e. La propriété fill.

- f. Les propriétés weightx et weighty.

- g. Les propriétés ipadx et ipady.

- 3. Un exemple complet.

- 4. Quelques problèmes courants.

- 4-1. Problème numéro 1: Oubli de spécifier des poids.

- 4-2. Problème numéro 2: Oubli de spécifier les tailles préférées et minimales.

- 4-3. Problème numéro 3: Un gestionnaire de placement vertical.

- 5. Conclusion.

- Références.

- Remerciements.

1 - Quelques rappels.

Nous allons commencer par quelques rappels qui nous ferons le plus grand bien. Un gestionnaire de placement est un algorithme qui, appliqué à un container (Frame, Panel, JWindows) va permettre de positionner les différents composants qu'on y ajoute. Illustrons ceci en prenant un exemple d'un des gestionnaires de placement les plus simples à comprendre, à savoir, le BorderLayout. Celui-ci divise le container auquel il est appliqué en cinq zones distinctes et permet de placer un composant dans chacune de ces zones.

Les 5 zones du BorderLayout

L'algorithme est assez simple à comprendre. Mais, ce qui est plus important à comprendre c'est comment l'algorithme va se charger, et sur quoi va-t-il se baser, pour calculer les tailles de ces différentes zones. Pour répondre à cette question, il faut nous rappeler que chaque composant à des désidératas qu'il exprime au travers de ses méthodes getPreferedSize(), getMinimumSize() et getMaximumSize(). L'algorithme défini au niveau du gestionnaire de placement se base sur ces désidératas là pour agencer les différents composants dans le container. Gardons bien en tête que tous les gestionnaires de placement ne font pas appel à ces trois méthodes. Reprenons notre exemple du BorderLayout. Celui-ci ne fait appel qu'aux méthodes getMinimumSize() et getPreferedSize() dont il ne tient pas compte complètement. Si nous prenons maintenant un code source utilisant le BorderLayout, celui-ci pourrait ressembler à ceci :

Utilisation du BorderLayout.

/* 1- Initialisation du container. */
container.setLayout(new BorderLayout());

/* 2- Création et initialisation d'un composant. */
JButton button = new JButton("PAGE_START");
button.setPreferedSize(new Dimension(-1, 50));

/* 3- Ajout du composant au container en précisant la contrainte BorderLayout.PAGE_START. */
container.add(button, BorderLayout.PAGE_START);

Comme nous pouvons le voir, l'utilisation du BorderLayout, ou d'un gestionnaire de placement en général, se fait en trois temps :

1. Initialisation du container en lui appliquant le gestionnaire de placement souhaité.

2. Création et initialisation de chacun des composants à ajouter au container.

3. Ajout de ces composants au container en l'associant à une contrainte.

Nous déguisons un peu la réalité d'abord pour la rendre plus simple à comprendre, mais principalement car l'objet de ce tutoriel est de comprendre le fonctionnement du GridBagLayout. Nous n'allons pas nous attarder sur d'autres gestionnaires de placement qui peuvent proposer certaines facilités.

En ce qui concerne le GridBagLayout, le principe est exactement le même. Au lieu d'être limité à cinq zones, le GridBagLayout agence les différents composants sur une grille virtuelle dont chaque ligne peut avoir une hauteur différente des autres et chaque colonne une largeur différente des autres. Chaque composant peut s'étendre sur plusieurs cellules aussi bien horizontalement que verticalement dans cette grille. Le GridBagLayout est donc tellement flexible que de simples constantes comme dans le cas du BorderLayout ne suffisent pas. Nous devons alors spécifier cette contrainte au travers d'un objet de la classe GridBagConstraints à notre étape numéro 3. Prenons un exemple de code reprenant les trois étapes en utilisant le GridBagLayout. Ce code pourrait ressembler à ceci :

Utilisation du GridBagLayout.

/* 1- Initialisation du container. */
container.setLayout(new GridBagLayout());

/* 2- Création et initialisation d'un composant. */
JButton button = new JButton("1");

/* 3- Ajout de ce composant au container en spécifiant une contrainte de type GridBagConstraints. */
GridBagConstraints gbc = new GridBagConstraints();

container.add(button, gbc);

Dans cet exemple nous avons choisi d'utiliser les valeurs par défaut de chacune des propriétés de l'objet GridBagConstraints. Mais il est évident que nous pouvons le paramétrer à notre guise au travers de ses propriétés publiques ; des propriétés que nous pouvons utiliser pour positionner, aligner et jouer sur la taille des composants. Nous allons les passer en revue dans les paragraphes suivants. A chaque propriété, nous vous proposerons quelques exercices simples pour pouvoir appliquer la théorie qui viendra d'être vue.

2 - Le GridBagLayout pas à pas.

2-1 - Définir la taille des composants.

Comme nous l'avons dit précédemment, le GridBagLayout est un gestionnaire de placement qui permet d'agencer des composants sur une grille virtuelle en se basant sur les désidératas des composants spécifiés au travers de leurs méthodes getMinimumSize() et getPreferedSize(). Si la taille du container le permet, tous les composants auront pour taille leur taille préférée, sinon, c'est la taille minimum qui leur sera attribuée, ce qui peut poser des résultats bizarres sur les champs de saisie. Ces désidératas vont également permettre de calculer la taille de chacune des cellules de la grille. Dans cette grille virtuelle, toutes les cellules d'une même ligne auront toujours la même hauteur. De même, chacune des cellules d'une même colonne auront toute la même largeur.

La hauteur d'une cellule de cette grille sera égale à la hauteur maximale parmi les composants de cette ligne. La largeur d'une cellule de la grille sera égale à la largeur maximale parmi les composants de cette colonne.

Il est inutile de définir une taille maximum par composant car le GridBagLayout ne fait jamais appel à leurs méthodes getMaximumSize(). Pour les tailles minimales et préférées, elles possèdent généralement des valeurs calculées en interne lors de l'initialisation du composant. C'est ainsi que lors du développement d'une interface graphique, il est souvent inutile de préciser les tailles minimums et préférées pour tous les composants. Certaines valeurs suffisent généralement. Prenons l'exemple d'un formulaire classique.

Un formulaire classique.

Excepté pour les boutons Ok et Annuler, sur aucun autre composant nous n'avons défini de tailles minimales ni préférées. Pour les étiquettes, nous utilisons les valeurs par défaut initialisées par Swing en interne. Pour les champs de saisie et pour la liste déroulante, nous avons demandé au GridBagLayout d'étendre ces composants pour que ceux-ci s'étendent sur toute la place qui est disponible horizontalement. Au niveau des boutons, pour que ceux-ci aient la même largeur, nous avons dû leur spécifier une taille préférée identique.

L'ensemble des codes source accompagnant ce tutoriel peuvent être téléchargés ici. Nous vous invitons à les télécharger, les compiler et les exécuter par vous-mêmes. Ceux-ci sont pleinement documentés et ont été écrit dans une optique pour faciliter leur compréhension.

2-2 - Définir des contraintes.

Revenons maintenant sur la classe GridBagConstraints. Lorsque nous ajoutons un composant à un container utilisant un GridBagLayout, ce composant est généralement associé à une contrainte de type GridBagConstraints. Si aucune contrainte n'est spécifiée, une contrainte avec des valeurs par défaut sera utilisée. Nous vous recommandons de toujours spécifier la contrainte pour éviter d'avoir des surprises.

Voyons maintenant les différentes possibilités qu'offre un objet de type GridBagConstraints au travers de ses propriétés publiques. Nous allons supposer que l'orientation des containers est toujours sur LEFT_TO_RIGHT.

a - Les propriétés gridx et gridy.

Ces propriétés permettent de positionner un composant dans la grille. Prenons un petit exemple pour comprendre comment les cases de cette grille sont numérotées.

Les propriétés gridx et gridy.

Dans cet exemple, nous pouvons voir que notre grille possède 4 lignes et 3 colonnes. La numérotation de la grille commence en gridx et gridy égales à zéro dans le coin supérieur gauche. Ces deux propriétés ne peuvent contenir que des valeurs entières supérieures ou égales à zéro. En se déplaçant de gauche à droite, gridx augmente de 1 par cellule. En se déplaçant vers le bas, gridy augmente de 1 par cellule.

Chacune des cellules ne doit pas forcément contenir un composant. Par exemple, la case (0,4) de cet exemple est vide.

Bien qu'il est fortement conseillé de toujours spécifié les positions x et y d'une contrainte, il est également possible d'utiliser la valeur GridBagConstraints.RELATIVE (qui est la valeur par défaut). Cette constante signifie que le composant sera placé dans une cellule à droite (gridx) ou dans une cellule en dessous (gridy) du composant précédent.

b - Les propriétés gridwidth et gridheight.

Ces propriétés permettent de préciser le nombre de cases qu'occupera un composant horizontalement (gridwidth) et verticalement (gridheight). Ces propriétés doivent être une valeur entière supérieure ou égale à 1. Cependant, il est également possible de spécifier deux constantes particulières : GridBagConstraints.REMAINDER et GridBagConstraints.RELATIVE. REMAINDER permet de préciser que ce composant est le dernier de sa ligne ou de sa colonne. Nous vous recommandons de toujours l'utiliser. RELATIVE permet de préciser l'avant dernier composant. Il occupera toutes les cellules disponibles depuis sa position jusqu'à l'avant dernière cellule.

Reprenons un nouvel exemple pour illustrer ces deux propriétés en images.

Les propriétés gridwidth et gridheight.

Nous avons dessiné la grille virtuelle en rouge sur cet exemple. Les composants encadrés en bleu sont les derniers de leurs lignes. Ils ont donc des gridwidth fixées à GridBagConstraints.REMAINDER. Le composant encadré en vert est le dernier de sa ligne et de sa colonne. Les propriétés gridwidht et gridheight de sa contrainte sont donc fixées toutes les deux à GridbagConstraints.REMAINDER.

Nous pouvons également remarquer que lorsque nous utilisons la constante REMAINDER, la grille est légèrement déformée. Par exemple, sur la première et la dernière ligne de notre interface, il n'y a qu'une seule colonne qui sera aussi large que la largeur du container. De même, notre grille ne contient pas de case numérotée (4, 4). Le dernier composant sur la quatrième ligne est en (3, 4).

Nous avons utilisé la constante RELATIVE pour la liste déroulante, juste pour montrer tout les cas possibles. Mais il est clair que nous aurions pu préciser un gridwith de 2 pour la liste déroulante. Ce qui aurait donné le même résultat.

Pour le reste des composants, il suffit de compter le nombre de cases sur lesquelles le composant peut s'étendre horizontalement et verticalement et de l'attribuer respectivement aux propriétés gridwidth et gridheight de la contrainte lui étant associée. Regardons sur l'exemple l'espace réservé à la photo. Cet espace à un gridheight de 4. Il s'étend donc sur les cellules {(0,1), (0,2), (0,3), (0,4)} de notre grille.

c - La propriété insets.

La propriété insets permet de préciser des marges en pixels autour du composant à l'intérieur de la (ou des) case(s) qui lui sont allouées. Cette propriété doit être un objet de type java.awt.Insets. Par défaut, les marges sont nulles. Reprenons notre exemple précédent pour illustrer cette propriété. Nous l'avons présenté ainsi:

Un formulaire avec marge.

En réalité, nous avons légèrement triché en utilisant des marges autour des composants. Si nous ne l'avions pas fait, nous aurions le résultat suivant :

Un Formulaire sans marges.

Tous les composants sont collés au bord du container et ne sont pas espacés entre eux. Remarquons au passage qu'une marge peut être négative (pratique déconseillée). Ceci permet de "faire sortir" le composant de sa cellule et de déborder sur la (les) cellule(s) adjacente(s). Nous pouvons le voir sur l'image suivante ; nous avons définie une marge négative à gauche (de 15 pixels) sur le bouton radio "Homme". Nous pouvons voir qu'il déborde sur la cellule de l'étiquette "Sexe".

Une marge peut être négative.

Jusqu'à présent, nous avons vu comment nous pouvons positionner un composant dans une cellule, comment lui allouer une ou plusieurs cellules et nous venons de voir comment mettre des marges autour des composants. Il est vraiment important que ces notions soient bien maîtrisées pour comprendre la suite de ce tutoriel, qui se corse légèrement. C'est pour cette raison que nous vous proposons trois petits exercices pour pouvoir vous exercer.

Exercice 1

Exercice 2

Exercice 3

d - La propriété anchor.

Nous l'avons vu précédemment, un composant est positionné sur une cellule de la grille grâce à sa position x et y et qu'il peut s'étendre sur plusieurs cellules aussi bien horizontalement que verticalement. La propriété anchor permet de spécifier un point d'ancrage à un composant à l'intérieur de sa (ou ses) cellule(s). Par défaut, le composant sera centré horizontalement et verticalement dans les cellules qui lui sont allouées. En effet, si la taille de la case de la grille est supérieure aux désidératas du composant, nous pourrons choisir où ancrer ce composant au sein de cet espace. Prenons un exemple pour illustrer la chose.

La propriété anchor.

Nous pouvons voir dans cet exemple une grille de 2 lignes et de 3 colonnes. La hauteur de la première ligne est trop grande (verticalement) par rapport aux désidératas des boutons 1 et 3. Grâce à la propriété anchor, nous allons pouvoir préciser comment positionner ces boutons dans l'espace qui leur est réservé. Pour le bouton 1, nous avons choisi de le placer au nord (GridBagConstraints.PAGE_START) et pour le bouton 3 au sud (GridBagConstraints.PAGE_END). Quant au bouton 4, lui a un espace horizontal beaucoup plus large que ce que lui demande. Nous avons choisi dans cet exemple de l'aligner au centre de sa case (GridBagConstraints.CENTER).

Nous pouvons, pour schématiser, imaginer une cellule (ou un groupe de cellules) subdivisée en une série de neuf zones, chacune pouvant servir de point d'ancrage:

Les constantes que nous vous présentons ont été introduites avec Merlin (Java 1.4). Antérieurement, il était possible de spécifier des constantes comme NORTH, EAST, SOUTHEAST... Ces constantes, bien qu'étant toujours acceptées, sont fortement déconseillées car elles ne permettent pas d'être localisées.

Nous n'allons utiliser que les nouvelles constantes dans nos exemples. Reprenons un exemple plus parlant pour découvrir la propriété anchor en images.

GridBagConstraints.LINE_START

GridBagConstraints.CENTER

GridBagConstraints.LINE_END

Dans cet exemple, notre grille ne contient qu'une seule colonne. Le bouton ayant besoin de peu de place par rapport à la liste de noms, nous pouvons choisir où ancrer ce bouton dans sa colonne.

Depuis Java SE 6, de nouvelles constantes pour les points d'ancrages ont été introduites pour supporter la ligne de base. Commençons par comprendre leur but pour ensuite revenir sur leur utilisation.

La ligne de base (ou baseline) est une ligne imaginaire sur laquelle reposent les lettres lorsque nous écrivons. Lorsque nous étions petits, et que nous apprenions à écrire, ces lignes étaient bel et bien réelles.

Le GridBagLayout supporte la ligne de base.

Lors du développement d'une interface utilisateur, il est bien plus élégant (et reposant pour les yeux à la lecture) d'aligner les composants sur cette ligne de base imaginaire Reprenons un de nos exemples précédents pour comprendre cela en images.

Alignement sur la ligne de base.

Nous avons dessiné en bleu les lignes de base. Sur chacune des lignes de notre grille, une ligne de base peut exister. En effet, tous les composants ne supportent pas la ligne de base (JEditorPane, JTextPane...), ou n'en n'ont pas d'utilité (JSeparator...). Aucune ligne de base ne sera donc définie sur une ligne de la grille ne contenant aucun composant ayant une ligne de base.

Comme nous l'avons fait pour les constantes de bases, nous pouvons diviser une cellule ou un groupe de cellules en neuf zones comme ceci (orientation du container sur LEFT_TO_RIGHT):

Chacune de ces zones permet d'ancrer un composant dans sa (ou ses) cellules. Elles s'utilisent exactement comme les constances précédemment présentées mais l'alignement sera, dans ce cas-ci plus adéquat.

e - La propriété fill.

Cette propriété permet de définir la manière dont un composant sera (ou ne sera pas) redimensionné lorsque l'espace qui lui est alloué est supérieur à celui de ses désidératas. Un composant pourra être redimensionné pour occuper l'entièreté de l'espace qui lui est alloué horizontalement, verticalement ou dans les deux sens. Par défaut, aucun redimensionnement n'est appliqué. Prenons un exemple.

Dans cet exemple, nous avons 3 boutons. Le Bouton 3 est seul sur sa ligne. Il a donc l'ensemble de sa l'espace de sa ligne pour se placer. Nous avons décidé de l'ancrer à la position LINE_START. Aligné au centre, cela aurait donné :

Maintenant, si nous précisons sur la contrainte du bouton 3 de s'étendre horizontalement (propriété fill = GridBagConstraints.HORIZONTAL), le résultat, et peu importe où le bouton est ancré serait :

Cette propriété est plutôt simple à comprendre. Par contre, elle est un peu plus subtile lorsqu'elle est combinée avec des poids (weightx et weighty). Nous allons voir ca dans le paragraphe suivant.

f - Les propriétés weightx et weighty.

Ces deux propriétés permettent de définir comment l'espace supplémentaire sera distribué parmi les composants horizontalement (weightx) et verticalement (weighty). Par défaut, aucun poids n'est défini. Ces deux propriétés sont très subtiles et, mal utilisées, peuvent provoquer des désastres au niveau de l'interface que nous développons. Les valeurs les plus communes sont les valeurs 0 et 1. Rare sont les cas où nous devrons préciser d'autres valeurs.

Prenons un exemple assez courant. Imaginons que nous avons une fenêtre avec trois boutons comme ceci :

Nous avons définie une taille préférée identique sur chacun des boutons et l'espace du container est encore trop large. La question est : comment distribuer l'espace supplémentaire (en vert sur l'image) ?.

Nous avons deux possibilités :

1- Soit allouer tout l'espace à un des composants, ici nous distribuons l'espace supplémentaire au bouton aide.

Pour obtenir un résultat pareil, il nous suffit de préciser un weightx de 1 sur le bouton Aide et de l'ancrer sur LINE_END. En fait, lorsque nous définissons un poids weightx sur une contrainte, c'est toute la colonne qui sera redimensionnée et nous aurons à choisir un point d'ancrage adéquat. Nous aurions très bien pu ancrer le bouton Aide au centre. Nous aurions eu le résultat suivant :

L'espace supplémentaire est complètement distribué pour la première colonne mais nous avons décidé d'ancrer le composant au centre de celle-ci ce qui donne le résultat précédent. Si nous décidons de centrer le composant sur LINE_START, nous verrons un résultat très courant.

Lorsque nous agrandissons le container, l'espace supplémentaire s'ajoutera entre les boutons Aide et OK.

2- Soit de partager l'espace supplémentaire parmi les composants. Nous allons par exemple choisir de donner 50% de l'espace supplémentaire au bouton Aide; les boutons OK et Cancel recevrons chacun 25% de l'espace supplémentaire (ce qui donne 100%).

Associé à une politique de redimensionnement le résultat peut donner :

Si nous prenons notre règle, nous pourrons nous assurer que le bouton Aide à une largeur deux fois plus grande que le bouton Ok ou Annuler.

Remarquons que toute valeur double est autorisée pour les poids. En fait, la somme des poids d'une rangée (ou d'une colonne) vaut 100%. C'est-à-dire que si nous précisons une seule contrainte de poids sur une rangée (weightx) de 0.5 ou de 50, celle-ci sera équivalente à 1. Si nous précisons 3 poids, disons de 10, 10, 20, ceux-ci seront équivalent à 0.25, 0.25 et 0.5.

Dans la plupart des cas, il y a souvent un élément central dans l'interface qui s'occupera de récupérer l'espace supplémentaire aussi bien horizontalement que verticalement et dans lequel il s'étendra. Nous le verrons dans notre exemple complet réalisé pas à pas.

g - Les propriétés ipadx et ipady.

Ces attributs permettent de définir des marges internes au composant. Nous l'avons vu précédemment, le GridBagLayout demande les tailles minimales ou préférées sur les composants ajoutés au container. Lorsque nous précisons une valeur ipadx différente de nulles, la largeur minimale (ou préférée) du composant à laquelle cette contrainte est augmentée de chaque coté du composant. L'utilité de ces marges est assez vague et nous ne les utiliserons pas dans nos exemples.

Voyons tout de même en image le comportement de ces propriétés.

Ces deux zones de saisie ont exactement la même taille préférée, mais à la seconde nous avons ajouté un ipadx de 20. Nous pouvons directement voir que leurs tailles ne sont pas égales. La seconde zone de saisie est légèrement plus large que la première car nous lui avons spécifié un ipadx de 20 pixels à la contrainte lui étant associée. Si nous mesurons nous pouvons remarquer que les 20 pixels sont distribué de part et d'autre du composant de manière égale (10 pixels de chaque coté).

3 - Un exemple complet.

Nous allons maintenant développer une simple interface utilisateur pas à pas. L'interface que nous avons choisie est un simple « Explorateur de dossiers », comme nous retrouvons dans Firefox. Voyons en images ce que nous devons réaliser.

Il est toujours important de bien savoir ce à quoi nous désirons arriver avant de commencer. Non pas qu'il soit compliqué d'adapter une interface, mais réfléchir sur une interface est bien plus efficace avec un crayon, une gomme sur une feuille de papier que dans un IDE quelconque. A gauche nous voyons notre interface à sa taille minimale. A droite, nous l'avons agrandie pour observer comment les composants de celle-ci se redimensionnent. Nous pouvons voir que seulement deux composants sont redimensionnés : l'arbre contenant la structure de fichier et la zone qui permet de saisir le nom du dossier. Nous pouvons également remarquer que les boutons Ok et Cancel ont toujours la même taille et qu'ils restent groupés à droite. Le bouton Make New Folder quant à lui reste aligné à gauche.

Il n'est pas toujours évident de savoir par où commencer. Nous, nous commencerons par nos trois étapes du début de ce tutoriel.

1- Initialisation d'un container avec le GridBagLayout.

Avouons que ceci est assez élémentaire. Le code pourrait se résumer à :

/* 1- Initialisation du container. */
mainFrame.setLayout(new GridBagLayout());

2- Création et initialisation des composants.

Ceci est toujours très simple comme étape. Nous avons sur notre interface :

1. Trois boutons : Make new folder - Ok et Cancel.

2. Deux étiquettes : Choose Download Folder et Folder.

3. Un champ de saisie.

4. Une arborescence pour afficher les dossiers.

Au total, sept composants à créer et initialiser. Nous allons commencer par les étiquettes.

JLabel chooseLabel = new JLabel("Choose Download Folder :");
JLabel folderLabel = new JLabel("Folder :");

Continuons avec le champ de saisie.

JTextField folderTextField = new JTextField();

Jusqu'ici, toujours aucun soucis. Continuons avec les boutons. Trois boutons, dont deux ayant les mêmes largeurs.

JButton makeNewFolderButton = new JButton("Make New Folder");
JButton okButton = new JButton("Ok");
JButton cancelButton = new JButton("Cancel");

Pour que les boutons Ok et Cancel ont la même taille, nous devons leur préciser une même taille préférée et minimale. Les tailles du bouton makeNewFolder seront initialisées par Swing.

okButton.setPreferredSize(cancelButton.getPreferredSize());
okButton.setMinimumSize(cancelButton.getMinimumSize());

Il ne nous reste plus que l'arborescence centrale. Le composant JTree étant capable d'afficher une telle arborescence, nous allons l'utiliser dans notre interface. Mais, rappelons-nous que celui-ci doit être placé dans un JScrollPane, pour pouvoir avoir les barres de défilement lorsque celles-ci sont nécessaires.

JTree folderTree = new JTree();
JScrollPane scrollPane = new JScrollPane(folderTree);

Notre deuxième étape est ainsi terminée.

3- Ajout de ces composants en spécifiant les bonnes contraintes.

Maintenant que nous avons créé nos composants et que nous les avons initialisés, nous allons les ajouter un à un à notre container. Nous allons les ajouter du haut vers le bas. Le premier composant à traiter est donc notre étiquette " Choose Download Folder".

Chacune des contraintes est commentée dans le code directement. Prenez donc le temps d'y jeter un coup d'oeil.

Ajout de l'étiquette 'Choose Download Folder'

GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = gbc.gridy = 0; // la grille commence en (0, 0)

gbc.gridwidth = GridBagConstraints.REMAINDER; // seul composant de sa colonne, il est donc le dernier.
gbc.gridheight = 1; // valeur par défaut - peut s'étendre sur une seule ligne.

gbc.anchor = GridBagConstraints.LINE_START; // ou BASELINE_LEADING mais pas WEST.

gbc.insets = new Insets(10, 15, 0, 0); // Marge à gauche de 15 et marge au dessus de 10.
/* - les attributs ipadx, ipdady, weightx et weighty valent tous 0 (valeur par défaut).
* - l'attribut fill est à NONE, car on ne souhaite pas de redimentionnement pour cette étiquette. */
mainFrame.add(chooseLabel, gbc);

Passons ensuite à la zone qui servira d'arborescence.

Ajout de l'étiquette 'Ajout de l'arborescence'.

/* réutilisons le même objet <code>gbc</code>. */
/* positionnons notre composant suivant (notre JScrollPane) sur la ligne suivante. */
gbc.gridx = 0;
gbc.gridy = 1;

/* ce qui suit est inutile, nous avions déjà définie des valeurs pareilles pour le composant précédent.
* cependant, il est toujours bon d'avoir toute les étapes dans un premier exemple. */
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.gridheight = 1; // valeur par défaut.

/* Nous pouvons le voir sur l'image à réaliser. Ce composant s'étend sur tout l'espace qu'il recoit aussi bien
* horizontalement que verticalement.
* Remarquons que c'est souvent le cas pour ce genre de composant de les laissez s'étendre un maximum possible dans
* le container en récupérant l'espace supplémentaire.
*/
gbc.weightx = 1.;
gbc.weighty = 1.;

gbc.fill = GridBagConstraints.BOTH;

gbc.anchor = GridBagConstraints.LINE_START; // pas WEST.

/* Marge à gauche de 15 (gardons la même que précédemment)
* Marge au dessus de 30 et
* Marge à droite de 10. */
gbc.insets = new Insets(30, 15, 0, 10);
mainFrame.add(scrollPane, gbc);

Viens ensuite l'étiquette 'Folder' et une zone de saisie.

Ajout de l'étiquette 'Folder' et de la zone de saisie.

/* le composant suivant à placer est notre étiquette.
* Réutilisons encore le même objet gbc.*/

gbc.gridx = 0;
gbc.gridy = 2;
/* une seule cellule sera disponible pour ce composant. */
gbc.gridwidth = 1;
gbc.gridheight = 1;

/* Nous devons supprimer les poids que nous avons spécifiés précédemment, et supprimer
* le redimentionnement. */
gbc.weightx = 0.;
gbc.weighty = 0.;

gbc.fill = GridBagConstraints.NONE;

/* Maintenant, nous voyons sur notre interface que le composant n'est pas le seul sur sa ligne.
* Un champ de saisie le suit. Pour aligner correctement les étiquettes et les champs de saisie,
* la ligne d'écriture nous facilite le travail. Nous allons l'utiliser ici. */
gbc.anchor = GridBagConstraints.BASELINE_LEADING; // pas LINE_START ni WEST !!

/* Une petite marge autour du composant. Attention à toujours indiquer les mêmes marges à gauche, sinon les
* composants ne sont plus alignés. */
gbc.insets = new Insets(10, 15, 0, 0);
mainFrame.add(folderLabel, gbc);

/* passons au composant suivant: le champ de saisie. */
gbc.gridx = 1; /* une position horizontalement à droite de l'étiquette */
gbc.gridy = 2; /* sur la même ligne que l'étiquette */

gbc.gridwidth = GridBagConstraints.REMAINDER; /* il est le dernier composant de sa ligne. */
gbc.gridheight = 1; /* une seule cellule verticalement suffit */

/* Le composant peut s'étendre sur tout l'espace qui lui est attribué horizontalement. */
gbc.fill = GridBagConstraints.HORIZONTAL;

/* Alignons ce composant sur la même ligne d'écriture que son étiquette. */
gbc.anchor = GridBagConstraints.BASELINE;
/* Une petite marge autour du composant. Remarquons que nous n'avons pas spécifié de marge au dessus du
* composant. Comme nous avons décidé d'alginer ce composant sur la même ligne d'écriture que l'étiquette,
* la marge du haut sera calculée en interne pour s'aligner correctement avec l'étiquette. */
gbc.insets = new Insets(0, 15, 0, 10);
mainFrame.add(folderTextField, gbc);

Il ne nous reste plus que nos boutons.

Ajout de nos boutons.

/* Nous pouvons passé aux boutons. */
gbc.gridy = 3; /* nouvelle ligne */
gbc.gridx = 0; /* première colonne, nous allons placé notre bouton "make new folder" */

/* Reprenons l'image. Nous voyons que le bouton est plus large que l'étiquette située au dessus de lui et
* que le champ de saisie commence avant le bord gauche du bouton. Nous précisons donc deux cellules
* horizontalement.
*/

gbc.gridwidth = 2;
gbc.gridheight = 1; /* une seule cellule verticalement suffit */

/* Nous allons alignerles boutons sur leur ligne d'écriture également.*/
gbc.anchor = GridBagConstraints.BASELINE_LEADING;

/* Aucun redimentionnement possible. Le bouton garde toujours soit sa taille minimum soit préférée. */
gbc.fill = GridBagConstraints.NONE;
/* Les attributs weightx, weighty sont tout deux à 0.*/

/* Une petite marge autour du composant.*/
gbc.insets = new Insets(10, 15, 10, 10);
mainFrame.add(makeNewFolderButton, gbc);

/* bouton suivant. */
/* le bouton précédent peut s'étendre sur deux cellules horizontalement. Celui-ci commence en 2. */
gbc.gridx = 2; /* pour les dubitatifs, gridy vaut toujours 3 ;-) */

gbc.gridwidth = GridBagConstraints.RELATIVE; // le bouton est l'avant dernier composant de sa ligne.

/* nous allons, sur ce bouton, définir un poids pour que celui-ci s'éloigne le plus du bouton précédent.*/
gbc.weightx = 1.;
gbc.anchor = GridBagConstraints.BASELINE_TRAILING; // Pas LINE_END, ni EAST.
gbc.insets = new Insets(0, 0, 0, 0);
mainFrame.add(okButton, gbc);

/* notre dernier bouton. */
gbc.gridx = 3;
gbc.weightx = 0.; /* remettons le poids à zéro. */
gbc.insets = new Insets(0, 3, 0, 10);
mainFrame.add(cancelButton, gbc);

Une fois terminé, voici à quoi ressemble notre interface.

4 - Quelques problèmes courants.

4-1 - Problème numéro 1: Oubli de spécifier des poids.

J'ai développé toute mon interface mais mes composants restent centrés dans ma fenêtre.
Vous avez probablement oublié de définir les poids weightx et weighty. En effet, voyons en images le problème (fenêtre new bookmark dans firefox).

Lorsque nous redimensionnons cette fenêtre, nous pouvons voir que tous les composants restent centrés. Cela est dû au fait que nous n'avons spécifié aucune contrainte de poids pour distribuer l'espace supplémentaire du container parmi les composants.

Voila comment, dans cet exemple, nous allons distribuer l'espace supplémentaire. Tous les champs seront étalés sur toute la largeur du container (nous laisserons tout de même une petite marge à droite). La zone description elle sera étendue aussi bien horizontalement que verticalement. C'est cette zone qui se chargera de récupérer l'espace supplémentaire du container. Sa contrainte devra donc avoir des poids weightx et weighty de 1 et un fill sur BOTH. Les boutons quant à eux seront alignés à droite et ne seront pas redimensionnés. Appliquons ces modifications à notre code et observons le résultat.

4-2 - Problème numéro 2: Oubli de spécifier les tailles préférées et minimales.

J'essaye d'utiliser le GridBagLayout et voici à quoi ressemble mon interface :

Quel est le problème et comment m'en sortir ?

Le problème est assez simple à comprendre. Par défaut, les tailles préférées (et minimums) des zones de saisies (JTextField) sont nulles. Donc, lorsqu'elles sont ajoutées à un container utilisant un GridBagLayout, si nous n'avons pas spécifié de tailles nous-mêmes, vous aurez le résultat présenté sur l'image précédente. Le problème est identique pour la zone description.
Pour résoudre le problème, nous pouvons soit spécifier des tailles minimales et préférées sur chacun de nos composants, soit définir une politique de redimensionnement (propriétés fill, weightx et weighty d'un objet GridBagConstraints). C'est-à-dire permettre aux composants de s'étendre sur l'espace qu'il leur est possible. Cette seconde solution est préférable à la première car elle s'adapte lorsque la taille du container est modifiée. C'est celle que nous avons implémentée ici.

4-3 - Problème numéro 3: Un gestionnaire de placement vertical.

Comment je peux faire pour aligner mes composants verticalement ? Je ne trouve pas de gestionnaire de placement qui me permette d'aligner des composants en colonne. Est-ce possible avec un GridBagLayout ?

Tout d'abord, il faut savoir que plusieurs gestionnaires de placements permettent d'aligner des composants en colonne (BoxLayout, SpringLayout). Mais il est également possible d'utiliser un GridBagLayout. Nous allons voir ici comment utiliser le GridBagLayout à cette fin, mais nous ne comparerons pas avec d'autres gestionnaires de placements.

Prenons un exemple de code

/* 2- Création et initialisation d'une série de composants. */
String sentence = "Le GridBagLayout est un gestionnaire de placement";
String[] words = sentence.split(" ");
JButton[] buttons = new JButton[words.length];
for (int i = 0; i < words.length; i++) {
buttons[i] = new JButton(words[i]);
}

/*3- Ajout de ces composants en spécifiant les contraintes de type GridBagConstraints. */
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = GridBagConstraints.RELATIVE;

for (JButton button : buttons) {
mainFrame.add(button, gbc);
}

Dans ce code, nous avons fixé la position gridx en 0 et comme nous avons spécifié un gridy sur GridBagConstraint.RELATIVE, à chaque ajout de composant, la position gridy sera incrémentée de 1 par rapport à la valeur précédente. Lorsque nous exécutons ce code, le résultat que nous aurons sera celui-ci :

Nous pouvons déjà y voir une colonne de boutons. Voyons maintenant comment nous pouvons remanier cette colonne pour correspondre à nos besoins. N'ayant pas de besoins particuliers, nous allons voir une série d'exemples courants.

Premièrement, pour agrandir l'ensemble des composants à la taille préférée du plus grand, nous pouvons modifier notre contrainte en spécifiant une propriété fill de GridBagConstraint.HORIZONTAL.

Deuxièmement, nous pouvons aussi mettre un poids (weightx de 1.) pour que ces composants s'étalent sur toute la largeur du container.

Troisièmement, nous pouvons distribuer l'espace supplémentaire verticalement parmi les composants soit de manière équitable (image suivante), soit de manière inégale.

Voila, nous avons rapidement fait une colonne de boutons et présenté quelques cas différents en modifiant la contrainte. Le GridBagLayout n'est pas limité qu'à ces cas. Sachez qu'il est également possible de jouer sur la taille des composants, de spécifier des marges, de définir ou non un redimensionnement... Nous vous invitons donc à lire le début du tutoriel.

5 - Conclusion.

Nous venons de voir le fonctionnement du GridBagLayout, un gestionnaire de placement vraiment très flexible qui a la réputation d'être trop complexe à comprendre et à utiliser. Nous pensons que, pris petit à petit, ce gestionnaire de placement est accessible à tous et ne devrait pas poser trop de difficultés.

Par contre, nous sommes bien conscients de ses défauts. L'un de ses plus gros défauts est qu'il ne respecte pas par défaut les UI guidelines de la plateforme (Windows XP, Apple Max OS X). Pour s'y adapter, et maintenir la portabilité, il faudrait énormément de temps. De plus, comme nous pouvons préciser des marges en pixels, les interfaces utilisant un GridBagLayout peuvent difficilement agir sur les proportions. Mais rappelons-nous que le GridBagLayout a plusieurs années de vie derrière lui, qu'il a donc été longuement utilisé et qu'il est encore intéressant de bien le connaitre.

C'est pour toute une série de défauts du GridBagLayout que de nouveaux gestionnaires de placement émergent petit à petit qui d'une manière ou d'une autre apporte une réponse à certains de ses problèmes. Comme par exemple, le GroupLayout, récemment ajouté à l'API Java, permet de résoudre un des problèmes majeurs du GridBagLayout, à savoir, l'espacement entre les composants. Le GridBagLayout permet de spécifier des marges en pixels, mais ces marges font partie des UI guidelines de la plateforme. Par exemple, il se pourrait que les marges entre une étiquette et un champ de saisie soit de 10 pixels lorsque nous sommes sous Windows et soit de 8 pixels sous Mac OS X. Le GroupLayout se charge donc de calculer ces marges pour correspondre à la plateforme.

De plus, le GridBagLayout est incapable d'assurer une taille minimum à un composant. Si la taille du container diminue plus que nécessaire à attribuer à chacun des composants leurs tailles minimales, les composants peuvent encore être rapetissés. Ce comportement n'est souvent pas souhaité. Le BoxLayout apporte une solution à ce genre de problème.

Pour terminer, nous voulons juste préciser que le but de ce tutoriel était d'expliquer le fonctionnement du GridBagLayout. Nous reviendrons une prochaine fois sur d'autres gestionnaires de placement et sur leur utilité. Mais sachez que toute remarque, un tant soi peu constructive, est la bienvenue pour améliorer, corriger ou débattre du contenu de ce tutoriel.

Références.

Pour plus d'informations sur le GridBagLayout, rendez-vous sur le site de référence. Pour tout autres questions, nous vous invitons à les poser sur le le forum.

Remerciements.

Je tiens à remercier toute l'équipe Java de developpez.com pour m'avoir aidé à terminer ce premier tutoriel, et plus particulièrement wichtounet pour toutes ses relectures et ses bons conseils.