Master CCI
POO, langage Java
Henri Garreta 

Gestionnaires de disposition efficaces

Traduction non autorisée de

Cette note explore en détail comment travaillent les gestionnaires de disposition1 de la bibliothèque JFC/Swing et comment on peut les imbriquer efficacement pour créer des interfaces-utilisateur graphiques (IUG) esthétiques et utiles.

Sommaire

Pourquoi avez-vous besoin de gestionnaires de disposition ?

Pour expliquer pourquoi les gestionnaires de disposition sont nécessaires il suffit d’examiner quelques uns des problèmes qu’ils résolvent. Regardez les copies d’écran suivantes. Elles décrivent plusieurs égarements trop fréquents des IUG :

Problème : la fenêtre grandit mais les composants restent figés

Un premier défaut de certaines interfaces graphiques est d’ignorer les changements de taille provoqués par l’utilisateur. 640 × 480 pixels est une dimension d’écran très commune et c’est une bonne idée de s’assurer que votre application peut se caser dans une telle taille. Cependant, beaucoup d’utilisateurs ont des écrans bien plus grands et souhaitent en profiter. Des IUG dont on ne peut pas changer la taille seront alors extrêmement frustrantes. Par exemple, dans l’affichage initial de l’écran suivant, le nom saisi est un peu long, si bien qu’il n’entre pas entièrement dans le champ de texte et n’est pas visible d’un seul coup :

badresize1.gif (2921 bytes)

Dans l’espoir de voir le nom tout entier vous allez agrandir horizontalement la boîte de dialogue. Malheureusement, le programmeur qui a écrit cette application a utilisé une taille et un positionnement absolus, si bien que les composants de la boîte de dialogue ne s’agrandissent pas :

badresize2.gif (3173 bytes)

Après avoir reçu des plaintes, le développeur réalisera son erreur et modifiera la IUG afin qu’elle change de taille proprement :

goodresize1.gif (2607 bytes)
Avant l’agrandissement

goodresize2.gif (2836 bytes)
Après l’agrandissement

Le problème des composants conçus pour un look-and-feel ou une taille de police spécifiques

Une autre faute courante consiste à penser que toutes les plate-formes ou bibliothèques de look-and-feel (si vous utilisez JFC/Swing) ont les mêmes caractéristiques relativement aux tailles des éléments. L’image suivante montre l’interface utilisateur précédente sous le look-and-feel « Motif » de JFC/Swing, quand on n’utilise pas un gestionnaire de disposition :

badlf.gif (2218 bytes)

Vous constatez que le look-and-feel Motif applique une bordure vide autour des boutons, ce qui a des effets indésirables lors du dessin. Changer la taille des polices de caractères a des effets similaires.

Le problème des composants conçus pour une langue spécifique

Certains mots prennent la même place dans différentes langues. « Non » est un bon exemple, car il s’épelle à peu près de la même manière dans plusieurs langues. Cependant, de nombreux mots ont une longueur variable selon la langue utilisée. Par exemple :

bye-english.gif (1061 bytes)

Un bouton simple pour finir une application. Le programmeur n’a pas pensé à la traduction dans d’autres langues...

bye-german.gif (1060 bytes)

...telles que l’allemand : « Auf Wiedersehen » apparaît coupé dans le mot beaucoup plus court « Wieder ».

L’utilisateur ne voit pas la phrase entière. Ce n’est pas qu’inesthétique, c’est dangereux (« wieder » signifie encore !).

Que sont les gestionnaires de disposition (Layout Managers) ?

Un gestionnaire de disposition est un objet qui encapsule un algorithme pour calculer la position et la taille des composants d’une IUG. Au lieu d’écrire cet algorithme à l’intérieur de votre code et de guetter les changements de taille et autres événements modifiant la disposition, l’algorithme est gardé séparément. Cela permet qu’il soit réutilisé dans plusieurs applications, tout en simplifiant votre propre code.

Le comportement d’un gestionnaire de disposition est défini par l’interface java.awt.LayoutManager, qui spécifie comment communiquent un conteneur et un gestionnaire de disposition. Elle introduit des méthodes pour :

Une interface additionnelle, java.awt.LayoutManager2, a été ajoutée à partir de la version 1.1 du JDK. Elle ajoute un petit nombre de méthodes de positionnement et de validation.

Méthodes concernant la taille des composants

Tout composant graphique (classe java.awt.Component) possède plusieurs méthodes de consultation pour aider le processus de disposition. Chacune de ces méthodes renvoie un objet java.awt.Dimension décrivant la taille demandée. Ces méthodes sont les suivantes :

Les gestionnaires de disposition utilisent ces méthodes pour calculer la place et la taille des composants. Selon leur nature, ils peuvent satisfaire ou ignorer tout ou partie des souhaits que ces méthodes expriment. Chaque gestionnaire de disposition a son propre algorithme et peut utiliser ou non cette information pour décider du placement d’un composant. Bien entendu, lorsqu’on écrit son propre gestionnaire de disposition on doit indiquer soigneusement lesquelles de ces méthodes sont respectées ou ignorées.

Lorsque vous définissez vos propres composants vous avez deux manières d’indiquer ce que les méthodes précédentes doivent renvoyer :

S’il s’agit d’un composant Swing, alors vous héritez de trois méthodes setPreferredSize(Dimension), setMinimumSize(Dimension) et setMaximumSize(Dimension). Vous pouvez appeler ces méthodes directement pour expliciter l’information correspondante. Par exemple :

  JButton boutonOk = new JButton("Ok");
  boutonOk.setPreferredSize(new Dimension(100,10));

N’ajustez les tailles que si vous êtes sûrs de ce que vous faites (probablement, l’exemple ci-dessus n’est pas une bonne idée...).

S’il ne s’agit pas d’un composant Swing (par exemple, c’est seulement un composant AWT) alors vous devez le sous-classer4 en redéfinissant les méthodes qui ajustent les tailles. Par exemple :

  Button boutonOk = new Button("Ok") { 
      public Dimension getPreferredSize() { 
          return new Dimension(100,10); 
      }
};

Gestionnaires de disposition et conteneurs

Les gestionnaires de disposition doivent être associés aux conteneurs (classe java.awt.Container) pour faire leur travail. Si un conteneur n’a pas de gestionnaire de disposition associé, alors il place les composants comme indiqué par le programmeur à l'aide des méthodes setLocation() et setSize() ou setBounds().

Si un conteneur est associé à un gestionnaire de disposition alors le premier demande au second la position et la taille des composants avant que ces derniers soient dessinés. Le gestionnaire de disposition lui-même ne se charge pas du dessin ; il décide simplement quelle taille et position chaque composant doit occuper et appelle setLocation() et setSize() ou setBounds() sur chacun de ces composants.

On associe un gestionnaire de disposition et un conteneur en appelant la méthode setLayout(LayoutManager) du conteneur. Par exemple :

  Panel p = new Panel();
  p.setLayout(new BorderLayout());

Notez que certains conteneurs, comme java.awt.Panel et javax.swing.JPanel, possèdent un constructeur bien pratique qui prend un gestionnaire de disposition comme argument :

  Panel p = new Panel(new BorderLayout());

Les méthodes add(...)

Les conteneurs ont plusieurs méthodes qui peuvent être utilisées pour leur ajouter des composants. Ce sont :

  public Component add(Component comp) 
  public Component add(String name, Component comp) 
  public Component add(Component comp, int index) 
  public void add(Component comp, Object constraints) 
  public void add( Component comp, Object constraints, int index)

Chacune de ces méthodes ajoute un composant au conteneur et passe des informations au gestionnaire de disposition correspondant. Toutes ces méthodes prennent un paramètre Component, qui spécifie quel composant doit être ajouté. Certaines prennent un indice. Il est utilisé pour indiquer un ordre dans le conteneur ; certains gestionnaires de disposition (comme les CardLayout) respectent l’ordre des composants ajoutés.

Les autres paramètres, name et constraints sont des informations qui peuvent être utilisées par un gestionnaire de disposition particulier pour aider à diriger le placement. Par exemple, lorsqu’on ajoute un composant à un conteneur qui est géré par un BorderLayout, vous devez spécifier un point cardinal comme contrainte.

Chacune des méthodes add() précédentes délègue son travail à une même méthode addImpl() :

  protected void addImpl(Component comp, Object constraints, int index)

(addImpl signifie « implémentation de la méthode add ») Cette méthode est celle qui fait tout le travail. Elle ajoute le Component au Container et, si un gestionnaire de disposition est en train de gérer le conteneur, elle appelle la méthode addLayoutComponent() de ce gestionnaire de disposition. C’est à travers addLayoutComponent() que le gestionnaire de disposition reçoit les contraintes (depuis l’appel de add()).

Si vous créez une sous-classe de Container et vous voulez surcharger une méthode add() il vous suffit de surcharger addImpl(). Toutes les autres méthodes add() passent par cette dernière.

Encadrement2 d’un conteneur

En plus du gestionnaire de disposition, chaque conteneur a une méthode getInsets() qui renvoie un objet java.awt.Insets. Un tel objet a quatre champs publics (de type int) : top, bottom, left and right.

L’encadrement définit l’aire qu’un conteneur se réserve pour son propre usage (par exemple pour dessiner une bordure décorative). Les gestionnaires de disposition doivent respecter cette aire lorsqu’ils positionnent et donnent une taille aux composants contenus.

A titre de démonstration, créez une simple sous-classe de Panel munie d’une bordure 3D surélevée entourant tout le panneau. Vous devez définir une bordure distante de 5 pixels du bord du conteneur, et réservant un espace comparable entre elle et les composants contenus. La classe ressemblera à quelque chose comme ceci :

  public class BorderPanel extends Panel { 
      private static final Insets insets = new Insets(10, 10, 10, 10); 

      public Insets getInsets() {
          return insets;
      }
 
      public void paint(Graphics g) { 
          Dimension size = getSize(); 
          g.setColor(getBackground());
          g.draw3DRect( 5, 5, size.width - 11, size.height - 11, true); 
      } 
  }

Pour créer le panneau, vous avez défini un objet Insets statique qui représente l’espace à réserver. Puisque cet espace ne doit pas changer, vous avez utilisé une simple instance finale. Vous devez renvoyer cette instance chaque fois qu’un gestionnaire de disposition (ou n’importe qui d’autre) appelle getInsets().

Ensuite vous définissez une méthode paint() qui prend la taille du conteneur dans lequel elle dessine et alors trace une bordure surélevée à l’intérieur de cet espace. Si vous utilisez la classe précédente comme ceci :

  Frame f = new Frame("Test"); 
  f.setLayout(new GridLayout(1, 0)); 
  f.setBackground(Color.lightGray);
  BorderPanel p = new BorderPanel(); 
  p.setLayout(new GridLayout(1,0)); 
  p.add(new Button("Hello")); 
  f.add(p); 
  f.setVisible(true); 
  f.pack();

vous obtiendrez l’IUG suivante :

insets1.gif (1695 bytes)

Si vous n’êtes pas familier avec les GridLayout, pas de problème, nous en discuterons plus tard.

Vous pouvez obtenir un autre effet intéressant en ajoutant ce qui suit à la méthode paint() :

  g.draw3DRect(6, 6, size.width-13, size.height-13, false);

après le premier draw3DRect():

insets2.gif (1714 bytes)

ou, si vous échangez true et false dans les deux appels de drawRect() :

insets3.gif (1707 bytes)

Si au lieu des composants d’AWT vous utilisez plutôt ceux de Swing alors vous préférerez explorer les diverses classes Border fournies pour produire ce genre d’effets, au lieu d’utiliser des Insets.

Les gestionnaires de disposition standard de AWT

La bibliothèque AWT inclut cinq gestionnaires de disposition standard ; en les combinant, vous pourrez créer toutes les IUG que vos pouvez imaginer. Ce sont :

Chacun va être expliqué en détail ci-dessous, ainsi que les stratégies et pièges de son utilisation. Par eux-mêmes, ces gestionnaires de disposition ne semblent pas très intéressants. Cependant, lorsqu’ils sont combinés, ils deviennent incroyablement flexibles.

FlowLayout

C’est le plus simple des gestionnaires de disposition de AWT. Sa stratégie de placement est :

Pour ajouter des composants à un conteneur géré par un FlowLayout vous pouvez utiliser une des méthodes add() suivantes :

  public Component add(Component comp) 
  public Component add(Component comp, int index)

Vous ne devez spécifier aucune contrainte pour les composants car FlowLayout dépend uniquement de l’ordre des appels de add() (ou de l’indice indiqué dans un appel de add(Component comp, int index).)

Examinons un simple FlowLayout en action. Supposons que nous ayons la définition de classe suivante :

  public class FlowTest { 
    public static void main(String[] args) { 
      Frame f = new Frame("FlowTest"); 
      f.setLayout(new FlowLayout()); 
      f.add(new Button("A"));
      f.add(new Button("B")); 
      f.add(new Button("C")); 
      f.add(new Button("D")); 
      f.add(new Button("E")); 
      f.setVisible(true); 
    } 
  } 

Cette classe crée un cadre (objet Frame) et lui ajoute cinq boutons. Elle dispose les boutons autant qu’elle peut par ligne, puis elle se déplace à la ligne suivante pour en afficher davantage. Les images suivantes montrent le cadre lorsqu’il est agrandi horizontalement :

flow1.gif (2004 bytes)
Taille initiale

flow2.gif (2004 bytes)
Un peu d’agrandissement horizontal

flow3.gif (2106 bytes)
Davantage d’agrandissement horizontal

Notez que le gestionnaire de disposition met autant de composants que possible. S’il n’y a pas de place pour tous, certains ne sont simplement pas montrés 

:flow44.gif (1667 bytes)

Variantes de FlowLayout

Les objets FlowLayout peuvent être personnalisés au moment de leur construction en passant des indications d’alignement au constructeur :

Ces réglages déterminent comment sont positionnés les composants d’une ligne donnée. Par défaut, tous les composants dans une ligne sont séparés par un écart horizontal, ensuite tout le bazar est centré dans l’espace disponible. LEFT et RIGHT introduisent un bourrage tel que la ligne est alignée à gauche ou à droite. L’alignement peut également être changé, après la construction, en appelant la méthode setAlignment() de FLowLayout avec une des indications d’alignement ci-dessus.

FlowLayout peut aussi être personnalisé avec différents écartements horizontaux et verticaux. Cela spécifie combien d’espace est laissé entre les composants, horizontalement (hgap) et verticalement (vgap).

La taille préférée d’un conteneur muni d’un FlowLayout

Il faut savoir que lorsqu’on demande à un Container quelle est sa taille préférée, il obtient sa réponse auprès de son gestionnaire de disposition. Cela amène la question de savoir ce qu’un FlowLayout fait de préférence avec ses composants.

Malheureusement, il n’y a pas de moyen pour un conteneur de connaître quoi que ce soit à propos de la taille de son parent (c.-à-d. le conteneur qui le contient) ou de la manière dont le gestionnaire de disposition de son parent prévoit de le disposer, lui. Ainsi, la seule information disponible pour déterminer la taille préférée d’un conteneur est l’ensemble des composants qu’il contient.

La préférence d’un FlowLayout est de disposer tous ses composants sur une seule ligne. Cela implique que sa hauteur préférée sera la hauteur du plus haut de ses composants, plus un certain débordement appelé son vgap (vgap et hgap sont des propriétés communes de la plupart des gestionnaires de disposition, et spécifient les écartements entre les composants). La largeur préférée sera la somme de toutes les largeurs des composants contenus, plus des hgap entre les composants ainsi qu’aux extrémités de la ligne.

Réfléchissez aux conséquences de cela. Qu’arrive-t-il si un conteneur géré par un FlowLayout est imbriqué dans un autre conteneur également géré par un FlowLayout ? Par exemple :

  Panel p1 = new Panel(new FlowLayout()); 
  Panel p2 = new Panel(new FlowLayout());
  p2.add(new Button("A")); 
  p2.add(new Button("B")); 
  p2.add(new Button("C")); 
  p2.add(new Button("D")); 
  p2.add(new Button("E")); 
  p1.add(p2); 

Le résultat est déconcertant. La démarche du processus de placement est la suivante :

  1. le parent de p1 demande à ce dernier de s’agencer,
  2. p1 constate qu’il est associé à un gestionnaire de disposition et lui délègue la tache d’agencement,
  3. le FlowLayout de p1 demande la taille préférée de tous ses composants
    1. le seul composant de p1 est p2
    2. p2 constate qu’il est associé à un gestionnaire de disposition et lui délègue la question sur la taille préférée
      1. le FlowLayout de p2 déclare qu’il préfère disposer tous ses composants sur une seule ligne,
      2. le FlowLayout de p2 demande à ses composants (les boutons) leur taille préférée et calcule la taille de cette ligne,
      3. le FlowLayout de p2 renvoie cette taille préférée,
    3. p2 renvoie la taille préférée
  4. le FlowLayout de p1 essaie de respecter autant que possible la taille préférée de p2

Qu’est-ce que nous voulons dire ? Si un conteneur géré par un FlowLAyout est placé à l’intérieur d’un autre conteneur dont le gestionnaire de disposition respecte sa largeur préférée, le conteneur géré par un FlowLayout imbriqué aura toujours une simple ligne.

flow5a.gif (1946 bytes)
Avant le changement de taille

flow5b.gif (1914 bytes)
Après le changement de taille

Stratégies et pièges potentiels des FlowLayout

Quand on utilise un FlowLayout il faut garder présents à l’esprit les points suivants :

BorderLayout

BorderLayout est probablement le plus utilisé des gestionnaires de disposition standard. Il définit un schéma de placement qui fait correspondre son conteneur avec cinq sections logiques :

border1.gif (2976 bytes)

La première chose qui vous vient à l’esprit est certainement « mais je n’ai jamais eu une IUG ressemblant à ceci ! » De plus, vous avez probablement raison. Mais le secret de BorderLayout se trouve dans l’emploi de ses possibilités d’imbrication, et dans l’utilisation de deux ou trois des sections logiques (il est vraiment rare qu’on utilise plus de trois sections à la fois).

Pour commencer, regardez l’extrait de code correspondant à la IUG ci-dessus :

  Frame f = new Frame("orderTest"); 
  f.setLayout(new BorderLayout()); 
  f.add(new Button("North"), BorderLayout.NORTH); 
  f.add(new Button("South"), BorderLayout.SOUTH);    
  f.add(new Button("East"), BorderLayout.EAST); 
  f.add(new Button("West"), BorderLayout.WEST);    
  f.add(new Button("Center"), BorderLayout.CENTER); 

Le gestionnaire BorderLayout requiert une contrainte lors de l’ajout d’un composant. La contrainte doit être une des suivantes :

Ces contraintes sont spécifiées dans les deux méthodes add() suivantes :

Vous verrez d’autres variantes de la méthode add() lorsque vous examinez le gestionnaire CardLayout, plus tard. Pour le moment, tenez-vous en aux formes ci-dessus.

Pour BorderLayout, l’argument contrainte décrit la position que le composant doit occuper. Notez que l’exemple donné plus haut aurait pu être écrit comme ceci :

  Frame f = new Frame("BorderTest"); 
  f.setLayout(new BorderLayout()); 
  f.add("North", new Button("North")); 
  f.add("South", new Button("South")); 
  f.add("East", new Button("East")); 
  f.add("West", new Button("West")); 
  f.add("Center", new Button("Center"));

La principale différence est que la nouvelle forme (utilisant les contraintes du type BorderLayout.NORTH) peut être vérifiée au moment de la compilation : si vous écrivez BorderLayout.NORFH le compilateur peut le détecter. Si vous écrivez "Norfh", cela ne sera pas détecté à la compilation, mais provoquera l’envoi d’une exception IllegalArgumentException durant l’exécution.

La plate-forme Java 2 (préalablement connue comme JDK 1.2) ajoute les constantes additionnelles BEFORE_FIRST_LINE, AFTER_LAST_LINE, BEFORE_LINE_BEGINS, et AFTER_LINE_ENDS. En réalité elles sont équivalentes à NORTH, SOUTH, WEST, et EAST, respectivement. Cependant, elles peuvent avoir une autre orientation lorsque le texte n’est pas orienté de gauche à droite et du haut vers le bas. Examinez la classe java.awt.ComponentOrientation pour obtenir de l’information supplémentaire sur ces questions dépendantes de la langue.

Comment un BorderLayout est-il disposé ?

BorderLayout respecte certaines des tailles préférées des composants contenus, mais non toutes. Sa stratégie de placement est :

  1. S’il y a un composant au Nord (c.-à-d. avec la contrainte NORTH), obtenir sa taille préférée.
    Respecter sa hauteur préférée, si possible, et lui donner pour largeur toute la largeur disponible du conteneur.
  2. S’il y a un composant au Sud (avec la contrainte SOUTH), obtenir sa taille préférée.
    Respecter sa hauteur préférée, si possible, et lui donner pour largeur toute la largeur disponible du conteneur.
  3. S’il y a un composant à l’Est (avec la contrainte EAST), obtenir sa taille préférée.
    Respecter sa largeur préférée, si possible, et définir sa hauteur comme la hauteur restante du conteneur.
  4. S’il y a un composant à l’Ouest (avec la contrainte WEST), obtenir sa taille préférée.
    Respecter sa largeur préférée, si possible, et définir sa hauteur comme la hauteur restante du conteneur.
  5. S’il y a un composant central (avec la contrainte CENTER) lui donner tout l’espace qui reste, s’il en reste.

Considérez maintenant l’imbrication d’un conteneur géré par un FlowLayout à l’intérieur d’un conteneur géré par un BorderLayout. D’abord, que va-t-il se passer si vous ajoutez le conteneur géré par un FlowLayout comme composant NORTH ou SOUTH du BorderLayout ?

  Panel flow = new Panel(new FlowLayout()); 
  Panel border = new Panel(new BorderLayout());    
  flow.add(new Button("A")); 
  flow.add(new Button("B")); 
  flow.add(new Button("C")); 
  flow.add(new Button("D")); 
  flow.add(new Button("E")); 
  border.add(flow, BorderLayout.NORTH);

Vous rappelez-vous ce qui arrive lorsqu’un conteneur géré par un FlowLayout est ajouté à un gestionnaire qui respecte sa taille préférée ? Le conteneur géré par le FlowLayout a une ligne unique ! Il n’arrangera jamais ses composants sur plus d’une ligne.

border4.gif (2450 bytes)

C’était facile (maintenant que vous connaissez le secret). Maintenant, que se passe-t-il si vous ajoutez le conteneur géré par un FlowLayout comme composant WEST ou EAST ? Pour la démonstration, placez le conteneur dans la section EAST :

border2.gif (2709 bytes)

Cet affichage initial est aussi beau que vous l’attendiez : chaque composant a pris sa taille préférée.

Maintenant, réduisez la largeur et augmentez la hauteur. Le conteneur FlowLayout insiste toujours pour maintenir la largeur préférée de la ligne et mange la place que le composant central devrait utiliser. Ce résultat est très inattendu, car on aurait pensé que le conteneur FlowLayout aurait rempli la zone Est et aurait laissé un peu de place au composant du centre.

border3.gif (3129 bytes)

A retenir : un BorderLayout demande la taille préférée de chaque section et la respecte autant que possible.

Pour montrer cela encore plus, réduisez la largeur jusqu’à ce que seul le composant à l’Est soit visible :

border5.gif (2527 bytes)

Enfin, si vous compressez encore, le conteneur géré par le FlowLayout est découpé à la taille de l’espace disponible :

border6.gif (2301 bytes)

Ainsi, la seule place dans laquelle vous devez mettre un conteneur géré par un FlowLayot dans un conteneur géré par un BorderLayout est la section CENTER (à moins que vous ayez seulement un petit nombre de composants dans le FlowLayout).

Considérons maintenant quelques jolies généralisations à propos de BorderLayout :

Ce sont des propriétés importantes d’un BorderLayout, et rendent ce dernier très puissant lorsqu’il est utilisé pour créer des IUG imbriquées plus complexes.

Prenons un exemple vraiment simple. Supposez que vous voulez un simple champ de texte étiqueté. Vous voulez écrire ce nouveau composant, exhibant les propriétés suivantes :

Vous pouvez démarrer avec le code suivant :

  Panel p = new Panel(new BorderLayout()); 
  Label nameLabel = new Label("Name:");
  TextField entry = new TextField(); 
  p.add(nameLabel, BorderLayout.WEST); 
  p.add(entry, BorderLayout.CENTER); 

Ici vous liez la largeur de l’étiquette sa largeur préférée. Elle prendre l’espace nécessaire et ne s’agrandira pas. Le champ de saisie n’est pas borné et peut s’agrandir. Ainsi, vous obtenez le résultat souhaité :

border7.gif (1641 bytes)
Taille initiale

border8.gif (1776 bytes)
Après agrandissement horizontal

En fait, il semble que vous obtenez le résultat souhaité. Regardez ce qui arrive quand vous agrandissez verticalement :

border9.gif (2140 bytes)

Le TextField est agrandi. Ainsi, vous avez besoin de lier la hauteur des deux composants à leur hauteur préférée. Cela peut être obtenu en plaçant la paire Label/TextField à l’intérieur d’un autre conteneur géré par un BorderLayout, à titre de composants NORTH ou SOUTH. Si vous voulez que les champs soient en haut de la IUG, vous devez les placer au Nord :

  Panel p = new Panel(new BorderLayout()); 
  Label nameLabel = new Label("Name:");
  TextField entry = new TextField(); 
  p.add(nameLabel, BorderLayout.WEST); 
  p.add(entry, BorderLayout.CENTER); 
  Panel p2 = new Panel(new BorderLayout()); 
  p2.add(p, BorderLayout.NORTH);

Maintenant, quand vous agrandissez verticalement, vous obtenez l’effet souhaité :

border10.gif (2075 bytes)

Variantes de BorderLayout

Les BorderLayout peuvent être personnalisés avec des valeurs hgap et vgap au moment de leur construction. Ces valeurs spécifient combien d’espace est laissé entre composants, horizontalement (hgap) et verticalement (vgap).

Taille préférée d’un conteneur muni d’un BorderLayout

Si on demande à un conteneur contrôlé par un BorderLayout quelle est sa taille préférée, que renvoie-t-il ? L’idée sous-jacente derrière sa taille préférée est d’assurer que tous les composants contenus reçoivent leurs tailles préférées. D’abord, regardons la largeur préférée :

border1.gif (2976 bytes)

Dans l’image ci-dessus apparaissent trois rangées de composants :

La largeur préférée du gestionnaire doit prendre en compte les largeurs de ces lignes. Si on écrit LP comme abréviation pour « largeur préférée », alors on peut écrire une équation simple donnant la largeur préférée d’un BorderLayout :

  pw = max(north.LP, south.LP, (west.LP + center.LP + east.LP + hgaps)) 

La quantité de hgaps dépend du nombre de composants présents dans la ligne centrale.

La hauteur perforée (HP dans l’équation suivante) dépend des tailles des composants NORTH et SOUTH, plus la plus grande taille d’un composant de la ligne centrale :

  ph = vgaps + north.HP + south.HP + max(west.HP, center.HP, east.HP)

La quantité de vgaps dépend du nombre de lignes présentes dans le BorderLayout.

Une application utile de cette manière de déterminer la taille préférée est la création de deux ou trois lignes de composants dont chacune garde sa taille préférée. Supposez que vous avez une étiquette (Label) une zone de texte (TextArea) et un champ de texte (TextField) dont vous voulez qu’ils apparaissent comme ceci :

border11.gif (3273 bytes)

Et quand vous agrandissez verticalement, vous ne voulez pas que les composants grandissent verticalement :

border12.gif (3703 bytes)

D’abord, rappelez-vous que vous pouvez lier la hauteur d’un composant à sa hauteur préférée en le plaçant dans la parte NORTH ou SOUTH d’un BorderLayout. S’il arrive que ce composant lié soit lui-même un autre BorderLayout (avec des composants NORTH, CENTER et SOUTH) chaque composant à l’intérieur de ce gestionnaire obtient sa hauteur préférée. La résultat est celui de la figure ci-dessus, et voici le code qui le produit :

  Frame f = new Frame("BorderTest"); 
  Panel p = new Panel(new BorderLayout());
  f.setLayout(new BorderLayout()); 
  p.add(new Label("Hello", Label.CENTER), BorderLayout.NORTH);
  p.add(new TextArea(), BorderLayout.CENTER); 
  p.add(new TextField(), BorderLayout.SOUTH);
  f.add(p, BorderLayout.NORTH);

Une combinaison efficace d’un FlowLayout et d’un BorderLayout sert à placer les boutons « Ok » et « Cancel » des dialogues. Par exemple :

border13.gif (2037 bytes)

On obtient cela avec le code suivant :

  Frame f = new Frame("BorderTest"); 
  Panel p = new Panel(new FlowLayout(FlowLayout.RIGHT));
  f.setLayout(new BorderLayout()); 
  p.add(new Button("Ok")); 
  p.add(new Button("Cancel"));
  f.add(p, BorderLayout.SOUTH);

Il y a un problème dans cette approche : les tailles des boutons sont différents. Nous verrons une meilleure manière de créer ce type de IUG dans un moment.

GridLayout

GridLayout dispose ses composants sur une grille. Chaque composant reçoit la même taille et est positionné de la gauche vers la droite, du haut vers le bas :

grid1.gif (2317 bytes)

Le code qui produit la IUG précédente ressemble à ceci :

  Frame f = new Frame("Grid Test"); 
  f.setLayout(new GridLayout(3,4)); 
  for (int x = 1; x < 13; x++) 
    f.add(new Button(""+x));

Dans la spécification d’un GridLayout il y a deux paramètres principaux : nrows (nombre de lignes) et ncols (nombre de colonnes). Vous pouvez spécifier les deux paramètres, mais un seul sera utilisé. Regardez le fragment de code suivant, extrait de GridLayout.java :

  if (nrows > 0)
    ncols = (ncomponents + nrows - 1) / nrows; 
  else 
    nrows = (ncomponents + ncols - 1) / ncols; 

Notez que si le nombre de lignes n’est pas nul alors le gestionnaire calcule le nombre de colonnes ; si le nombre de lignes est nul, il le calcule d’après le nombre de colonnes.

Pour un observateur peu attentif, l’instruction

f.setLayout(new GridLayout(3,4));

semble devoir toujours diviser le composant en douze (3 × 4) sections, mais ce n’est pas le cas. Dans l’instruction ci-dessus, vous pouvez mettre ce que vous voulez comme nombre de colonnes, l’effet obtenu sera exactement le même.

Une meilleure manière de spécifier les lignes et les colonnes d’un GridLayout est de toujours mettre l’un des deux à zéro. Une valeur nulle pour ligne ou colonnes signifie « n’importe quel nombre de  ».

N.B. Attention, vous ne pouvez pas spécifier zéro pour les deux, une IllegalArgumentException serait lancée.

Donner zéro pour une valeur rendent évidentes les intentions de conception. Si vous voulez nécessairement quatre ligne, dites cela ; si vous tenez absolument à avoir trois colonnes, dites cela. L’exemple précédent devrait être écrit soit comme ceci :

f.setLayout(new GridLayout(3,0));

soit comme cela :

f.setLayout(new GridLayout(0,4));

La taille préférée d’un conteneur GridLayout

Comment déterminer la taille préférée d’un GridLayout ? Ce gestionnaire veut s’accommoder à la taille préférée de tous les composants contenus, si possible. Pour cela il examine toutes les tailles préférées et détermine la plus grande largeur préférée et la plus grande hauteur préférée. Vous devez garder à l’esprit que la largeur préférée maximale et la hauteur préférée maximale ne proviennent pas nécessairement du même composant !

Le GridLayout cherche à donner pour taille à chaque composant cette largeur préférée maximum et cette hauteur préférée maximum ( rappelez-vous : tous les composants dans un GridLayout doivent avoir la même taille). Cela produit la taille préférée d’un GridLayout :

  pw = (maxPrefWidth * cols) + (hgap * (cols + 1)) 
  ph = (maxPrefHeight * rows) + (vgap * (rows + 1)) 

« Ok » et « Cancel » revisités

Nous devions faire en sorte que les boutons « Ok » et « Cancel » de la boîte de dialogue vue précédemment soient de même taille. Cela peut être obtenu en déposant les boutons dans un GridLayout à une ligne. Vous pouvez alors ajouter ce conteneur GridLayout à un FlowLayout, ou bien utiliser un BorderLayout imbriqué au lieu du FlowLayout. Voici ces deux approches :

gridb1.gif (2035 bytes)

  Frame f = new Frame("Ok/Cancel");
  f.setLayout(new BorderLayout());
  Panel p = new Panel();
  p.setLayout(new FlowLayout(FlowLayout.RIGHT));
  Panel p2 = new Panel()
  p2.setLayout(new GridLayout(1,0,5,5));
  p2.add(new Button("Ok"));
  p2.add(new Button("Cancel"));
  p.add(p2, BorderLayout.EAST);
  f.add(p, BorderLayout.SOUTH);

gridb2.gif (2014 bytes)

  Frame f = new Frame("Ok/Cancel");
  f.setLayout(new BorderLayout());
  Panel p = new Panel();
  p.setLayout(new BorderLayout());
  Panel p2 = new Panel()
  p2.setLayout(new GridLayout(1,0,5,5));
  p2.add(new Button("Ok"));
  p2.add(new Button("Cancel"));
  p.add(p2, BorderLayout.EAST);
  f.add(p, BorderLayout.SOUTH);

Notez la différence d’aspect. Le FlowLayout met un espacement défini par hgap et vgap autour des composants, tandis que BorderLayout ne met de l’espace qu’entre les composants (si bien que les composants touchent les bord du conteneur).

Dans les deux cas, les boutons apparaissent avec la même taille. L’imbrication de ces gestionnaires peut être vue plus facilement sur la figure suivante :

iso1.gif (3573 bytes)

CardLayout

CardLayout utilise une stratégie différente de celles des autres gestionnaires de disposition. Au lieu d’affecter des emplacements dans le conteneur pour tous les composants inclus, il n’affiche qu’un composant à la fois. Les composants sont ajoutés à un CardLayout en utilisant les méthodes add suivantes :

  public void add(Component component, String key);
  public void add(String key, Component component);
  public void add(String key, Component component, int index);

Les deux premières formes de la méthode add() ajoutent le composant à la fin de la liste des composants du conteneur. La dernière forme l’ajoute à la position indiquée. La position du composant dans le conteneur détermine l’ordre dans lequel les composants sont affichés vie les méthodes de manipulation de CardLayout.

Une chaîne unique doit être affectée à chaque composant ajouté à un conteneur. Par exemple :

  Panel p = new Panel(new CardLayout());
  p.add("one",   new Button ("the first component"));
  p.add(new Button ("the second component"), "two");
  p.add("three", new Button ("the third component"));
  p.add("between two and three", new Button ("the fourth component"), 2);

Lorsque des composants sont ajoutés à un conteneur contrôlé par un CardLayout, une clé de type String est associée à chaque composant. Différents composants peuvent être affichés en utilisant les méthodes next(), previous() et show() de CardLayout. L’ordre dans lequel les composants ont été ajoutés au conteneur détermine leur ordre d’affichage lorsqu’on utilise les méthodes next et previous. Par exemple :

  CardLayout l = (CardLayout) p.getLayout();
  l.previous(p);
  l.next(p);
  l.show(p, "two");

Notez que les méthodes previous(), next() et show() requièrent qu’une référence au conteneur leur soit passée comme argument. Ces gestionnaires de disposition ne gardent pas une référence au conteneur qu’ils gèrent. Lorsqu’ils effectuent des actions comme appeler previous(), next() ou show() et, comme nous le verrons plus tard, l’arrangement des composants, ces gestionnaires de disposition doivent être informés du conteneur sur lesquels ils opèrent afin d’avoir accès aux composants qu’ils agencent.

Les CardLayout sont couramment employés dans les IUG qui veulent organiser leur données dans plusieurs écrans plus petits, au lieu d’un unique écran plus grand. Cela s’utilise typiquement en combinaison avec plusieurs boutons pour permuter les composants à l’intérieur du CardLayout.

[ Dans JFC/Swing, un composant JTabbedPane remplace avantageusement un panneau munis d’un CardLayout – N. du T.]

La taille préférée d’un conteneur CardLayout

Comment déterminer la taille préférée d’un CardLayout ? Puisque la taille préférée doit prendre en compte la taille préférée de tous les composants inclus dans le conteneur, la taille préférée du CardLayout sera définie par la plus grande des largeurs préférées des composants et la plus grande des hauteurs préférées des composants.

GridBagLayout

GridBagLayout est connu comme le gestionnaires de disposition le plus difficile à comprendre. Il y a plusieurs raisons à cette renommée :

Traiter tous les détails de l’utilisation d’un GridBagLayout peut occuper un livre entier. Cela sera brièvement couvert dans le contexte d’un exemple dans une section ultérieure.

Sans un outil de construction de IUG comme VisualAge for Java ou JBuilder vous devriez éviter les GridBagLayout autant que possible. Pour la plupart des IUG vous obtiendrez les mêmes résultats en imbriquant d’autres gestionnaires de disposition plus simples.

Imbriquer les gestionnaires de disposition pour atteindre le nirvana

Les sections suivantes décrivent une interface utilisateur réelle et comment faire pour la créer avec l’aide des gestionnaires de disposition de Java.

La IUG à créer

Un gros exemple d’imbrication de gestionnaires de disposition standard est WS_FTP, un client FTP graphique pour Windows :

Cette IUG, qui semble destinée aux utilisateurs avertis, est passablement complexe. Les boutons du bas sont des items de menu et ceux à droite de chaque liste de fichiers des menus surgissants en relation avec les listes de fichiers. Cependant, comme elle est, cette IUG est un exemple intéressant d’imbrication de gestionnaires de disposition basiques.

Comment concevoir cette IUG

Avant d’écrire n’importe quelle IUG vous devez toujours faire deux choses :

  1. Dessiner une image ! Vous seriez surpris de savoir combien de gens essaient de visualiser une IUG dans leur tête. Faire un dessin rend la conception significativement plus aisée.
  2. Décrire le comportement lors des chargements de taille. Après avoir dessiné une image, ajoutez des informations sur les parties de la IUG qui doivent s’agrandir ou se rétrécir lorsque la fenêtre change de taille.

Dans l’exemple précédent, ignorez les lignes autour des composants. On peut les ajouter une fois que la IUG est construite, soit en utilisant le modèle de conception « Décorateur » soit, si vous utilisez un conteneur JFC/Swing, en appelant setBorder().

Partez d’un dessin décrivant ce à quoi vous voulez que la IUG ressemble. Simplifiez un peu en enlevant les bordures décoratives et supposez l’existence d’un composant « Liste de fichiers » (probablement une table avec un en-tête).

Les flèches indiquent quelles parties de la IUG doivent s’agrandir ou se rétrécir lorsque la IUG est modifiée en taille.

Le diagramme ci-dessus a été annoté avec le comportement des changements de taille de la IUG. Nous avons montré dessous quelles lignes représentent un comportement basé sur une taille fixe (Preferred size) ou une taille variable (Varying size). Lorsqu’une partie d’un composant a une taille fixe (horizontale, verticale ou les deux) il ne s’agrandit ni ne se contracte lorsque la fenêtre est modifiée. Le signe égal (=) indique l’assemblage de composants ayant la même taille.

Quelques notes sur le comportement de la IUG :

Maintenant, l’amusement commence. Vous devez examiner la IUG est essayer de visualiser les gestionnaires de disposition utilisés pour représenter ses diverses sections. Quand vous essayez de concevoir une IUG, travaillez vers l’intérieur à partir des bords externes. Commencez toujours en cherchant des « frontaliers », des composants ou groupes de composants qui bordent une face et ont une largeur ou un hauteur fixe.

En regardant la IUG ci dessus vous pouvez la visualiser comme un BorderLayout avec deux composants : un SOUTH et un CENTER :

La partie SOUTH du BorderLayout a une hauteur fixe, basée sur la taille préférée de ses composants. La partie CENTER s’agrandit pour remplir tout l’espace restant dans la IUG. En examinant la section SOUTH de plus près, on voit trois parties :

On note trois composants, empilés verticalement, chacun avec sa hauteur préférée. Vous pouvez être tenté de les planter dans un GridLayout, mais cela les forcerait à prendre le même espace verticalement. Ce n’est pas ce que vous voulez.

Supposez que vous placez ces trois composants comme le NORTH, le CENTER et le SOUTH d’un panneau muni d’un BorderLayout, lui-même imbriqué comme composant SOUTH dans le BorderLayout principal. Pensons à ce qui se passe lorsque cette partie de la IUG est agencée :

  1. Le BorderLayout extérieur demande sa taille préférée à son composant SOUTH
    1. Le composant SOUTH étant lui-même un BorderLayout, demande leur taille préférée à ses propres composants
    2. Le BorderLayout imbriqué renvoie une taille qui est
      • largeur = la plus grande des largeurs préférées de ses trois composants
      • hauteur = la somme des hauteurs préférées de ses trois composants
  2. Le BorderLayout extérieur affecte à la taille de son composant SOUTH la largeur du cadre et la hauteur préférée du composant SOUTH. Le composant CENTER obtient tout l’espace restant.
  3. Le composant SOUTH peut maintenant agencer les composants qu’il contient
    1. Le composant SOUTH demande leur taille préférée à ses enfants,
    2. Le composant SOUTH affecte à ses composants toute la largeur qu’il a lui-même obtenue
    3. Il accorde à son composant NORTH sa hauteur préférée
    4. Il accorde à son composant SOUTH sa hauteur préférée
    5. Il accorde à son composant CENTER l’espace restant.

Notez le dernier alinéa. Le centre obtient tout l’espace restant. Puisque le BorderLayout extérieur a accordé au composant SOUTH sa hauteur préférée et que la hauteur préférée est égale à la somme des hauteurs préférées des composants contenus, le composant CENTER imbriqué (la fenêtre de message, un objet TextArea) obtient justement sa hauteur préférée.

D’abord, définissons la partie NORTH de cette sous-IUG. Elle a trois composantes, également espacées, centrées sur la largeur du panneau. Cela ressemble à un FlowLayout, non ? Ben oui, c’est un Panel avec un FlowLayout contenant trois composants CheckBox. Notez que les deux premiers CheckBox sont associés à l’aide d’un CheckBoxGroup, si bien qu’ils deviennent des « boutons radio ».

Créons la partie SOUTH de cette sous-IUG. Elle a sept composants Button, de même taille. L’expression « de même taille » doit immédiatement déclencher « GridLayout » dans votre esprit. Donc, la partie SOUTH de la sous-IUG est un Panel avec un GridLayout contenant sept composants Button. Ce GridLayout consiste en une unique ligne, il faut donc utilisez les paramètres (1, 0) dans l’appel de son constructeur.

La partie CENTER est un simple composant TextArea. Vous n’avez pas besoin de l’imbriquer dans un autre composant Panel ; les composants peuvent être directement ajoutés aux conteneurs... Définissez le nombre de lignes de cette zone de texte à 3 et le nombre de colonnes à 30. Ces nombres pilotent la taille préférée de la zone de texte ; ils sont sans effet sur cette dernière lorsque sa taille est définie par le gestionnaire de disposition. Ici vous devez les spécifier car on demandera à la zone sa taille préférée et cette dernière donnera par défaut une réponse supérieure à 3 lignes. Le 30 n’est utile que si on appelle la méthode pack() du cadre, car il contribuera alors au calcul de sa largeur. Une dernière indication à propos de TextArea : si vous la placez dans un BorderPanel comme discuté dans la section Insets ci-dessus, vous obtiendrez un bord en relief, en creux ou en sillon.

Récapitulons :

Maintenant il va falloir s’intéresser à la partie CENTER du BorderLayout extérieur. D’abord, examinez ce dessin de la section centrale :

Vous ne voyez rien d’intéressant ? Cela a un rapport avec les deux boîtes dessinées autour des composants de la partie gauche et ceux de la partie droite. Ils sont structurellement les mêmes. Exactement les mêmes. Lorsque deux choses sont les mêmes, et que les faire est une corvée, vous devez penser réutilisation.

Une classe auxiliaire : FileDisplay

Prenons un de ces machins et examinons-le séparément. Décidons d’en faire une classe FileDisplay. Pour commencer l’examen de cette sous-IUG recherchons ses éléments frontaliers. On en voit immédiatement trois :

La partie NORTH consiste en un Label et une « combo box » (nous utiliserons un composant Choice de AWT pour le moment), empilés verticalement. Le CENTER est un composant List. L’East est un groupe de Button empilés verticalement.

SI nous regardons d’abord la partie NORTH, il faut que le Label et le Choice prennent leur hauteur préférée et toute la largeur. Puisque ceci est le composant NORTH, tout ce qu’on y mettra dedans recevra sa largeur préférée. En plaçant le Label et le Choice comme NORTH et SOUTH (ou NORTH et CENTER, ou CENTER et SOUTH) d’un BorderLayout, ils auront leur hauteur préférée. Encore une fois, un GridLayout aurait été un mauvais choix car cela aurait supposé que les deux composants avaient la même hauteur. Vous pouvez vouloir changer la taille de la police dans le Label pour la rendre plus grosse, sans vouloir que cela affecte la taille de l’objet Choice.

La partie CENTER est simple : uniquement un composant List. Non contenu dans un Panel (sauf si vous avec une sous-classe de Panel qui fournit une bordure décorative, comme le BorderPanel précédemment étudié).

Venons-en à la partie EAST, qui est un peu plus tordue. Vous voulez que tous les composants Button aient la même taille (vos pensez immédiatement GridLayout) mais cette taille doit être constamment leur taille préférée. C’est l’espace sous les boutons qui s’agrandit et se rétrécit si nécessaire.

Puisque vous avez placé toute la pile de Button comme le composant EAST d’un BorderLayout, leur largeur est fixée à la largeur préférée du conteneur qui renferme ces boutons. Pour que leur largeur effective soit leur largeur préférée vous devez placer toute la bande à l’intérieur d’un autre BorderLayout, en tant que composant NORTH ! En définitive, le composant EAST est un Panel muni d’un BorderLayout contenant un autre Panel à titre de composant NORTH, et ce second Panel a lui-même un GridLayout qui contrôle les boutons. Le GridLayout étant une simple colonne de boutons, les paramètres de sa construction sont (0, 1).

Voici donc la structure d’un FileDisplay :

Pour que tout ceci fonctionne proprement vous devez faire que la propriété « text » du composant Label devienne la propriété « text » du FileDisplay tout entier. Pou cela il vous faut définir dans la classe FileDisplay des méthodes String getText() et void setText(String text) qui simplement obtiennent ou définissent la valeur du texte du Label. Vous verrez cela dans le code montré plus loin.

Il ne reste plus maintenant que la section CENTRE de la IUG principale.

Retour à la IUG principale...

Examinons à quoi ressemble la section CENTER quand on enlève les détails encapsulés dans les FileDisplay :

Les deux ovales représentent les deux instances de FileDisplay. Le comportement de cette partie de la IUG est déterminé par quatre composants : deux composants FileDisplay, qui doivent prendre la même quantité d’espace, et deux composants Button, chacun avec sa taille préférée, flottant librement entre les deux précédents.

Supposons que, pour des raisons esthétiques, les deux boutons doivent être centrés verticalement.

C’est une situation tordue. Parmi les gestionnaires de disposition standard, il n’y a qu’un choix possible : GridBagLayout.

Voyez comment cela devient difficile, y compris pour un cas aussi simple que celui-ci. D’abord, vous devez déterminer quelles sont les cellules de la grille. Pour cela, une chose est toujours nécessaire, avec des GridBagLayout : dessiner des lignes traversant toute la IUG entre chaque paire de composants adjacents :

Vous pouvez voir que ce GridBagLayout a six cellules et quatre composants (numérotés). Chaque cellule requiert une configuration, passée à l’aide d’un objet GridBagConstraints, dans la version adéquate de la méthode add : add(Component component, Object constraints). Vous devez cheminer à travers des options des GridBagConstraints et déterminer lesquelles vous conviennent.

D’abord les plus faciles : gridx, gridy, gridwidth, gridheight, ipadx, et ipady. Vous pouvez utiliser des encadrements (insets) au lieu de bourrages (pad), si bien que ipadx et ipady seront nuls pour tous les composants :

 
1
2
3
4
gridx
0
2
1
1
gridy
0
0
0
1
gridwidth
1
1
1
1
gridheight
2
2
1
1
ipadx
0
0
0
0
ipady
0
0
0
0

 

Ensuite vous devez comprendre le paramètre remplissage (fill). Rappelez-vous que cela signifie la manière dont les composants s’étendent à l’intérieur des cellules qu’on leur a allouées. Pour les composants 1 et 2, cela doit être BOTH ; si vous voulez que les composants 3 et 4 aient leur taille préférée, le paramètre fill doit être NONE.

Ensuite, l’ancre. Puisque les composants 1 et 2 remplissent complètement leur espace alloué, peu importe leur ancre. Dans ce cas on utilise habituellement la valeur par défaut CENTER. Le composant 3 se tient en bas et au centre de sa cellule, son paramètre ancre doit être SOUTH. Le composant 4 est en haut et au centre de sa cellule, son paramètre ancre est NORTH.

Les encadrements définissent l’espace laissé autour des composants. Il n’y a pas d’espace à laisser autour des composants 1 et 2, ainsi leurs encadrements sont laissés à 0. Vous voulez sans doute un peu d’espace autour des composants 3 et 4, quelque chose comme 4 pixels entre eux et tout autre composant voisin. Il y a plusieurs manières d’obtenir 4 pixels entre les composants 3 et 4, par exemple mettre 2 pour leurs encadrements supérieur et inférieur.

On arrive au pire moment de la construction de ce GridBagLayout : les poids. Réfléchissez aux règles que vous voulez :

  1. Les cellules des composants 1 et 2 s’agrandissent horizontalement de la même manière.
  2. Les cellules des composants 3 et 4 ne s’agrandissent pas horizontalement.
  3. Les cellules des composants 1 et 2 s’agrandissent verticalement pour remplir tout l’espace, et cela de la manière manière.
  4. Les cellules des composants 3 et 4 s’agrandissent verticalement, chacune remplissant la moitié de l’espace.

Par la règle 1, les paramètres weightx des composants 1 et 2 doivent être égaux. Par la règle 2, les paramètres weightx des composants 3 et 4 doivent être 0. Ce sont les seules règles relatives à l’espace horizontal, si bien que vous pouvez prendre les poids que vous voudrez pour les composants 1 et 2, pourvu qu’ils soient égaux. Les poids sont des nombres flottants, et une convention sympa consiste à faire que leur somme soit 1.0. Par conséquent, il faut donner aux composants 1 et 2 une valeur de 0.5 pour le paramètre weightx.

Par la règle 3, les paramètres weighty des composants 1 et 2 doivent être égaux, et égaux à la valeur weighty totale du conteneur. Par la règle 4, le paramètre weighty de chacun des composants 3 et 4 doit être la moitié du weighty total. Tout cela donne les résultats suivants :

 
1
2
3
4
 gridx
0
2
1
1
 gridy
0
0
0
1
 gridwidth
1
1
1
1
 gridheight
2
2
1
1
 ipadx
0
0
0
0
 ipady
0
0
0
0
 fill
BOTH
BOTH
NONE
NONE
 anchor
CENTER
CENTER
SOUTH
NORTH
 weightx
0.5
0.5
0.0
0.0
 weighty
1.0
1.0
0.5
0.5
 insets  top
0
0
0
2
 bottom
0
0
2
0
 left
0
0
4
4
 right
0
0
4
4

La définition de ces GridBagConstraints termine la conception du IUG. Le code réalisé produit ceci :

ws10.gif (8008 bytes)

Lequel, décorations mises à part, se présente exactement comme vous vouliez !

N. B. A propos du dernier GridBagLayout : quelquefois, la première idée qui vient en étudiant les composants 3 et 4 est de mettre weightx et weightxy égaux à 0 pour les deux composants. Cela produit le résultat suivant :

ws11.gif (8037 bytes)

Notez où sont les boutons "<--" et "-->". Les valeurs de weightx et weighty contrôlent la taille des cellules de la gille que le composant occupe. Si la somme des poids pour une ligne ou une colonne données n’égale pas celle des autres lignes ou colonnes, alors le GridBagLayout comble la différence en définissant le poids du dernier composant dans la ligne ou la colonne. Dans notre cas nous avons trois colonnes : deux sont des composants FileDisplay et le troisième est la colonne avec les deux buttons dessus. Puisque la somme des valeurs weighty des deux boutons ne correspond pas à la valeur weighty des autres colonnes, GridBagLayout donne à la cellule du second bouton le totalité de l’espace restant.

Ecrivons du code pour cette IUG

Il y a deux approches courantes de l’écriture du code d’une IUG à la main. L’une consiste à construire les sous-IUG par des appels de méthodes, l’autre consiste simplement à mettre tout le code d’un seul trait.

Le code suivant illustre la première manière :

Ce code a été généré en utilisant VisualAge for Java, de IBM, et simplifié en lui enlevant la gestion des exceptions et les préfixes « ivj » de tous les noms de variables. Ce code suit une stratégie d’instantiation « paresseuse » : aucun composant de la IUG et aucune sous-IUG ne sont crées avant que e ne soit nécessaire. Ce qui a plusieurs avantages :

Bien entendu, il y a quelques désavantages :

Une autre alternative consiste à utiliser un constructeur de IUG pour créer l’interface. Cela peut être une option très intéressante, surtout si votre IUG est très complexe.

En prenant le même exemple et en l’écrivant d’un seul jet on obtient le code suivant :

L’avantage de cette manière est d’avoir moins de code, mais il peut être difficile de voir la structure de la IUG dans le code. Il peut aussi devenir très difficile d’ordonner correctement la création/addition des objets de la IUG : soyez très prudents avant de créer quelque chose avant de l’utiliser.

Un besoin très fréquent

Un besoin fréquent dans la construction de IUG est celui d’un masque de saisie3. Voici un exemple simple :

nameform1.gif (2577 bytes)

Notez que dans cette IUG toutes les étiquettes sont alignées horizontalement, ainsi que les champs de texte. Lorsque cette IUG est agrandie horizontalement, les champs de texte s’agrandissent mais les étiquettes non.

Une des choses à garder dans la tête quand on conçoit cette IUG est ce qui doit arriver lorsque la fenêtre est agrandie verticalement. Les champs de texte ont tendance à devenir laids lorsqu’ils s’agrandissent verticalement. Vous voulez probablement conserver leur hauteur préférée.

nameform2.gif (3056 bytes)
Garder les étiquettes et les champs de texte à leur taille préférée

nameform3.gif (3435 bytes)
Permettre aux étiquettes et champs de texte de grandir

Bon, comment concevoir cette IUG ?

D’abord vous devez réfléchir à la manière de lier les étiquettes et les champs de texte à leur hauteur préférée. Cela peut se faire en plaçant tous les composants comme parties NORTH d’un BorderLayout.

Ensuite vous devez diviser la IUG en deux parties également étendues : la partie gauche et la partie droite. Vous devez penser « GridLayout » dès que vous entendez « également étendues ». Ce GridLayout consiste en une unique ligne ; le paramètre pour le constructeur sera donc (1, 0) (Dans l’exemple ci-dessus on a aussi défini hgap à 5).

Maintenant, comment produire les paires étiquette + champ de texte ? La première réponse est sans doute de placer chaque paire dans leur propre BorderLayut. Par exemple :

  Panel panel1 = new Panel(new BorderLayout());
  panel1.add(new Label("First name:"), BorderLayout.WEST);
  panel1.add(new TextField(), BorderLayout.CENTER);

En utilisant cette stratégie pour construire chaque paire d’étiquettes et de champs de texte on obtient un effet légèrement indésirable :

nameform4.gif (2866 bytes)

Qu’est-ce qui s’est passé ? Pensez à ce que BorderLayout fait. Il dit « Je regarde mon composant WEST ; je lui donne sa largeur préférée. Ensuite, je donne à mon composant CENTER tout le restant de place ». Notez bien qu’il ne dit pas « Je regarde le composant au-dessous de moi ; je m’assure que mon composant WEST a la même largeur que son composant WEST » D’une manière ou d’une autre, vous avez besoin d’associer d’une part les étiquettes les une avec les autres, et d’autre part les champs de texte les uns avec les autres.

La seule manière de faire cela est de mettre toutes les étiquettes qui vont ensemble dans le même conteneur. Et pareil pour les champs de texte. Cependant, vous devez aussi vous assurer que les étiquettes et les champs de texte sont alignés horizontalement.

Plus qu’une traduction, le texte en bleu est une adaptation assez libre des explications originales et de l’exemple qui va avec. [N. du T.]

L’élément le plus extérieur de notre IUG est un panneau muni d’un BorderLayout. Il a pour composant NORTH un panneau (nommé diptyque dans notre code), lequel dispose donc de toute la largeur disponible et de sa hauteur préférée.

Ce panneau est muni à son tour d’un gestionnaire GridLayout à une ligne, ce qui assure que ses deux composants (nommés voletDeGauche et voletDeDroite) ont exactement la même taille.

Chacun de son côté, ces deux volets sont munis d’un gestionnaire BorderLayout et ont deux panneaux (nommés dans notre code panneauDeLabels et panneauxDeTexte) comme composants WEST et CENTER, ce qui donne au premier une largeur fixe (égale à sa largeur préférée) et au second une largeur qui varie avec les changements de taille du cadre tout entier.

Enfin, ces panneaux de labels et de textes sont munis de GridLayout à trois lignes, et reçoivent les composants élémentaires (des Label et des TextField) nécessaires.

Finalement, notre IUG sera donc structurée comme suit (on a fait apparaître les noms des variables qui représentent les composants dans notre code) :

Le code correspondant, enrichi d’un peu de hgap et vgap pour écarter les composants entre eux, se trouve dans le fichier InputForm.java.

Notez qu’un des TextField reçoit une indication de largeur (12 colonnes). Cela définit sa taille préférée initiale. Il faut le faire car notre panneau est placé dans un cadre dont la taille est initialisée par un appel de pack() ; si tous les champs de texte étaient sans indication de largeur, ils seraient tous ridiculement réduits. D’autre part, il suffit de fixer la largeur d’un champ de texte, puisque notre assemblage de gestionnaires de disposition fait en sorte qu’ils soient tous de même largeur.

Notez également que cette IUG est satisfaisante parce que le plus long texte d’une étiquette du volet de gauche (« First name :  ») est sensiblement aussi long, une fois affiché, que le plus long texte d’une étiquette du volet de droite (« Last name :  »). Cela rend toutes les étiquettes sensiblement égales. Si ce n’était pas le cas, il faudrait forcer l’égalité en définissant explicitement la longueur de la plus longue étiquette de chaque côté :

    ...
    Label etiquette = new Label("First name : ");
    etiquette.setPreferredSize(new Dimension(100, 0));
    panneauDeLabels.add(etiquette);
    panneauDeLabels.add(new Label("Street : "));
    panneauDeLabels.add(new Label("Phone : "));
    ...

Il faut noter les paramètres passés aux conteneurs GridLayout les plus imbriqués. On aura pu les concevoir en partant du fait qu’ils ont une colonne. Cela aurait été une erreur : si au lieu de « panneauDeTextes = new Panel(new GridLayout(3, 0,...)); » on avait construit ces conteneurs par l’appel « panneauDeTextes = new Panel(new GridLayout(0, 1, ...)); » la IUG aurait ressemblé à ceci :

nameform5.gif (2814 bytes)

Copyright 1998-1999 MageLang Institute. All Rights Reserved.


[1] Gestionnaire de disposition est employée partout dans ce document comme traduction de layout manager.

[2] Nous traduisons insets par encadrement.

[3] Nous traduisons input form par masque de saisie.

[4] Rappel : on dit qu'on « sous-classe » un objet lorsqu'on définit une sous-classe de sa classe afin de pouvoir redéfinir certaines de ses méthodes.