Rechercher
 

Utilisation de stratégies de positionnement
(layout managers)

Gestions d'événements Le Garbage Collector



Accès rapide :
   Affecter une stratégie de placement à un conteneur
   La stratégie java.awt.FlowLayout
   La stratégie java.awt.BorderLayout
   La stratégie java.awt.GridLayout
   La stratégie java.awt.GridBagLayout
   La stratégie java.awt.CardLayout
   La stratégie javax.swing.BoxLayout

Une difficulté classiquement rencontrée lors de la mise en oeuvre d'interfaces graphiques réside dans le fait de correctement positionner les différents composants graphiques dans une zone afin d'obtenir une sensation d'équilibre dans l'IHM (l'interface graphique). Si au contraire les éléments graphiques sont trop rammassés dans un coin de l'IHM, l'estétisme de l'interface risque (peut être) de rebuter les utilisateurs. De même, il faudrait, lorsque l'on retaille la fenêtre, que les composants qu'elle contient soient repositionnés et retaillés harmonieusement.

Pour répondre à ces besoins, et pour automatiser les calculs de positionnement et de retaillage, les librairies AWT et Swing utilisent le concept commun de "layout manager" (gestionnaires de positionnement : personnellement, je préférerai parler de stratégies de positionnement). Il existe plusieurs classes de layout managers, chacune d'elle possédant sa propre logique de positionnement des éléments (positionnement en lignes, en grille, ...).

En fait, il existe très peu de layout manager : 5 classes, plus quelques unes introduites avec Swing. Pour les cinq premières, sachez qu'elles sont contenues dans le package java.awt. Les dernières se trouvent dans le package javax.swing. Nous allons, dans ce chapitre, étudier uniquement que les stratégies proposées par l'AWT (mais, j'insiste, qui restent utilisables/utilisées en Swing).

Affecter une stratégie de placement à un conteneur

La première chose à savoir, est qu'une stratégie de placement se définie au niveau d'un conteneur (objet de type java.awt.Container). N'oubliez pas que toutes les classes Swing dérivent (directement ou indirectement de java.awt.Container. Chaque type de conteneur utilise une stratégie par défaut : par exemple une fenêtre Swing possège un conteneur (accessible via la méthode getContentPane) qui par défaut utilise une stratégie de type java.awt.BorderLayout (nous allons la présenter dans quelques instants).

Vous pouvez, aussi, ne pas utiliser de stratégie de positionnement sur un conteneur : dans ce cas, vous devrez systématiquement positionner vos composants au pixels près. De même il vous faudra contrôller la taille de vos composants au pixels près. Tant que possible, essayer de ne pas utiliser cette technique, car elle est très gourmande en termes de nombre de lignes de code. Néanmoins, dans certaint cas, vous serez ammené à utiliser cette possibilité.

Bien entendu, une stratégie de placement se définie en créant une instance de l'une des classes de stratégie de placement. Une fois cet objet instancié, il ne reste plus qu'à l'affecter à un conteneur. Pour ce faire, on utilise la méthode setLayout. Elle prend un unique paramètre : le LayoutManager. Si vous passez la valeur null en paramètre, aucune stratégie de placement ne sera utilisée : il faut alors positionner au en absolu vos composants.

La stratégie java.awt.FlowLayout

Cette première stratégie est certainement la plus simple à manipuler. Si vous l'affectez à un conteneur, celui-ci va tenter de mettre le plus de composants possible sur une ligne. Dès que la ligne est pleine (en fonction de la taille du conteneur), le layout positionnera les prochains éléments sur une nouvelle ligne, et ainsi de suite.

Vous pouvez de plus configurer votre layout en spécifiant quel type d'alignement vous souhaitez utiliser. Trois possibilité vous sont offertent : alignement par la gauche, par la droite, ou bien centré. Toutes les lignes de composants utiliseront alors cette configuration. Cette configuration peut être réalisée soit à la construction de la stratégie de positionnement, soit par la suite en utilisant la méthode setAlignment. Des attributs (constants) de la classe FlowLayout sont préfédinis : FlowLayout.LEFT, FlowLayout.CENTER, FlowLayout.RIGHT.

A titre d'exemple, considérons le programme suivant : il créer trois boutons est demande de les placer dans une fenêtre. Pour mettre en évidence le rôle de la stratégie de placement, il suffit de retailler la fenêtre. Notez qu'il est possible de contrôller l'alignement de la stratégie de placement en cliquant sur l'un des trois boutons. Cliquez sur le bouton "Start Demo" pour lancer l'exemple au travers de "Java Web Start".

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;

public class FlowLayoutSample extends JFrame implements ActionListener {
    
    private JButton btnLeft = new JButton( "Set to left alignment" );
    private JButton btnCenter = new JButton( "Set to center alignment" );
    private JButton btnRight = new JButton( "Set to right alignment" );
    private JPanel pnlMain = null;
    
    public FlowLayoutSample() {
        super( "FlowLayout sample" );
        
        this.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
        
        pnlMain = (JPanel) this.getContentPane();
        pnlMain.setLayout( new FlowLayout( FlowLayout.CENTER ) );
        
        
        this.btnLeft.addActionListener( this );
        pnlMain.add( this.btnLeft );
        
        this.btnCenter.addActionListener( this );
        pnlMain.add( this.btnCenter );

        this.btnRight.addActionListener( this );
        pnlMain.add( this.btnRight );
        
        this.setSize( 300, 200 );
        this.setLocationRelativeTo( null );
    }
    
    @Override public void actionPerformed( ActionEvent event ) {
        if ( event.getSource() == btnLeft ) {
            this.pnlMain.setLayout( new FlowLayout( FlowLayout.LEFT ) );
        } else if ( event.getSource() == btnCenter ) {
            this.pnlMain.setLayout( new FlowLayout( FlowLayout.CENTER ) );
        } else {
            this.pnlMain.setLayout( new FlowLayout( FlowLayout.RIGHT ) );
        }
        
        this.pnlMain.revalidate();
    }
    
    public static void main( String[] args ) throws Exception {
        
        // Try to set Nimbus look and feel
        for ( LookAndFeelInfo info : UIManager.getInstalledLookAndFeels() ) {
            if ( "Nimbus".equals( info.getName() ) ) {
                UIManager.setLookAndFeel( info.getClassName() );
                break;
             }
        }

        // Start the demo
        new FlowLayoutSample().setVisible( true );
    }
}

La stratégie java.awt.BorderLayout

Cette seconde stratégie permet d'opérer une division de l'espace utilisable au sein d'un conteneur en cinq zones. Quatre de ces cinq zones sont placés de chaque côté du conteneur (BorderLayout.NORTH, BorderLayout.CENTER, BorderLayout.EAST et BorderLayout.SOUTH). La cinquième zone est placée au centre du conteneur (BorderLayout.WEST) et est entourée par les quatre autres.

Vous n'êtes pas obligé d'utiliser toutes les zones proposées. Si aucun composant n'est placé dans les zones périphériques, alors celles-ci ne prendront pas de place. Ainsi, si les quatre zones latérales ne sont pas utilisées, seule la zone centrale sera visualisée. Notez aussi que vous ne pouvez placer qu'un unique composant par zone, mais rien n'empêche que certains de ces composants soient eux mêmes des containers (comme dans l'exemple présenté ci-dessous, ou une barre d'outils est placée au nord du conteneur). Voici donc un petit exemple d'utilisation de la stratégie BorderLayout. Il vous est possible de la lancer directement via "Java Web Start", en cliquant sur le bouton "Start Demo".

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
import java.awt.BorderLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;

public class BorderLayoutSample extends JFrame {
        
    
    public BorderLayoutSample() {
        super( "BorderLayout sample" );
        
        this.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
        
        JPanel pnlMain = (JPanel) this.getContentPane();
        pnlMain.setLayout( new BorderLayout() );
        
        JToolBar toolbar = new JToolBar();
        toolbar.add( new JButton( "First" ) );
        toolbar.add( new JButton( "Second" ) );
        toolbar.add( new JButton( "Third" ) );
        
        pnlMain.add( toolbar, BorderLayout.NORTH );
        pnlMain.add( new JTree(), BorderLayout.WEST );
        pnlMain.add( new JTextArea(), BorderLayout.CENTER );
        pnlMain.add( new JTree(), BorderLayout.EAST );
        pnlMain.add( new JLabel( "Status bar" ), BorderLayout.SOUTH );
        
        this.setSize( 500, 360 );
        this.setLocationRelativeTo( null );
    }
    
    
    public static void main( String[] args ) throws Exception {
        
        // Try to set Nimbus look and feel
        for ( LookAndFeelInfo info : UIManager.getInstalledLookAndFeels() ) {
            if ( "Nimbus".equals( info.getName() ) ) {
                UIManager.setLookAndFeel( info.getClassName() );
                break;
             }
        }

        // Start the demo
        new BorderLayoutSample().setVisible( true );
    }
}

Si vous avez été curieux, vous avez du retailler la fenêtre. Si cela n'a pas encore été le cas, faîtes le ! Que constatez-vous ? Et oui, en effet, chacun des composants se retaille aussi pour occuper 100% de la superficie de la zone qui lui est associée. Les zones de saisie de texte et le bouton deviennent difforme. A priori, vous pourriez penser qu'une telle stratégie n'est pas utilisable. Détrompez-vous ! Au contraire, elle peut être fort utile, mais à une condition : placez y des conteneurs. Ainsi, sur chaque conteneur, vous définissez à nouveau une stratégie de placement, et groupe de composants par groupe, vous définissez la structure de votre interface. A titre d'exemple, étudiez le code suivant.

import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class Layout3 extends Applet {
    Image startImage = null;
    Frame f = null;

    public void init() {
        try {
            MediaTracker mt = new MediaTracker(this);
            startImage = this.getImage(this.getCodeBase(),"start.gif");
            mt.addImage(startImage,0);

            this.addMouseListener(new MouseAdapter() {
                public void mouseClicked(MouseEvent e) {
                    showWindow();
                }
            });
            
            mt.waitForAll();
        } catch(Exception e) {}
    }

    public void stop() {
        if (f != null && f.isVisible()) {
            f.setVisible(false);
        }
    }

    public void paint(Graphics gc) {
        gc.drawImage(startImage,0,0,null);
    }

    public void showWindow() {
        f = new Frame("Ma fenetre");
        f.setLayout(new BorderLayout());

        Panel panel1 = new Panel();
        panel1.setLayout(new FlowLayout());
        for(int i=0;i<5;i++) panel1.add(new Button("Button H"+i));

        Panel panel2 = new Panel();
        panel2.setLayout(new GridLayout(-1,1));
        for(int i=0;i<5;i++) panel2.add(new Button("Button V"+i));

        f.add(panel1,BorderLayout.NORTH);
        f.add(panel2,BorderLayout.EAST);
        f.add(new Button("Un boutton"),BorderLayout.CENTER);

        f.pack();
        f.setVisible(true);

        f.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent event) {
                f.setVisible(false);
            }
        });
    }
}

En fait cette stratégie de placement a été pensée pour permettre de définir les différentes zones standards d'une fenêtre d'application. Au "nord", on trouve normalement une barre d'outils (notez que la barre de menu se trouve en dehors du conteneur de la fenêtre). Au "sud" on trouve souvent une barre de status (si vous voulez faire les choses simplement, placez-y un label). A "l'ouest", on trouve souvent une arborescence. La partie "est" n'est pas toujours utilisée. Au "centre" placez-y un nouveau conteneur et travaillez dedans.

La stratégie java.awt.GridLayout

Nous allons maintenant parler d'une autre stratégie de placement. Celle-ci permet de disposer vos composants dans une grille constituée de lignes et de colonnes. Notez que nous avons déjà testé cette stratégie dans le dernier exemple de la stratégie précédente (la petit barre de boutons dans la zone de droite du BorderLayout).

Donc, comme dit précédemment, le but de cette stratégie est de découper l'espace d'un conteneur un une grille. Pour ce faire, le constructeur de la classe GridLayout peut accepter deux paramètres : le nombre de lignes et le nombre de colonnes. En fait, cette classe accepte d'autre constructeurs, dont voici leurs prototypes.

GridLayout();
GridLayout(int rows,int cols);
GridLayout(int rows,int cols,int hgap, int vgap);

Notez un détail fort utile : si vous passez une valeur inférieure ou égale à zéro, en terme de nombre de lignes (ou de colonnes), alors le gestionnaire de positionnement acceptera un nombre quelconque de lignes (ou de colonnes). Dans certains cas, cela simplifie le maintient et l'évolution de votre code.

Un premier exemple simple

Nous allons commencer par un exemple simple d'utilisation de ce gestionnaire de positionnement. Par la suite nous améliorerons le tout. Dans cet exemple, nous allons placer six boutons dans une grille de trois lignes par deux colonnes. Afin de bien comprendre le fonctionnement de ce layout, il est conseiller de retailler la fenêtre.

Pour ajouter les composants dans le conteneur, nous utilisons toujours la méthode add à laquelle nous passons nos composants en paramètres. Notez qu'elle ne prend pas de second paramètre. En effet cette stratégie de positionnement cherche à remplir d'abord la première ligne de la gauche vers la droite, puis passe ensuite à la ligne suivante, et ainsi de suite.

import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class GridLayout1 extends Applet {
    Image startImage = null;
    Frame f = null;

    public void init() {
        try {
            MediaTracker mt = new MediaTracker(this);
            startImage = this.getImage(this.getCodeBase(),"start.gif");
            mt.addImage(startImage,0);

            this.addMouseListener(new MouseAdapter() {
                public void mouseClicked(MouseEvent e) {
                    showWindow();
                }
            });
            
            mt.waitForAll();
        } catch(Exception e) {}
    }

    public void stop() {
        if (f != null && f.isVisible()) {
            f.setVisible(false);
        }
    }

    public void paint(Graphics gc) {
        gc.drawImage(startImage,0,0,null);
    }

    public void showWindow() {
        f = new Frame("Ma fenetre");
        f.setLayout(new GridLayout(3,2));

        for(int i=0;i<6;i++)
            f.add(new Button("Bouton" + i));

        f.pack();
        f.setVisible(true);

        f.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent event) {
                f.setVisible(false);
            }
        });
    }
}

Qu'avez-vous constaté ? Et oui, le composant placé dans les cellules de la grille cherche a en occuper tout l'espace, ce qui n'est pas toujours du plus bel effet.

Embellissons un peu les choses

Pour rendre l'exemple précédent un peu plus agréable à visionner, il y a plusieurs solutions envisageables. Une seule l'est si l'on souhaite garder le gestionnaire GridLayout. Elle consiste à mettre des espaces de séparation entre chaque composant. Pour ce faire, il vous suffit de donner des valeurs à deux attributs de votre gestionnaire (soit à la construction, soit par les méthodes d'accès). Ces attributs fixent les espaces pour les séparation horizontales et celles verticales.

L'exemple suivant utilise cette technique. Le résultat est initialement plus agréable, mais si vous cherchez à retailler la fenêtre, vous observerez toujours un comportement d'adaptation de la superficie de composant à celle de la cellule. Les séparations sont malgré tout conservées.

import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class GridLayout2 extends Applet {
    Image startImage = null;
    Frame f = null;

    public void init() {
        try {
            MediaTracker mt = new MediaTracker(this);
            startImage = this.getImage(this.getCodeBase(),"start.gif");
            mt.addImage(startImage,0);

            this.addMouseListener(new MouseAdapter() {
                public void mouseClicked(MouseEvent e) {
                    showWindow();
                }
            });
            
            mt.waitForAll();
        } catch(Exception e) {}
    }

    public void stop() {
        if (f != null && f.isVisible()) {
            f.setVisible(false);
        }
    }

    public void paint(Graphics gc) {
        gc.drawImage(startImage,0,0,null);
    }

    public void showWindow() {
        f = new Frame("Ma fenetre");
	GridLayout layout = new GridLayout(3,2);
	layout.setHgap(10);
	layout.setVgap(10);

        f.setLayout(layout);

        for(int i=0;i<6;i++)
            f.add(new Button("Bouton" + i));

        f.pack();
        f.setVisible(true);

        f.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent event) {
                f.setVisible(false);
            }
        });
    }
}

Remarquez un petit détail : les tailles précisées sont bien celles des séparations entre composants. Il n'y a pas d'espace entre les bords du conteneurs et les composants.

La stratégie java.awt.GridBagLayout

La stratégie java.awt.CardLayout

La stratégie javax.swing.BoxLayout

...



Gestions d'événements Le Garbage Collector