Le bloc notes Java

Le blog note Java

Maîtriser le GridBagLayout

Le GridBagLayout est assez puissant pour convenir à 95% des besoins. Pourtant il est souvent décrit comme difficile à utiliser.
Avec un peu de méthode, le GridBagLayout est facile à utiliser. Pourquoi s'en passer ?

L'API de base

Je part du principe que vous avez déjà parcouru la documentation standard du GridBagLayout.
Ceci n'est qu'un rappel du principe de base.

Pour utiliser le GridBagLayout sur un panel par exemple, il faut ajouter les composants au panel en leur associant à chacun une contrainte du type GridBagConstraints.
Pour construire ces contraintes, il ne faut pas moins de 11 paramètres, ce qui rend le code particulièrement illisible et explique pour beaucoup le manque de succès de ce layout.

La classe GridBagConstraints contient deux sortes d'informations:
gridx Position en x dans la grille (colonne)
gridyPosition en y dans la grille (ligne)
gridwidthNombre de colonnes occupées par le composant (typiquement 1)
gridheightNombre de lignes occupées par le composant (typiquement 1)
weightx Si la grille est plus large que l'espace demandé, cet espace est distribué en proportion des valeurs de weightx des différentes colonnes.
La valeur zéro indique que la cellule ne veut pas recevoir plus d'espace.
weightySi la grille est plus haute que l'espace demandé, cet espace est distribué en proportion des valeurs de weighty des différentes lignes.
La valeur zéro indique que la cellule ne veut pas recevoir plus d'espace.
anchorAncrage du composants dans la cellule:

FIRST_LINE_START...PAGE_START.....FIRST_LINE_END
LINE_START...........CENTER.............LINE_END
LAST_LINE_START.....PAGE_END.......LAST_LINE_END

fillRemplissage si la cellule est plus grande que le composant:
NONE, HORIZONTAL, VERTICAL, BOTH
insetsEspace autour du composant. Voir Insets. S'ajoute aux espacements définis pas les propriétés ipadx et ipady ci-dessous.
ipadxEspacement à gauche et à droite autour du composant.
ipadyEspacement au dessus et au dessous du composant.

Je ne conseille pas de suivre la façon de coder donnée dans les exemples de Sun et d'autres.
La même définition de contraintes est utilisée successivement pour ajouter tous les composants en faisant des modifications dessus à mesure des ajouts. Le gain en vitesse est négligeable et c'est le meilleur moyen de ce tromper et de provoquer des effets de bord.
Utiliser des objets immuables est une pattern de design bien connue. Il vaut mieux créer une fois pour toute chacune des contraintes et les rendre ainsi indépendantes.

Les 2 classes d'aide à la création d'un layout

Les informations de position sont propres à chaque composants. Par contre le style de mise en forme peut être partagé par plusieurs composants et gagne donc à être isolé dans un objet.
Voici deux petites classes pour aider à construire un layout:
Pour ajouter un composant au layout, on fait appel à l'une des méthodes add() du XGridBag.

La classe XGridBag:
package exemple; /* Ce code est dans le domain public */

import java.awt.Component;
import java.awt.Container;
import java.awt.GridBagConstraints;

/**
 * The <code>XGridBag</code> helps to use the GridBagConstraints.<br>
 * This class defines styles reusable by differents components in a grid.
 *
 * To create a layout do the following step:<br>
 * - create a <code>GridBagLayout</code> and a XGridBag</code>.<br>
 * - prepare the components size (min/prefered/max).<br>
 * - create one or more style using <code>CellStyle.getStyle</code> method<br>
 * - add components to the grid using the <code>XGridBag.add</code> method.<br>
 * <br>
 * Example:<br>
 * <code>
 *   GridBagLayout layout = new GridBagLayout();<br>
 *   panel.setLayout(layout);<br>
 *   XGridBag grid = new XGridBag(panel);<br>
 *   <br>
 *   CellStyle style1 = new CellStyle(1.0, 0.0,<br>
 *      GridBagConstraints.WEST, GridBagConstraints.VERTICAL, insets, 4, 0);<br>
 *   ...<br>
 *   grid.add(comp1, style1, 0, 0);<br>
 *   grid.add(comp2, style1, 0, 1);<br>
 *   grid.add(comp3, style2, 1, 0);<br>
 *   ..<br>
 * </code>
 * @see CellStyle
 * @see java.awt.GridBagConstraints
 * @see java.awt.Insets
 */
public class XGridBag extends GridBagConstraints {

    public Container container;

    /**
     * Add a component to the container.
     * The component occupy only one cell width and heigth.
     *
     * @param c component to add
     * @param style cell style
     * @param row first Y cell
     * @param col first X cell
     * @see CellStyle
     */
    public void add(Component c, CellStyle style, int row, int col) {
        add(c, style, row, col, 1, 1);
    }

    /**
     * Add a component to the container.
     *
     * @param c component to add
     * @param style cell style
     * @param row first Y cell
     * @param col first X cell
     * @param rowHeight number of cells in a column (use GridBagConstraints.REMAINDER to fill out all rows to the end).
     * @param colWidth number of cells in a row (use GridBagConstraints.REMAINDER to fill out all columns to the end).
     * @see CellStyle
     */
    public void add(Component c, CellStyle style, int row, int col,
        int rowHeight, int colWidth) {
        this.gridx = col;
        this.gridy = row;
        this.gridwidth = colWidth;
        this.gridheight = rowHeight;
        this.fill = style.fill;
        this.ipadx = style.ipadx;
        this.ipady = style.ipady;
        this.insets = style.insets;
        this.anchor = style.anchor;
        this.weightx = style.weightx;
        this.weighty = style.weighty;
        container.add(c, this);
    }

    /**
     * XGridBag constructor.
     * @param container
     */
    public XGridBag(Container container) {
        super();
        this.container = container;
    }

}

La classe CellStyle:
package exemple; /* Ce code est dans le domain public */

import java.awt.Insets;

/**
 * Define a cell style used by XGridBag.
 *
 * @see XGridBag
 * @see java.awt.GridBagConstraints
 */
public class CellStyle {

    /**
     * anchor CENTER, NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST, NORTHWEST.
     */
    int anchor;

    /**
     * fill NONE, HORIZONTAL, VERTICAL, BOTH
     */
    int fill;

    /**
     * insets (int top, int left, int bottom, int right)
     */
    Insets insets;

    /**
     * internal X padding
     */
    int ipadx;

    /**
     * internal Y padding
     */
    int ipady;

    /**
     * extra X space (none=0.0 ... all=1.0)
     */
    double weightx;

    /**
     * extra Y space (none=0.0 ... all=1.0)
     */
    double weighty;

    /**
     * CellStyle constructor.
     *
     * @param weightx extra X space usage (none=0.0 ... all=1.0)
     * @param weighty extra Y space usage (none=0.0 ... all=1.0)
     * @param anchor location if component doesn't occupy entire cell:
     * <br><code>
     * --------------------------------------------------<br>
     * |FIRST_LINE_START...PAGE_START.....FIRST_LINE_END|<br>
     * |LINE_START...........CENTER.............LINE_END|<br>
     * |LAST_LINE_START.....PAGE_END.......LAST_LINE_END|<br>
     * --------------------------------------------------<br>
     * <code>
     * @param fill NONE, HORIZONTAL, VERTICAL, BOTH
     * @param insets (int top, int left, int bottom, int right)
     * @param ipadx internal X padding
     * @param ipady internal Y padding
     *
     * @see XGridBag
     * @see java.awt.GridBagConstraints
     * @see java.awt.Insets
     */
    public CellStyle(double weightx, double weighty,
        int anchor, int fill, Insets insets, int ipadx, int ipady) {
        super();
        this.fill = fill;
        this.ipadx = ipadx;
        this.ipady = ipady;
        this.insets = insets;
        this.anchor = anchor;
        this.weightx = weightx;
        this.weighty = weighty;
    }

}

La procédure à suivre pour coder le layout

Les deux classes ci-dessus sont très simples mais elles ne suffisent pas à coder un layout sans difficultés.
Ce qui manque, c'est la procédure d'utilisation, la voici:
  1. Dessiner le layout sur une feuille de papier et numéroter les lignes et les colonnes.
  2. Créer si besoin les objets d'espacement (Insets) qui entourerons les divers composants.
  3. Créer le layout et tous les composants.
  4. Certain composants peuvent réclamer un dimensionnement fixe et ont une taille variable (JTextField...).
    Leur donner une largeur et/ou une hauteur fixe avec les méthodes setPreferredSize/setMinimumSize/setMaximumSize.
  5. Créer les styles utilisés en commun par les différentes cellules.
  6. Ajouter chaque composant au container en indiquant son style et sa position avec XGridBag.add().

Un exemple d'utilisation

Cet exemple de formulaire rassemble plusieurs pièges courants pour un layout:
Essayez de commenter diverses lignes de code et de repérer l'effet associé.

Un exemple de layout

package exemple; /* Ce code est dans le domain public */

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;

import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;

import org.argil.swing.CellStyle;
import org.argil.swing.XGridBag;

public class DemoGridBag {

    public static void main(String[] args) throws Exception {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager
                        .getSystemLookAndFeelClassName());
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                DemoGridBag demo = new DemoGridBag();
                JFrame frame = new JFrame("Formulaire");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                demo.createLayout(frame);
                frame.setLocationByPlatform(true);
                frame.pack();
                frame.setVisible(true);
            }
        });
    }

    JLabel lbhPresentation = new JLabel(
        "<html><b>Demande d'inscription au stage de fin d'étude:</b>"
            + "<dl><dd>Remplir ce formulaire avant le 10 mai."
            + " Préciser la zone géographique.");

    JLabel lbhSujet = new JLabel("Sujet du stage:");

    JLabel lbNom = new JLabel("Nom:");

    JLabel lbPrenom = new JLabel("Prénom:");

    JLabel lbAge = new JLabel("Age:");

    JTextField tfSujet = new JTextField("");

    JSpinner spinnerAge = new JSpinner();

    JComboBox boxSexe = new JComboBox(new Object[] {
        "Masculin", "Féminin"
    });

    JLabel lbSexe = new JLabel("Sexe:");

    JTextField tfNom = new JTextField("Dupond");

    JTextField tfPrenom = new JTextField("Pierre");

    JTextArea txtSujet = new JTextArea(
        "Réaliser un logiciel graphique de calcul de contraintes mécaniques en langage Java.\n"
            + "De préférence au sud ouest de Paris.");

    JScrollPane txtPane = new JScrollPane(txtSujet,
        ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
        ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);

    private void createLayout(JFrame frame) {
        // Créer le layout
        JPanel panel = (JPanel) frame.getContentPane();
        panel.setBorder(new EmptyBorder(6, 6, 6, 6));
        GridBagLayout layout = new GridBagLayout();
        panel.setLayout(layout);

        // Créer les inserts (insets)
        Insets cellInsets = new Insets(2, 2, 2, 2);
        Insets cellInsets2 = new Insets(2, 12, 2, 2);

        // Préparer les composants
        Dimension dimSpinner = spinnerAge.getPreferredSize();
        Dimension dimBox = boxSexe.getPreferredSize();
        int maxH = Math.max(dimSpinner.height, dimBox.height);
        setHeigth(spinnerAge, maxH);
        setWidth(spinnerAge, 40);
        setHeigth(boxSexe, maxH);
        setWidth(boxSexe, 80);

        txtSujet.setRows(3);
        txtSujet.setLineWrap(true);
        txtSujet.setWrapStyleWord(true);

        // Créer les styles de cellule
        CellStyle titleStyle = new CellStyle(0.0, 0.0,
            GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH,
            new Insets(12, 2, 2, 2), 2, 0);
        CellStyle fixStyle = new CellStyle(0.0, 0.0,
            GridBagConstraints.LINE_START, GridBagConstraints.NONE, cellInsets,
            2, 4);
        CellStyle fixStyle2 = new CellStyle(0.0, 0.0,
            GridBagConstraints.LINE_START, GridBagConstraints.NONE, cellInsets2,
            2, 4);
        CellStyle varHStyle = new CellStyle(0.0, 0.0,
            GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL,
            cellInsets, 2, 0);
        CellStyle varHVStyle = new CellStyle(1.0, 1.0,
            GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH,
            cellInsets, 2, 0);

        // Ajouter les composants
        XGridBag g = new XGridBag(panel);
        // Row 0
        g.add(lbhPresentation, titleStyle, 0, 0, 1, 5);
        // Row 1
        g.add(lbNom, fixStyle, 1, 0);
        g.add(tfNom, varHStyle, 1, 1, 1, 4);
        // Row 2
        g.add(lbPrenom, fixStyle, 2, 0);
        g.add(tfPrenom, varHStyle, 2, 1, 1, 4);
        // Row 3
        g.add(lbAge, fixStyle, 3, 0);
        g.add(spinnerAge, fixStyle, 3, 1);
        g.add(lbSexe, fixStyle2, 3, 2);
        g.add(boxSexe, fixStyle, 3, 3);
        // Row 4
        g.add(lbhSujet, titleStyle, 4, 0, 1, 5);
        // Row 5
        g.add(txtPane, varHVStyle, 5, 0, 1, 5);
    }

    private static void setHeigth(JComponent c, int h) {
        Dimension dim = c.getPreferredSize();
        dim.height = h;
        c.setPreferredSize(dim);
        c.setMinimumSize(dim);
        c.setMaximumSize(dim);
    }

    private static void setWidth(JComponent c, int w) {
        Dimension dim = c.getPreferredSize();
        System.out.println(dim);
        dim.width = w;
        c.setPreferredSize(dim);
        c.setMinimumSize(dim);
        c.setMaximumSize(dim);
    }
}

En savoir plus sur le sujet

sujet

liens

Un tutorial de Borland An introduction to GridBagLayout concepts
Using GridBagLayout - part 1, part 2part 3.
Le tutorial Java de Sun How to Use GridBagLayout
Quelques exemples
Java Source and Support
Un comparatif avec le TableLayout par Sun GridBagLayout Versus TableLayout

Le bilan

Le GridBagLayout est supporté par les IDE comme Eclipse et Netbeans.
Il est possible d'utiliser ces éditeurs visuels mais cela s'avère plus difficile que par codage direct.
L'éditeur n'assure pas la mise en facteur des styles, des insets ni la mise à la même taille des composants. Le résultat est moins précis et moins lisible. Toutefois, l'éditeur visuel permet de réaliser une maquette rapidement avant de la compléter à la main.

Le GridBagLayout vaut bien l'apprentissage qu'il demande. Une fois maîtrisé, il permet de coder de façon sûre presque toutes les interfaces utilisateur.

Ce site est mis à disposition sous licence Creative Commons. Copyright © 2004,2005 Louis Cova. Ecrivez-moi: