Les composants de sélection ou de choix

Chapitres traités   

Au travers de cette étude, nous nous intéressons plus particulièrement à tout ce qui nous permet de faire une sélection : un choix ou un ensemble de choix.

Nous commencerons tout simplement, même si nous l'avons largement utilisé, par un bouton (simple ou double états). Nous en profiterons pour découvrir tous les artifices qu'il est possible de mettre en oeuvre sur ces boutons, afin que l'ergonomie soit la plus intuitive, et donc la plus simple, tout en ayant une apparence des plus sympatiques.

Vous savez maintenant comment recueillir du texte tapé par des utilisateurs. Toutefois, il est souvent préférable de leur offrir un ensemble fini de choix plutôt que de leur demander d'introduire des données dans un composant de texte. Au moyen d'un ensemble de boutons ou d'une liste d'options, vous pouvez indiquer aux utilisateurs les choix mis à leur disposition. De plus, vous n'aurez pas besoin de créer des procédures de contrôle d'erreurs pour ces types de sélections. Dans cette section, vous apprendrez à programmer des cases à cocher, des boutons radio, des listes d'options, des curseurs, etc.

Choix du chapitre La classe de base AbstractButton

La classe AbstractButton est une classe abstraite et sert de super-classe pour tous les composants Swing dont le comportement est similaire à celui d'un bouton. Comme cette classe est générique, elle définit un grand nombre de propriétés. Les boutons Swing, comme les libellés, peuvent afficher du texte, une image (une icône) ou les deux.

Par défaut, un AbstractButton affiche une seule ligne de texte dans une fonte unique. Cependant, si la propriété du texte commence par "<html> ...", le bouton de texte est formaté en HTML et peut contenir plusieurs fontes et plusieurs lignes.

Gestion possible de plusieurs icônes

Les boutons Swing peuvent même afficher des icônes différentes suivant l'état dans lesquels ils se trouvent. Ainsi, en plus de l'icône par défaut, AbstractButton possède des propriétés qui spécifient les icônes à afficher :

  1. Quand le bouton est appuyé,
  2. Quand le bouton est sélectionné,
  3. Quand le bouton est désactivé,
  4. Quand le bouton est désactivé et sélectionné,
  5. Quand le curseur de la souris passe au dessus du bouton sans qu'il soit sélectionné,
  6. Et quand le curseur de la souris passe au dessus du bouton en étant sélectionné.

Si la propriété rollOver est spécifiée et si la propriété rollOverEnabled est à true (mode par défaut), la rolloverIcon est affichée quand la souris se trouve au dessus du bouton.

Gestion des icônes de la classe AbstractButton
Icon getDisabledIcon()
void setDisabledIcon(Icon icône)
Choix de l'icône du bouton lorsque ce dernier est désactivé.
Icon getDisabledSelectedIcon()
void setDisabledSelectedIcon(Icon icône)
Choix de l'icône du bouton lorsque ce dernier est sélectionné et désactivé.
Icon getIcon()
void setIcon(Icon icône)
Choix de l'icône par défaut.
Icon getPressedIcon()
void setPressedIcon(Icon icône)
Choix de l'icône du bouton lorsque ce dernier est appuyé.
Icon getRolloverIcon()
void setRolloverIcon(Icon icône)
Choix de l'icône du bouton lorsque le curseur de la souris passe au dessus sans qu'il soit sélectionné.
Icon getRolloverSelectedIcon()
void setRolloverSelectedIcon(Icon icône)
Choix de l'icône du bouton lorsque le curseur de la souris passe au dessus en étant sélectionné.
Icon getSelectedIcon()
void setSelectedIcon(Icon icône)
Choix de l'icône du bouton lorsque ce dernier est sélectionné.
boolean isRolloverSelectedIcon()
void setRolloverSelectedIcon(boolean validation)
Active ou pas le mode de gestion de passage de la souris au dessus du bouton.

Les événements

Les boutons Swing génèrent trois types d'événements :

  1. java.awt.event.ActionEvent : est généré lorsqu'un bouton quelconque est pressé.
  2. java.awt.event.ItemEvent : est généré lorsqu'un bouton de type bascule (double état) est sélectionné ou désélectionné.
  3. java.awt.event.ChangeEvent : est généré quand l'état interne du bouton change, par exemple, quand le pointeur de la souris arrive sur le bouton ou quand l'utilisateur arme le bouton en cliquant dessus.
Enregistrement des écouteurs d'événements
void addActionListener(java.awt.event.ActionListener écouteur)
void removeActionListener(java.awt.event.ActionListener écouteur)
Prise en compte ou non de la gestion d'événement de type ActionEvent.
void addChangeListener(java.awt.event.ActionListener écouteur)
void removeChangeListener(java.awt.event.ActionListener écouteur)
Prise en compte ou non de la gestion d'événement de type ChangeEvent.
void addItemListener(java.awt.event.ActionListener écouteur)
void removeItemListener(java.awt.event.ActionListener écouteur)
Prise en compte ou non de la gestion d'événement de type ItemEvent.

Plusieurs artifices d'affichage suivant l'état du bouton, et suivants ses propriétés intrinsèques

Il est possible de gérer finement l'apparence de vos boutons, surtout suivant l'état dans lequel ils se trouvent. Voici ci-dessous quelques unes des propriétés qui s'avèrent intéressantes :

Propriétés intéressantes
int getHorizontalAlignment()
void setHorizontalAlignment(int justification)
int getVerticalAlignment()
void setVerticalAlignment(int justification)
Positionnement souhaité pour l'icône et le texte. Par défaut, les éléments sont placés au centre.
int getHorizontalTextPosition()
void setHorizontalTextPosition(int justification)
int getVerticalTextPosition()
void setVerticalTextPosition(int justification)
Position du texte par rapport à l'icône.
void setEnabled(boolean activation)
Un bouton Swing peut être activé et désactivé. Les boutons désactivés sont typiquement affichés avec des graphismes grisés, bien que nous puissions spécifier une autre icône de désactivation.
int getMnemonic()
void setMnemonic(int raccourci)
void setMnemonic(char raccourci)
Nous pouvons spécifier un mnémonique. Le caractère mnémonique est alors souligné dans le texte du bouton et le bouton peut alors être pressé depuis le clavier.
String getText()
void setText(String libellé)
Introduction du libellé du bouton.
boolean isFocusPainted()
void setFocusPainted(boolean activation)
Permet de tracer ou pas le focus du bouton qui se traduit par un rectangle de couleur assez neutre autour du libellé. Le tracé est proposé par défaut. Lorsque vous utilisez plusieurs boutons, ce tracé est intéressant puisqu'il permet de voir tout de suite le bouton qui est actif. Vous pouvez alors agir avec le clavier pour lancer l'action souhaitée sans passer nécessairement par la souris. Par contre, lorsque vous possédez un seul bouton sur votre IHM, il est plutôt souhaitable de désactivé ce tracé qui devient alors gênant.
boolean isSelected()
void setSelected(boolean sélection)
Détermine si le bouton est sélectionné. Cette propriété peut s'avérer intéressante dans le cas d'une gestion de plusieurs boutons pour un même groupe, notamment pour faire un choix parmi plusieurs valeurs possibles.
void doClick()
void doClick(int delai)
Simule l'action d'un clic de la souris. Ainsi, par programme, vous pouvez lancer la méthode actionPerformed() indirectement au travers de cette méthode doClic(). Il est également possible de préciser un temps en milliseconde.
java.awt.Insets getMargin()
void setMargin(java.awt.Insets libellé)
Il possible de régler une marge intérieure, donc entre le bord du bouton et le texte du libellé.
protected void paintBorder(java.awt.Graphics contexteGraphique)
Si vous redéfinissez cette méthode, il est possible de prévoir le dessin de la bordure totalement personnalisé.
ButtonModel getModel()
void setModel(ButtonModel modèleDeBouton)
Les boutons respecte le modèle MVC (Modèle-Vue-Contrôleur). Il peut être intéressant de récupérer le modèle du bouton (chacun en dispose du sien) pour connaître l'état du bouton en un instant donné (voir plus loin).
int getIconTextGap()
void setIconTextGap(int espacement)
Définit l'espacement entre l'icône et le libellé dans le cas où ces deux éléments sont sollicités pour définir le bouton concerné.

Le Modèle-Vue-Contrôleur des boutons Swing

Nous connaissons déjà le principe du modèle MVC. Nous l'avons largement utilisé lors de l'étude précédente. Pour la plupart des composants, la classe modèle implémente une interface dont le nom se termine par Model, d'où l'interface appelée ButtonModel dans le cas des boutons.

Les classes implémentant cette interface ButtonModel peuvent ainsi définir l'état des divers types de boutons. En fait, les boutons ne sont pas aussi complexes et la bibliothèque Swing contient une unique classe appelée DefaultButtonModel qui implémente cette interface.

interface ButtonModel
String getActionCommand()
void setActionCommand(String commande)
Renvoie la chaîne de commande d'action associée à ce bouton. Par défaut, il s'agit tout simplement du libellé du bouton. Toutefois, Il est possible choisir une chaîne de commande et ainsi de préciser l'action désirée.
int getMnemonic()
void setMnemonic(char raccourci)
Le raccourci clavier pour ce bouton.
boolean isArmed()
void setArmed(boolean activation)
Précise si le bouton est pressé et que la souris se trouve toujours au-dessus.
boolean isEnabled()
void setEnabled(boolean activation)
Indique si le bouton peut être sélectionné.
boolean isSelected()
void setSelected(boolean sélection)
Indique si l'état du bouton a été basculé (pour les cases à cocher, les boutons radios et les bouton à deux états).
boolean isPressed()
void setPressed(boolean sélection)
Précise si le bouton de commande a été pressé, mais que le bouton de la souris n'a pas encore été relâché.
boolean isRollover()
void setRollover(boolean sélection)
Précise tout simplement si la souris se trouve au dessus du bouton.

 

Choix du chapitre Le bouton poussoir JButton

La classe abstraite AbstractButton est la classe de base de l'ensemble des composants constituant une sélection ou un choix. Elle possède de nombreuses compétences que nous allons illustrer au travers de notre premier composant concret, celui que nous connaissons déjà bien, JButton.

Cette classe JButton implémente un bouton poussoir. La plupart des proriétés et des méthodes intéressantes sont implémentées par la classe abstraite AbstractButton. Les boutons Swing peuvent comporter une image en plus d'un label. La classe JButton possède des constructeurs acceptant un objet Icon capable de se dessiner lui-même. Vous pouvez créer des boutons dotés de légendes, d'images ou des deux.

JButton()
JButton(String libellé)
JButton(Icon icône)
JButton(String libellé,
Icon icône)
JButton(Action action)

Il est possible de prérégler un bouton au travers d'une action. Nous avons déjà étudié ce concept lors de l'étude de la gestion des événements. Repportez-vous y pour avoir plus de précision.

Comme nous l'avons découvert dans l'étude des fenêtres, la classe conviviale ImageIcon s'occupe de charger une image pour vous et peut être utilisée pour ajouter une image à un bouton.

Mise en oeuvre de notre première application qui prend en compte certaines spécificités des boutons

Nous allons mettre en oeuvre l'application que nous connaissons bien qui permet de faire une conversion entre les €uros et les francs. Sur le bouton qui permet de réaliser la conversion, je propose cette fois-ci deux icônes :

  1. la première est l'icône par défaut qui apparaît dès le départ,
  2. la deuxième apparaît juste au moment où la souris se déplace au-dessus du bouton.

Vous avez ci-dessous les différents états du bouton de conversion :

  1. Etat désactivé :


  2. Etat normal (apparaît dès le début de la saisie sur la zone de gauche) :


  3. Survol de la souris au-dessus du bouton :


Vous remarquez la présence d'un raccourci clavier sur le caractère 'C' du bouton. Le raccourci clavier correspondant est donc <Alt+C>.
.

Code correspondant
package boutons;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.text.*;
import javax.swing.event.*;

public class Conversion extends JFrame {
   private FormatNombre saisie = new FormatNombre(NumberFormat.getCurrencyInstance());
   private FormatNombre résultat = new FormatNombre(new DecimalFormat("#,##0.00 F"));
   private Bouton validation = new Bouton();
   
   public Conversion() {
      super("Conversion €uro -> Francs");
      setLayout(new FlowLayout());
      add(saisie);
      add(validation);
      résultat.setEditable(false);
      add(résultat);
      saisie.addKeyListener(new KeyAdapter() {
         @Override
         public void keyTyped(KeyEvent ev) {
            validation.setEnabled(true);
         }
       });      
      getContentPane().setBackground(Color.YELLOW);
      pack();
      setResizable(false);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }
   
   private class FormatNombre extends JFormattedTextField {
      public FormatNombre(NumberFormat format) {
         super(format);
         setColumns(12);
         setValue(0);
         setHorizontalAlignment(RIGHT);
         setMargin(new Insets(3, 3, 3, 3));
      }    
   }
   
   private class Bouton extends JButton  implements ActionListener, ChangeListener {
      public Bouton() {
         super("Convertion", new ImageIcon("validation.gif"));
         addActionListener(this);
         addChangeListener(this);
         setEnabled(false);
         setFocusPainted(false);
         setRolloverIcon(new ImageIcon("survol.gif"));
         setMnemonic('C');
      }    
      
      public void actionPerformed(ActionEvent e) {
        final double TAUX = 6.55957;
        double €uro = ((Number)saisie.getValue()).doubleValue();
        double franc = €uro * TAUX;
        résultat.setValue(franc);
      }

      public void stateChanged(ChangeEvent e) {
         setForeground(getModel().isRollover() ? Color.RED : Color.BLACK);
      }
   }
   
   public static void main(String[] args)  { new  Conversion(); }   
}
Il est possible, comme nous l'avons évoqué, de préciser le libellé du bouton en format HTML afin d'obtenir la visualisation souhaitée. Dans ce cas là, même si nous sollicitons un raccourci clavier, le symbole du raccourci n'apparaît pas sous la lettre 'C'.

package boutons;
...

public class Conversion extends JFrame {  
...
   private class Bouton extends JButton  implements ActionListener, ChangeListener {
      public Bouton() {
         super("<html><i>Convertion<br>€uro => Franc</i></html>", new ImageIcon("validation.gif"));
         ...
      }    
...
}

 

Choix du chapitre Bouton à bascule JToggleButton

La classe JToggleButton implémente un bouton à bascule : un bouton qui peut être sélectionné ou déselectionné. L'utilisateur peut basculer entre les deux états en cliquant sur le bouton. Comme tous les boutons Swing, un JToggleButton peut afficher du texte et une icône.

L'état de sélection est généralement indiqué par la bordure du bouton et sa couleur de fond. Nous pouvons aussi appeler les méthodes setIcon() et setSelectedIcon() pour spécifier diverses icônes pour les états standard et sélectionné. Comme pour JButton, la classe JToogleButton possède des constructeurs acceptant un objet Icon capable de se dessiner lui-même. Vous pouvez créer des boutons dotés de légendes, d'images ou des deux. Il est également possible de faire en sorte que le bouton soit dans l'état sélectionné dès le départ.

JToggleButton()
JToggleButton(Action action)
JToggleButton(String libellé)
JToggleButton(Icon icône)
JToggleButton(String libellé,
Icon icône)
JToggleButton(String libellé, boolean sélectionné)
JToggleButton(Icon icône, boolean sélectionné)
JToggleButton(String libellé,
Icon icône, boolean sélectionné)

Il est possible de prérégler un bouton au travers d'une action. Nous avons déjà étudié ce concept lors de l'étude de la gestion des événements. Repportez-vous y pour avoir plus de précision.

Par défaut, JToggleButton conserve la trace de son état de sélection avec un objet JToogleButton.ToggleButtonModel. Cette classe JToogleButton.ToggleButtonModel est le bouton ButtonModel utilisé par défaut par les composants JToogleButton, JCheckBox et JRadioButton. Elle surcharge plusieurs méthodes de DefaultButtonModel pour déléguer les informations d'état de sélection du bouton à un objet ButtonGroup (voir plus loin). Les applications n'ont généralement pas besoin d'instancier cette classe.

JToggleButton est moins couramment utilisé que ses sous-classes JCheckBox et JRadioButton.
.

Exploitation de l'action sur un bouton à bascule

Nous aurons souvent besoin d'exploiter un bouton à bascule de deux façons différentes :

  1. En réagissant immédiatement à une action directe sur le bouton,
  2. En cherchant à connaître son état à un instant donné.

Retour sur l'application de conversion

Nous allons permettre à l'application précédente de convertir dans les deux sens. Nous rajoutons pour cela un bouton à bascule qui permet d'effectuer le choix désiré.

Vous avez ci-dessous les deux états possibles correspond à la conversion désirée :

  1. Etat activé permettant la conversion des €uros vers les Francs :


  2. Etat désactivé permettant la conversion des Francs vers les €uros :


Code correspondant
package boutons;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.text.*;
import javax.swing.event.*;

public class Conversion extends JFrame {
   private FormatNombre  = new FormatNombre(NumberFormat.getCurrencyInstance());
   private FormatNombre F = new FormatNombre(new DecimalFormat("#,##0.00 F"));
   private Bouton validation = new Bouton();
   private Bascule choix = new Bascule();
   
   public Conversion() {
      super("Conversion €uro <-> Francs");
      setLayout(new FlowLayout());
      add();
      add(validation);
      add(choix);
      add(F);    
      getContentPane().setBackground(Color.YELLOW);
      pack();
      setResizable(false);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }
   
   private class FormatNombre extends JFormattedTextField {
      public FormatNombre(NumberFormat format) {
         super(format);
         setColumns(12);
         setValue(0);
         setHorizontalAlignment(RIGHT);
         setMargin(new Insets(3, 3, 3, 3));
      }    
   }
   
   private class Bouton extends JButton  implements ActionListener, ChangeListener {
      public Bouton() {
         super("Convertion", new ImageIcon("validation.gif"));
         addActionListener(this);
         addChangeListener(this);
         setFocusPainted(false);
         setRolloverIcon(new ImageIcon("survol.gif"));
         setMnemonic('C');
      }    
      
      public void actionPerformed(ActionEvent e) {
        final double TAUX = 6.55957;
        if (choix.isSelected()) {
           double €uro = ((Number).getValue()).doubleValue();
           double franc = €uro * TAUX;
           F.setValue(franc);
        }
        else {
           double franc = ((Number)F.getValue()).doubleValue();
           double euro = franc / TAUX;
           .setValue(euro);           
        }
      }

      public void stateChanged(ChangeEvent e) {
         setForeground(getModel().isRollover() ? Color.RED : Color.BLACK);
      }
   }
   
   private class Bascule extends JToggleButton implements ActionListener {
      public Bascule() {
         super("€ => F",  new ImageIcon("validation.gif"), true);
         setHorizontalTextPosition(LEFT);
         setFocusPainted(false);
         addActionListener(this);
      }
      
      public void actionPerformed(ActionEvent e) {
         setText(isSelected() ? " € => F" : "€ <= F");
      }      
   }
   
   public static void main(String[] args)  { new  Conversion(); }   
}

Autre application qui permet de choisir la taille d'une image

Dans l'exemple ci-dessous, lorsque l'image apparaît sur la zone principale de la fenêtre, nous pouvons, à l'aide d'un bouton à bascule, choisir si elle doit s'afficher en taille réelle ou au contraire si elle doit être totalement visible quelque soit la dimension de la fenêtre.

Code correspondant



package cadre;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Fenêtre extends JFrame implements ActionListener {
   private JLabel bienvenue = new JLabel("Bienvenue...");
   private JToggleButton changement = new JToggleButton("Dimensions image normale");
   private PanneauImage panneauImage = new PanneauImage();
   private JPanel panneauSud = new JPanel();
   private JPanel panneauCentre = new JPanel();
   
   private String metal = "javax.swing.plaf.metal.MetalLookAndFeel";
   private String motif = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
   private String windows = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
   private String windowsClassic = "com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel";
   
   public Fenêtre() throws Exception {
      setTitle("Transparence");
      setBounds(100, 100, 400, 300);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      
      UIManager.setLookAndFeel(motif);
      
      bienvenue.setFont(new Font("Arial", Font.ITALIC+Font.BOLD, 54));
      bienvenue.setForeground(new Color(0, 255, 0, 96));
      
      panneauCentre.setOpaque(false);
      panneauCentre.add(bienvenue);
      
      changement.addActionListener(this);
      changement.setOpaque(false);
      changement.setFocusPainted(false);
      changement.setForeground(Color.YELLOW);
      
      panneauSud.setOpaque(false);
      panneauSud.add(changement);
      
      panneauImage.setLayout(new BorderLayout());
      panneauImage.add(panneauCentre);
      panneauImage.add(panneauSud, BorderLayout.SOUTH);
      
      add(panneauImage);

      setVisible(true);
   }
    
   public static void main(String[] args) throws Exception {
      new Fenêtre()  ;
   }

   public void actionPerformed(ActionEvent e) {
      if (changement.isSelected()) {
        panneauImage.dimensionAutomatique = false;
        changement.setText("Taille adaptée à la fenêtre");
      }
      else {
        panneauImage.dimensionAutomatique = true;
        changement.setText("Dimensions image normale");          
      }
      panneauImage.repaint();
   }
}

class PanneauImage extends JComponent {
    boolean dimensionAutomatique = true;
    private Image imageFond = new ImageIcon("Cabane dans un champ.jpg").getImage();
    
   @Override
    public void paintComponent(Graphics fond) {
        if (dimensionAutomatique)
           fond.drawImage(imageFond, 0, 0, getWidth(), getHeight(), null);
        else
           fond.drawImage(imageFond, 0, 0, imageFond.getWidth(null), imageFond.getHeight(null), null);
    }
}

 

Choix du chapitre Le groupe de boutons ButtonGroup

Il peut être intéressant de regrouper un ensemble de boutons à bascule ou de boutons radio, de telle sorte que lorsque un bouton est sélectionné, les autres se retrouvent automatiquement désactivés. Les boutons s'excluent alors réciproquement. La classe ButtonGroup permet de réaliser cette fonction et impose une exclusion mutuelle (un comportement de bouton radio) à un groupe de boutons. Une fois les boutons ajoutés à un ButtonGroup par la méthode add(), l'exclusion mutuelle est automatique, sans qu'aucune action ultérieure soit nécessaire.

L'objet ButtonGroup est très ordinaire. On pourrait s'attendre à ce qu'il soit un conteneur ou un composant, amis il n'en est rien ; il s'agit simplement d'un objet assistant qui n'autorise le choix que d'un seul bouton à la fois.

La classe ButtonGroup possède une méthode getSelection() qui renvoie une référence au modèle de bouton (ButtonModel), mais pas le bouton lui-même. L'interface ButtonModel possède à son tour la méthode getActionCommand() qui renvoie alors la commande d'action d'un bouton correspondant au libellé du bouton.

Retour sur l'application de conversion

Cette fois-ci, prévoyons deux boutons pour choisir la conversion désirée, un pour les Francs (activé par défaut) et l'autre pour les €uros. Lorsqu'un bouton est sélectionné l'autre se désactive automatiquement.

Code correspondant

package boutons;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.text.*;
import javax.swing.event.*;

public class Conversion extends JFrame {
   private FormatNombre  = new FormatNombre(NumberFormat.getCurrencyInstance());
   private FormatNombre F = new FormatNombre(new DecimalFormat("#,##0.00 F"));
   private Bouton conversion = new Bouton();
   private ButtonGroup groupe = new ButtonGroup();
   private Bascule choix€ = new Bascule("");
   private Bascule choixF = new Bascule("F");   
   
   public Conversion() {
      super("Conversion €uro <-> Francs");
      setLayout(new FlowLayout());
      add();
      add(conversion);
      add(choix€);
      add(choixF);
      add(F);    
      choixF.setSelected(true);
      getContentPane().setBackground(Color.YELLOW);
      pack();
      setResizable(false);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }
   
   private class FormatNombre extends JFormattedTextField {
      public FormatNombre(NumberFormat format) {
         super(format);
         setColumns(12);
         setValue(0);
         setHorizontalAlignment(RIGHT);
         setMargin(new Insets(3, 3, 3, 3));
      }    
   }
   
   private class Bouton extends JButton  implements ActionListener, ChangeListener {
      public Bouton() {
         super("Convertion", new ImageIcon("validation.gif"));
         addActionListener(this);
         addChangeListener(this);
         setFocusPainted(false);
         setRolloverIcon(new ImageIcon("survol.gif"));
         setMnemonic('C');
      }    
      
      public void actionPerformed(ActionEvent e) {
        final double TAUX = 6.55957;
        if (choixF.isSelected()) {
           double €uro = ((Number).getValue()).doubleValue();
           double franc = €uro * TAUX;
           F.setValue(franc);
        }
        else {
           double franc = ((Number)F.getValue()).doubleValue();
           double euro = franc / TAUX;
           .setValue(euro);           
        }
      }

      public void stateChanged(ChangeEvent e) {
         setForeground(getModel().isRollover() ? Color.RED : Color.BLACK);
      }
   }
   
   private class Bascule extends JToggleButton implements ItemListener {
      public Bascule(String libellé) {
         super(libellé);
         setFocusPainted(false);
         groupe.add(this);
         addItemListener(this);
      }

      public void itemStateChanged(ItemEvent e) {
          setForeground(isSelected() ? Color.RED : Color.BLACK);
      }
   }
   
   public static void main(String[] args)  { new  Conversion(); }   
}

Retour sur les événements : La classe abstraite AbstractButton dispose déjà de tous les ingrédients pour gérer correctement tous les types d'événements. Ainsi, les boutons, qui héritent tous de cette classe de base, récupèrent automatiquement tout ce potentiel.

Jusqu'à présent, nous avons surtout utilisé l'événement de type Action. Dans l'exemple qui précéde, et plus généralement lorsque nous avons une gestion globale des boutons à prendre en compte, grâce à la notion de groupe, l'événement de type Item prend alors toute son importance. Je rappelle que cet événement est sollicité à chaque fois qu'un bouton change d'état, qu'il soit sélectionneé directement ou pas. Ainsi, il est possible d'agir comme dans cet exemple, en temps réel, sur la couleur des libellés des boutons.

 

Choix du chapitre Les cases à cocher JCheckBox

Un petit peu à l'image des boutons à bascule, si vous souhaitez recueillir une réponse qui se limite à une entrée "oui" ou "non", utilisez plutôt une case à cocher. Ce type de composant, qui est représenté par la classe JCheckBox, s'accompagne d'un intitulé qui permet de les identifier. L'utilisateur clique à l'intérieur de la case pour la cocher, et fait de même pour la désactiver.

Pour ajouter/supprimer la coche, l'utilisateur peut également appuyer sur la barre d'espace si le focus d'entrée se trouve dans la case à cocher.
.

L'action de l'utilisateur sur une case à cocher se limite à la modification de sont état : passage de l'état coché à l'état non coché, ou l'inverse. L'état visuel de la case (cochée, non cochée) est gérée automatiquement par les méthodes de la classe JCheckBox. Lorsque nous cochons une case, son état n'est généralement contrôlé qu'ultérieurement, par exemple au moment où l'utilisateur lance une action. Cela ressemble à un peu à un formulaire : nous pouvons mofifier nos choix tant que nous ne l'avons pas envoyé.

Les cases à cocher s'accompagnent d'un libellé qui indique leur rôle. Le texte prévu pour le libellé est passé au constructeur. Par défaut, une case à cocher est construite dans l'état non coché. Nous pouvons lui imposer l'état coché en utilisant une autre version de constructeur.

Comme pour les autres boutons, il est également possible de prévoir une icône à la place du libellé ou avec le libellé. Attention, dans ce cas là, l'image (l'icône) représentant la case à cocher n'existe plus.

Les constructeurs suivant vous offrent toutes les opportunités souhaités :

JCheckBox()
JCheckBox(Action action)
JCheckBox(String libellé)
JCheckBox(Icon icône)
JCheckBox(String libellé,
Icon icône)
JCheckBox(String libellé, boolean sélectionné)
JCheckBox(Icon icône, boolean sélectionné)
JCheckBox(String libellé,
Icon icône, boolean sélectionné)

Il est possible de prérégler un bouton au travers d'une action. Nous avons déjà étudié ce concept lors de l'étude de la gestion des événements. Repportez-vous y pour avoir plus de précision.

Après coup, et indépendamment de l'action de l'utilisateur, nous pouvons, à tout instant, imposer par programme un état donné à l'aide de la méthode setSelected().

Un tel appel de méthode, comme la classe ToogleButton, génère un événement de type Item.
.

De la même façon, nous pouvons connaître à tout instant l'état d'une case à cocher (indépendamment de son éventuel changement d'état provoqué par l'utilisateur) à l'aide de la méthode isSelected().

Comme tout composant, nous ajoutons une case à cocher à un conteneur par la méthode add() de ce dernier.

Retour sur les événements : La classe abstraite AbstractButton dispose déjà de tous les ingrédients pour gérer correctement tous les types d'événements. Ainsi, les boutons, qui héritent tous de cette classe de base, récupèrent automatiquement tout ce potentiel.

Ici aussi, comme pour la classe ToggleButton, nous pouvons aussi bien prendre un événement de type Action qu'un événement de type Item. Par contre, d'après ce que nous avons dit plus haut, beaucoup d'applications ne s'en préoccupent pas, puisque c'est plutôt lors d'une validation par un bouton classique (JButton) et au travers de la méthode isSelected() que l'état des cases à cocher sont réellement identifiées. Ainsi, généralement, la gestion des événements est plutôt différée.

Application sur une horloge numérique

Afin d'illustrer le comportement des cases à cocher, je vous propose de revenir sur une application que nous avons déjà mise en oeuvre. Il s'agit d'une horloge avec un simple affichage numérique. L'affichage de la police de caractères est réglable. Il est possible de demander à avoir les chiffres en italiques et/ou en gras. Dans cette application, dès qu'une case à cocher est sollicité, le changement d'aspect de la police est instantanément répercuté. Pour cela, je prévois une gestion d'événement de type Item.

Code correspondant

package horloge;

import java.awt.*;
import java.awt.event.*;
import java.text.DateFormat;
import java.util.Date;
import javax.swing.*;
import javax.swing.border.*;

public class Boutons extends JFrame implements ActionListener {
   private Timer minuteur = new Timer(1000, this);
   private JLabel heure = new JLabel();
   private CaseACocher caseGras = new CaseACocher("Gras");
   private CaseACocher caseItalique = new CaseACocher("Italique");
   private JPanel panneau = new JPanel();
   
   public Boutons() {
      super("Horloge");     
      heure.setFont(new Font("Arial", Font.BOLD+Font.ITALIC, 32));
      heure.setHorizontalAlignment(JLabel.CENTER);
      heure.setBorder(BorderFactory.createLoweredBevelBorder());
//      heure.setOpaque(true);
//      heure.setBackground(Color.YELLOW);
      add(heure);      
      panneau.add(caseGras);
      panneau.add(caseItalique);
      panneau.setBackground(Color.ORANGE);
      add(panneau, BorderLayout.SOUTH);
      minuteur.start();
      setSize(170, 100);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setResizable(false);
      setVisible(true);
   }   

   public void actionPerformed(ActionEvent e) {
      heure.setText(DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date()));
   }
   
   private class CaseACocher extends JCheckBox implements ItemListener {
      public CaseACocher(String libellé) {
         super(libellé, true);
         setMnemonic(libellé.charAt(0));
         addItemListener(this);
         setOpaque(false);
      }

      public void itemStateChanged(ItemEvent e) {
         int mode = 0;
         mode += caseGras.isSelected() ? Font.BOLD : 0;
         mode += caseItalique.isSelected() ? Font.ITALIC : 0;
         heure.setFont(new Font("Arial", mode, 32));
      }
   }
   
   public static void main(String[] args) {  new Boutons(); }
}

Changement des icônes d'activation et de désactivation

La représentation visuelle des cases à cocher est standard. Nous retrouvons les mêmes apparences quelque soit la technologie utilisée. Il est toutefois possible de personnaliser cet aspect visuel. Comme nous l'avons vu, il suffit de proposer une icône personnelle lorsque la case est non active et une autre lorsque la case est cochée. Vous pouvez en profiter pour en placer une autre qui correspond au passage de la souris au dessus de votre case à cocher.

Voici par exemple ce que nous pouvons obtenir à partir de l'exemple précédent :

Cette fois-ci, les libellés n'existent plus (informations redondantes). Deux icônes seulement représentent les deux cases à cocher.

  1. Dans le cas où elles sont actives, les lettres correspondant à l'action souhaitée apparaissent normalement, respectivement B et I.
  2. Lorsque qu'une case n'est plus active une croix rouge se place alors au dessus de la lettre concernée.
  3. Pour finir, si nous déplaçons le curseur de la souris au dessus de l'une des cases à cocher, la couleur de la lettre devient verte, que cette dernière soit active ou pas.
Partie de code correspondant
...

public class Boutons extends JFrame implements ActionListener {
...
   private CaseACocher caseGras = new CaseACocher("Gras");
   private CaseACocher caseItalique = new CaseACocher("Italique");
   private JPanel panneau = new JPanel();
   
...
   
   private class CaseACocher extends JCheckBox implements ItemListener {
      public CaseACocher(String libellé) {
         super(new ImageIcon(libellé+"Inactif.gif"), true);
         setSelectedIcon(new ImageIcon(libellé+".gif"));
         Icon icône = new ImageIcon(libellé+"Over.gif");
         setRolloverIcon(icône);
         setRolloverSelectedIcon(icône);
         setMnemonic(libellé.charAt(0));
         addItemListener(this);
         setOpaque(false);
      }

      public void itemStateChanged(ItemEvent e) {
         int mode = 0;
         mode += caseGras.isSelected() ? Font.BOLD : 0;
         mode += caseItalique.isSelected() ? Font.ITALIC : 0;
         heure.setFont(new Font("Arial", mode, 32));
      }
   }
   
   public static void main(String[] args) {  new Boutons(); }
}

 

Choix du chapitreLes boutons radio JRadioButton

Dans l'exemple précédent, l'utilisateur pouvait activer ou désactiver une ou plusieurs options, ou aucune. De nombreuses situations exigent que l'utilisateur ne puissent choisir qu'une seule option parmi un ensemble de choix. Lorqu'une seconde option est sélectionnée, la première est désactivée.

Cet ensemble d'options est souvent mis en oeuvre au moyen d'un groupe de boutons radio, représentés par la classe JRadioButton. Ils sont ainsi appelés, car ils fonctionnent de la même manière que les boutons d'une radio : lorsque vous appuyez sur un bouton, celui qui était enfoncé ressort.

Ainsi, comme la case à cocher, le bouton radio permet d'exprimer un choix de type "oui" ou "non". Mais sa vocation est systématiquement de faire partie d'un groupe de boutons, représenté par la classe ButtonGroup, dans lequel une seule option peut être sélectionnée à la fois. Autrement dit, et comme nous l'avons déjà vu, cette classe ButtonGroup fait en sorte que choix d'une option entraine automatiquement la désactivation de l'option choisie précédemment.

Notez qu'un groupe de boutons ne contrôle que le comportement des boutons. Si vous voulez grouper les options pour des raisons de présentation, vous devez les placer dans un conteneur tel que JPanel. Comme tout composant, nous ajoutons un bouton radio à un conteneur par la méthode add() de ce dernier.

Comme pour les cases à cocher, les boutons radio s'accompagnent d'un libellé qui indique un choix possible parmi plusieurs autres. Le texte prévu pour le libellé est passé au constructeur. Par défaut, un bouton radio est construit dans l'état non actif. Nous pouvons lui imposer l'état sélectionné en utilisant une autre version de constructeur. Si vous sollicitez plusieurs boutons radio actifs dès le départ, c'est le dernier construit qui est effectivement sélectionné, puisque normalement un seul bouton radio doit être activé.

Comme pour les autres boutons, il est également possible de prévoir une icône à la place du libellé ou avec le libellé. Attention, dans ce cas là, l'image (l'icône) représentant la case à cocher n'existe plus.

Les constructeurs suivant vous offrent toutes les opportunités souhaités :

JRadioButton()
JRadioButton(Action action)
JRadioButton(String libellé)
JRadioButton(Icon icône)
JRadioButton(String libellé,
Icon icône)
JRadioButton(String libellé, boolean sélectionné)
JRadioButton(Icon icône, boolean sélectionné)
JRadioButton(String libellé,
Icon icône, boolean sélectionné)

Les boutons radio n'ont toutefois pas la même apparence que les cases à cocher, ce qui permet de les différencier. Ces dernières se présentent en standard sous la forme d'un carré qui contient une coche une fois qu'elles sont sélectionnées. Les boutons radio sont ronds et contiennent un point lorsqu'ils sont activés.

Après coup, et indépendamment de l'action de l'utilisateur, nous pouvons, à tout instant, imposer par programme un état donné à l'aide de la méthode setSelected().

Un tel appel de méthode, comme la classe ToogleButton, génère un événement de type Item.
.

De la même façon, nous pouvons connaître à tout instant l'état d'un bouton radio (indépendamment de son éventuel changement d'état provoqué par l'utilisateur) à l'aide de la méthode isSelected().

Retour sur les événements : La classe abstraite AbstractButton dispose déjà de tous les ingrédients pour gérer correctement tous les types d'événements. Ainsi, les boutons, qui héritent tous de cette classe de base, récupèrent automatiquement tout ce potentiel.

Ici aussi, comme pour la classe ToggleButton, nous pouvons aussi bien prendre un événement de type Action qu'un événement de type Item. Par contre, d'après ce que nous avons dit plus haut, beaucoup d'applications ne s'en préoccupent pas, puisque c'est plutôt lors d'une validation par un bouton classique (JButton) et au travers de la méthode isSelected() que l'état des boutons radio sont réellement identifiées. Ainsi, généralement, la gestion des événements est plutôt différée.

Application sur l'horloge numérique

Afin d'illustrer le comportement des boutons radio, je vous propose de revenir sur l'horloge. Cette fois-ci, il est possible de choisir entre l'heure, la date ou les deux. Dans cette application, dès qu'un bouton radio est sollicité, le changement doit s'opérer instantanément. Pour cela, je prévois une gestion d'événement de type Action. Cette fois-ci, l'événement de type Item n'est pas bon choix. Effectivement seul le bouton sélectionné doit proposer l'action désirée. Ici, le changement d'état d'un bouton n'est pas intéressant, seule la sélection est utile.

Code correspondant

package horloge;

import java.awt.*;
import java.awt.event.*;
import java.text.DateFormat;
import java.util.Date;
import javax.swing.*;
import javax.swing.border.*;

public class Boutons extends JFrame implements ActionListener {
   private Timer minuteur = new Timer(1000, this);
   private JLabel heure = new JLabel();
   private ButtonGroup groupe = new ButtonGroup();
   private BoutonRadio choixHeure = new BoutonRadio("Heure");
   private BoutonRadio choixDate = new BoutonRadio("Date");
   private BoutonRadio choixLesDeux = new BoutonRadio("Les deux");
   private JPanel panneau = new JPanel();
   private DateFormat présentation = DateFormat.getTimeInstance(DateFormat.MEDIUM);
   
   public Boutons() {
      super("Horloge");     
      heure.setFont(new Font("Arial", Font.BOLD+Font.ITALIC, 32));
      heure.setHorizontalAlignment(JLabel.CENTER);
      heure.setBorder(BorderFactory.createLoweredBevelBorder());
      add(heure);      
      choixHeure.setSelected(true);
      panneau.add(choixHeure);
      panneau.add(choixDate);
      panneau.add(choixLesDeux);
      panneau.setBackground(Color.ORANGE);
      add(panneau, BorderLayout.SOUTH);
      minuteur.start();
      setSize(380, 100);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setResizable(false);
      setVisible(true);
   }   

   public void actionPerformed(ActionEvent e) {
      heure.setText(présentation.format(new Date()));
   }
   
   private class BoutonRadio extends JRadioButton implements ActionListener {      
      public BoutonRadio(String libellé) {
         super(libellé);
         setMnemonic(libellé.charAt(0));
         addActionListener(this);
         setOpaque(false);
         groupe.add(this);
      }

      public void actionPerformed(ActionEvent e) {
         if (choixHeure.isSelected()) 
            présentation = DateFormat.getTimeInstance(DateFormat.MEDIUM);
         else if (choixDate.isSelected()) 
            présentation = DateFormat.getDateInstance(DateFormat.FULL);
         else if (choixLesDeux.isSelected()) 
            présentation = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM);
         heure.setText(présentation.format(new Date()));
      }
   }
   
   public static void main(String[] args) {  new Boutons(); }
}

 

Choix du chapitre Choix simple ou multiple par une liste de valeurs - JList

En java, JList est un composant qui permet de choisir une ou plusieurs valeurs dans une liste prédéfinie. Comme pour les boutons radio, les listes permettent donc à l'utilisateur d'effectuer un choix parmi plusieurs valeurs. Toutefois, c'est un seul composant qui réalise cette opération. Par ailleurs, elles peuvent être configurées de sorte qu'il soit possible d'effectuer plusieurs choix en une seule fois.

Attention : JList n'hérite pas de AbstractButton.
.

Les éléments de la liste sont généralement des chaînes de caractères. Il est possible toutefois d'utiliser n'importe quel type de composant du moment qu'il hérite justement de la classe Component. Nous pouvons ainsi avoir des éléments de type JLabel, qui permettent donc d'incorporer à la fois du texte et une icône, des éléments qui soient capable d'afficher des images, des éléments qui soient capable d'afficher des dessins, etc. Nous pouvons même envisager d'avoir une liste de chaîne de caractères et d'avoir un rendu (apparence) sous forme de composant spécifique : JLabel, images, dessins, etc.

Sélection d'un élément

La sélection d'un élément ou d'un ensemble d'éléments est classique dans un environnement graphique :

  1. L'utilisateur peut choisir un élément en cliquant dessus.
  2. Il peut étendre sa sélection à une fourchette d'élément en maintenant la touche <Maj> enfoncée tout en cliquant sur un autre élément.
  3. Pour effectuer des sélections discontinues, il utilise la touche <Ctrl> à la place de la touche <Maj>. Sur Mac, c'est la touche <Commande>.

Création d'une boîte de liste

Nous pouvons créer des boîtes de liste à l'aide du constructeur adapté à la situation requise :

  1. JList() : Préparer une liste vide qui sera complété plus tard à la suite d'événements spécifiques à l'aide de la méthode setListData().
  2. JList(Object[] liste) : Construire la boîte au moyen d'un tableau d'objet, comme par exemple un tableau de chaînes de caractères : String[]. Dans ce cas de figure, le nombre d'élément est prédéterminé.
  3. JList(Vector<?> liste) : Nous pouvons aussi construire la boîte de liste au moyen d'un tableau dynamique représenté par la classe Vector<?>. Cette fois-ci, le nombre d'élément n'est pas spécialement fixé.
  4. JList(ListModel modèleDeListe) : La classe JList est conçue, comme les autres, suivant le modèle MVC. Ainsi, il est possible de construire une liste par rapport à une autre en connectant les modèles entre eux au moyen de la méthode getModel() de la première liste déjà construite.

Choix du type de sélection

Il existe trois sortes de boîtes de liste, correspondant chacune à la façon de sélectionner un élément ou un ensemble d'éléments. Il est donc nécessaire, une fois que l'objet JList est construit, de choisir le mode de sélection au travers de la méthode setSelectionMode() en proposant le type approprié délivré par la classe ListSelectionModel :

  1. SINGLE_SELECTION : Sélection d'une seule valeur.
  2. SINGLE_INTERVAL_SELECTION : Sélection d'une seule plage de valeur (contiguës).
  3. MULTIPLE_INTERVAL_SELECTION : Sélection d'un nombre quelconque de plages de valeurs.

Par défaut, nous avons affaire à une boîte de liste de type MULTIPLE_INTERVAL_SELECTION.
.

Pour sélectionner une plage de valeur, comme nous l'avons évoqué plus haut, l'utilisateur doit cliquer sur la première, appuyer sur la touche <Maj> et, tout en la maintenant enfoncée, cliquer sur la dernière valeur de la plage. Pour sélectionner plusieurs plages, il doit procéder de même, tout en maintenant en outre la touche <Ctrl> enfoncée.

Apparence des boîtes de liste

Par défaut, une boîte de liste affiche toutes les options présentes dans la liste dans la mesure de la capacité du conteneur, ce qui la différencie de la boîte combo qui elle, n'affiche qu'une option à la fois (au repos). Bien entendu, nous pouvons modifier ce comportement initial pour que la visualisation de votre liste corresponde à l'apprence souhaitée.

  1. Injection d'une barre de défilement : Il est possible que le nombre d'option soit très grand, et du coup, les dernières ne seront alors plus visibles, ce qui implique que vous ne pourrez plus les atteindre. Pour avoir accès à l'ensemble des options, pensez à proposer un panneau de défilement intermédiaire de type JScrollPane.
  2. Définir un nombre d'élément restraint à afficher : Par défaut, dans la mesure de la capacité du conteneur, un grand nombre d'élément sont directement visibles. Il est possible d'imposer un nombre restraint d'éléments à afficher en même temps, par exemple 3, au moyen de la méthode setVisibleRowCount().
  3. Choisir l'orientation de la liste ou présentation sous forme de tableau : En standard, la boîte de liste s'affiche verticalement, c'est-à-dire que l'ensemble des éléments de la liste s'affiche les uns au dessus des autres. Il est possible de changer cette présentation au travers de la méthode setLayoutOrientation() en proposant la bonne orientation souhaitée, en conjonction éventuellement de la méthode setVisibleRowCount() que nous venons de voir. La classe JList dispose de trois constantes spécifiques à l'orientation : VERTICAL, HORIZONTAL_WRAP et VERTICAL_WRAP :


  4. Influencer le rendu de chaque cellule : La boîte de liste est capable d'afficher directement des chaînes de caractères, ce qui est bien entendu tout à fait normal. Par défaut, c'est d'ailleurs son seul mode de présentation, avec éventuellement les icônes (si elles ne sont pas trop grande). Dans ce dernier cas, il faut utiliser la classe Icon. Aussi, si vous devez afficher, pour chaque cellule, autre chose que ces deux types d'éléments, il faudra lui expliquer comment faire. Cela se fait au travers de la méthode setCellRenderer(). En argument de cette méthode, vous devez alors spécifier un objet qui implémente l'interface ListCellRenderer. Cette interface possède une seule méthode getListCellRendererComponent() que vous êtes donc obligé de redéfinir afin d'indiquer comment doit se faire la présentation souhaités. Voici la signature de cette méthode :

    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus);

    Remarquez bien au passage que cette méthode retourne un Component. Cela signifie que votre objet qui implémente cette interface ListCellRenderer doit également hérité d'une classe enfant de Component.
  5. Imposer des dimensions aux cellules de la liste : Toujours dans la présentation des cellules, il est possible d'imposer une largeur ou une hauteur, au travers respectivement des méthodes setFixedCellWidth() et setFixedCellHeight().
  6. Proposer une couleur particulière pour la sélection : Il est possible de choisir la couleur de fond et la couleur du texte des éléments sélectionnés au travers des méthodes setSelectionBackground() et setSelectionForeground().

Imposer une sélection

Initialement, aucune valeur n'est sélectionnée dans la liste. Le cas échéant, nous pouvons forcer la sélection :

  1. d'un élément de rang donné par la méthode setSelectedIndex(int).
  2. d'un ensemble d'éléments en proposant les indices concernés au travers d'un tableau et avec la méthode setSelectedIndices(int[]).
  3. d'une valeur particulière au travers de la méthode setSelectedValue(Object, boolean). La valeur booléenne passée en argument, si elle est valide, demande un déplacement de l'ascenceur pour que la sélection devienne visible dans la boîte de liste.
  4. d'un ensemble d'éléments délimité par un inteval d'indices au travers de la méthode setSelectionInterval(int, int).

Accès aux informations sélectionnées

Bien entendu, si nous avons une liste de choix, c'est ultérieurement pour récupérer les valeurs sélectionnées afin de réaliser le traitement souhaité :

  1. Pour une liste à sélection simple, la méthode getSelectedValue() fournit la (seule) chaîne de caractères sélectionnée. On notera que son résultat est de type Object et non String. Il faudra donc procéder à une conversion explicite.
  2. Pour les autres types de liste, la méthode