37. L'interception des actions de l'utilisateur 39. Le développement d'interfaces graphiques avec SWT Imprimer Sommaire Consulter avec table des matières
Développons en Java   v 2.10  
Copyright (C) 1999-2016 .  

 

38. Le développement d'interfaces graphiques avec SWING

 

chapitre 3 8

 

Niveau : niveau 3 Intermédiaire 

 

Swing fait partie de la bibliothèque Java Foundation Classes (JFC). C'est une API dont le but est similaire à celui de l'API AWT mais dont les modes de fonctionnement et d'utilisation sont complètement différents. Swing a été intégré au JDK depuis sa version 1.2. Cette bibliothèque existe séparément. pour le JDK 1.1.

La bibliothèque JFC contient :

Ce chapitre contient plusieurs sections :

 

38.1. La présentation de Swing

Swing propose de nombreux composants dont certains possèdent des fonctions étendues, une utilisation des mécanismes de gestion d'événements performants (ceux introduits par le JDK 1.1) et une apparence modifiable à la volée (une interface graphique qui emploie le style du système d'exploitation Windows ou Motif ou un nouveau style spécifique à Java nommé Metal).

Tous les éléments de Swing font partie d'un package qui a changé plusieurs fois de nom : le nom du package dépend de la version du J.D.K. utilisée :

Les composants Swing forment un nouvelle hiérarchie parallèle à celle de l'AWT. L'ancêtre de cette hiérarchie est le composant JComponent. Presque tous ses composants sont écrits en pur Java : ils ne possèdent aucune partie native sauf ceux qui assurent l'interface avec le système d'exploitation : JApplet, JDialog, JFrame, et JWindow. Cela permet aux composants de toujours avoir la même apparence quelque soit le système sur lequel l'application s'exécute.

Tous les composants Swing possèdent les caractéristiques suivantes :

La procédure à suivre pour utiliser un composant Swing est identique à celle des composants de la bibliothèque AWT : créer le composant en appelant son constructeur, appeler les méthodes du composant si nécessaire pour le personnaliser et l'ajouter dans un conteneur.

Swing utilise la même infrastructure de classes qu'AWT, ce qui permet de mélanger des composants Swing et AWT dans la même interface. Il est toutefois recommandé d'éviter de les utiliser simultanément car certains peuvent ne pas être restitués correctement.

Les composants Swing utilisent des modèles pour contenir leurs états ou leurs données. Ces modèles sont des classes particulières qui possèdent toutes un comportement par défaut.

 

38.2. Les packages Swing

Swing contient plusieurs packages :

javax.swing

package principal : il contient les interfaces, les principaux composants, les modèles par défaut

javax.swing.border

Classes représentant les bordures

javax.swing.colorchooser

Classes définissant un composant pour la sélection de couleurs

javax.swing.event

Classes et interfaces pour les événements spécifiques à Swing. Les autres événements sont ceux d'AWT (java.awt.event)

javax.swing.filechooser

Classes définissant un composant pour la sélection de fichiers

javax.swing.plaf

Classes et interfaces génériques pour gérer l'apparence

javax.swing.plaf.basic

Classes et interfaces de base pour gérer l'apparence

javax.swing.plaf.metal

Classes et interfaces pour définir l'apparence Metal qui est l'apparence par défaut

javax.swing.table

Classes définissant un composant pour la présentation de données sous forme de tableau

javax.swing.text

Classes et interfaces de bases pour les composants manipulant du texte

javax.swing.text.html

Classes permettant le support du format HTML

javax.swing.text.html.parser

Classes permettant d'analyser des données au format HTML

javax.swing.text.rtf

Classes permettant le support du format RTF

javax.swing.tree

Classes définissant un composant pour la présentation de données sous forme d'arbre

javax.swing.undo

Classes permettant d'implémenter les fonctions annuler/refaire

 

38.3. Un exemple de fenêtre autonome

La classe de base d'une application est la classe JFrame. Son rôle est équivalent à la classe Frame de l'AWT et elle s'utilise de la même façon.

Exemple ( code Java 1.1 ) :
import javax.swing.*;
import java.awt.event.*;

public class swing1 extends JFrame {

   public swing1() {
      super("titre de l'application");

      WindowListener l = new WindowAdapter() {
         public void windowClosing(WindowEvent e){
            System.exit(0);
         }
      };

      addWindowListener(l);
      setSize(200,100);
      setVisible(true);
   }

   public static void main(String [] args){
      JFrame frame = new swing1();
   }
}

 

38.4. Les composants Swing

Il existe des composants Swing équivalents pour chacun des composants AWT avec des constructeurs semblables. De nombreux constructeurs acceptent comme argument un objet de type Icon, qui représente une petite image généralement stockée au format Gif.

Le constructeur d'un objet Icon admet comme seul paramètre le nom ou l'URL d'un fichier graphique

Exemple ( code Java 1.1 ) :
import javax.swing.*;
import java.awt.event.*;

public class swing3 extends JFrame {

  public swing3() {

     super("titre de l'application");

      WindowListener l = new WindowAdapter() {
         public void windowClosing(WindowEvent e){
            System.exit(0);
         }
      };

      addWindowListener(l);

      ImageIcon img = new ImageIcon("tips.gif");
      JButton bouton = new JButton("Mon bouton",img);

      JPanel panneau = new JPanel();
      panneau.add(bouton);
      setContentPane(panneau);
      setSize(200,100);
      setVisible(true);
   }

  public static void main(String [] args){
      JFrame frame = new swing3();
  }
}

 

38.4.1. La classe JFrame

JFrame est l'équivalent de la classe Frame de l'AWT : les principales différences sont l'utilisation du double buffering qui améliore les rafraichissements et l'utilisation d'un panneau de contenu (contentPane) pour insérer des composants (ils ne sont plus insérés sans le JFrame mais dans l'objet contentPane qui lui est associé). Elle représente une fenêtre principale qui possède un titre, une taille modifiable et éventuellement un menu.

La classe possède plusieurs constructeurs :

Constructeur

Rôle

JFrame()

 

JFrame(String)

Création d'une instance en précisant le titre


Par défaut, la fenêtre créée n'est pas visible. La méthode setVisible() permet de l'afficher.

Exemple ( code Java 1.1 ) :
import javax.swing.*;

public class TestJFrame1 {

  public static void main(String argv[]) {
    JFrame f = new JFrame("ma fenetre");
    f.setSize(300,100);
    f.setVisible(true);
  }
}

La gestion des événements est identique à celle utilisée dans l'AWT depuis le J.D.K. 1.1.

Exemple ( code Java 1.1 ) :
import javax.swing.*;
import java.awt.event.*;

public class swing2 extends JFrame {

   public swing2() {

     super("titre de l'application");

      WindowListener l = new WindowAdapter() {
         public void windowClosing(WindowEvent e){
           System.exit(0);
         }
      };
     addWindowListener(l);

     JButton bouton = new JButton("Mon bouton");
     JPanel panneau = new JPanel();
     panneau.add(bouton);

     setContentPane(panneau);
     setSize(200,100);
     setVisible(true);
   }

   public static void main(String [] args){
      JFrame frame = new swing2();
   }
}

Tous les composants associés à un objet JFrame sont gérés par un objet de la classe JRootPane. Un objet JRootPane contient plusieurs Panes. Tous les composants ajoutés au JFame doivent être ajoutés à un des Pane du JRootPane et non au JFrame directement. C'est aussi à un de ces Panes qu'il faut associer un layout manager si nécessaire.

Le Pane le plus utilisé est le ContentPane. Le Layout manager par défaut du contentPane est BorderLayout. Il est possible de le changer :

Exemple ( code Java 1.1 ) :
...
f.getContentPane().setLayout(new FlowLayout());
...

Exemple ( code Java 1.1 ) :
import javax.swing.*;

public class TestJFrame2 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300,100);
    JButton b =new JButton("Mon bouton");
    f.getContentPane().add(b);
    f.setVisible(true);
  }
}

Le JRootPane se compose de plusieurs éléments :

Le glassPane est un JPanel transparent qui se situe au-dessus du layeredPane. Le glassPane peut être n'importe quel composant : pour le modifier il faut utiliser la méthode setGlassPane() en fournissant le composant en paramètre.

Le layeredPane regroupe le contentPane et le menuBar.

Le contentPane est par défaut un JPanel opaque dont le gestionnaire de présentation est un BorderLayout. Ce panel peut être remplacé par n'importe quel composant grâce à la méthode setContentPane().

stop Attention : il ne faut pas utiliser directement la méthode setLayout() d'un objet JFrame sinon une exception est levée.

Exemple ( code Java 1.1 ) :
import javax.swing.*;
import java.awt.*;

public class TestJFrame7 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setLayout(new FlowLayout());
    f.setSize(300,100);
    f.setVisible(true);
  }
}

Résultat :
C:\swing\code>java TestJFrame7
Exception in thread "main" java.lang.Error: Do not use javax.swing.JFrame.setLay
out() use javax.swing.JFrame.getContentPane().setLayout() instead
        at javax.swing.JFrame.createRootPaneException(Unknown Source)
        at javax.swing.JFrame.setLayout(Unknown Source)
        at TestJFrame7.main(TestJFrame7.java:8)

Le menuBar permet d'attacher un menu à la JFrame. Par défaut, le menuBar est vide. La méthode setJMenuBar() permet d'affecter un menu à la JFrame.

Exemple ( code Java 1.1 ) : Création d'un menu très simple
import javax.swing.*;
import java.awt.*;

public class TestJFrame6 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300,100);
    JButton b =new JButton("Mon bouton"); 
    f.getContentPane().add(b);

    JMenuBar menuBar = new JMenuBar();
    f.setJMenuBar(menuBar);

    JMenu menu = new JMenu("Fichier");
    menu.add(menuItem);
    menuBar.add(menu);

    f.setVisible(true);
  }
}

 

38.4.1.1. Le comportement par défaut à la fermeture

Il est possible de préciser comment un objet JFrame, JInternalFrame, ou JDialog réagit à sa fermeture grâce à la méthode setDefaultCloseOperation(). Cette méthode attend en paramètre une valeur qui peut être :

Constante

Rôle

WindowConstants.DISPOSE_ON_CLOSE

détruit la fenêtre

WindowConstants.DO_NOTHING_ON_CLOSE

rend le bouton de fermeture inactif

WindowConstants.HIDE_ON_CLOSE

cache la fenêtre


Cette méthode ne permet pas d'associer d'autres traitements. Dans ce cas, il faut intercepter l'événement et lui associer les traitements.

Exemple ( code Java 1.1 ) : la fenêtre disparaît lors de sa fermeture mais l'application ne se termine pas.
import javax.swing.*;

public class TestJFrame3 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300,100);
    JButton b =new JButton("Mon bouton"); 
    f.getContentPane().add(b);

    f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

    f.setVisible(true);
  }
}

 

38.4.1.2. La personnalisation de l'icône

La méthode setIconImage() permet de modifier l'icône de la JFrame.

Exemple ( code Java 1.1 ) :
import javax.swing.*;

public class TestJFrame4 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300,100);
    JButton b =new JButton("Mon bouton"); 
    f.getContentPane().add(b);
    f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

    ImageIcon image = new ImageIcon("book.gif");
    f.setIconImage(image.getImage());
    f.setVisible(true);

  }
}

Si l'image n'est pas trouvée, alors l'icône est vide. Si l'image est trop grande, elle est redimensionnée.

 

38.4.1.3. Centrer une JFrame à l'écran

Par défaut, une JFrame est affichée dans le coin supérieur gauche de l'écran. Pour la centrer dans l'écran, il faut procéder comme pour une Frame : déterminer la position de la Frame en fonction de sa dimension et de celle de l'écran et utiliser la méthode setLocation() pour affecter cette position.

Exemple ( code Java 1.1 ) :
import javax.swing.*;
import java.awt.*;

public class TestJFrame5 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300,100);
    JButton b =new JButton("Mon bouton"); 
    f.getContentPane().add(b);

    f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
    f.setLocation(dim.width/2 - f.getWidth()/2, dim.height/2 - f.getHeight()/2);

    f.setVisible(true);
  }
}

 

38.4.1.4. Les événements associées à un JFrame

La gestion des événements associés à un objet JFrame est identique à celle utilisée pour un objet de type Frame de AWT.

Exemple ( code Java 1.1 ) :
import javax.swing.*;
import java.awt.event.*;

public class TestJFrame8 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300,100);
    f.setVisible(true);
    f.addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }        
      }
    );

  }
}

 

38.4.2. Les étiquettes : la classe JLabel

Le composant JLabel propose les mêmes fonctionnalités que les intitulés AWT mais ils peuvent en plus contenir des icônes .

Cette classe possède plusieurs constructeurs :

Constructeurs

Rôle

JLabel()

Création d'une instance sans texte ni image

JLabel(Icon)

Création d'une instance en précisant l'image

JLabel(Icon, int)

Création d'une instance en précisant l'image et l'alignement horizontal

JLabel(String)

Création d'une instance en précisant le texte

JLabel(String, Icon, int)

Création d'une instance en précisant le texte, l'image et l'alignement horizontal

JLabel(String, int)

Création d'une instance en précisant le texte et l'alignement horizontal


Le composant JLabel permet d'afficher un texte et/ou une icône en précisant leur alignement. L'icône doit être au format GIF et peut être une animation dans ce format.

Exemple ( code Java 1.1 ) :
import javax.swing.*;
import java.awt.*;

public class TestJLabel1 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(100,200);

    JPanel pannel = new JPanel(); 
    JLabel jLabel1 =new JLabel("Mon texte dans JLabel"); 
    pannel.add(jLabel1);

    ImageIcon icone = new ImageIcon("book.gif");
    JLabel jLabel2 =new JLabel(icone); 
    pannel.add(jLabel2);

    JLabel jLabel3 =new JLabel("Mon texte",icone,SwingConstants.LEFT); 
    pannel.add(jLabel3);

    f.getContentPane().add(pannel);
    f.setVisible(true);
  }
}

La classe JLabel définit plusieurs méthodes pour modifier l'apparence du composant :

Méthodes

Rôle

setText()

Permet d'initialiser ou de modifier le texte affiché

setOpaque()

Indique si le composant est transparent (paramètre false) ou opaque (true)

setBackground()

Indique la couleur de fond du composant (setOpaque doit être à true)

setFont()

Permet de préciser la police du texte

setForeGround()

Permet de préciser la couleur du texte

setHorizontalAlignment()

Permet de modifier l'alignement horizontal du texte et de l'icône

setVerticalAlignment()

Permet de modifier l'alignement vertical du texte et de l'icône

setHorizontalTextAlignment()

Permet de modifier l'alignement horizontal du texte uniquement

setVerticalTextAlignment()

Permet de modifier l'alignement vertical du texte uniquement

Exemple :

jLabel.setVerticalTextPosition(SwingConstants.TOP);

setIcon()

Permet d'assigner une icône

setDisabledIcon()

Permet de définir l'icône associée au JLabel lorsqu'il est désactivé


L'alignement vertical par défaut d'un JLabel est centré. L'alignement horizontal par défaut est soit à droite s'il ne contient que du texte, soit centré s'il contient une image avec ou sans texte. Pour modifier cet alignement, il suffit d'utiliser les méthodes ci-dessus en utilisant des constantes en paramètres : SwingConstants.LEFT, SwingConstants.CENTER, SwingConstants.RIGHT, SwingConstants.TOP, SwingConstants.BOTTOM

Par défaut, un JLabel est transparent : son fond n'est pas dessiné. Pour le dessiner, il faut utiliser la méthode setOpaque() :

Exemple ( code Java 1.1 ) :
import javax.swing.*;
import java.awt.*;

public class TestJLabel2 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");

    f.setSize(100,200);
    JPanel pannel = new JPanel(); 

    JLabel jLabel1 =new JLabel("Mon texte dans JLabel 1"); 
    jLabel1.setBackground(Color.red);
    pannel.add(jLabel1);

    JLabel jLabel2 =new JLabel("Mon texte dans JLabel 2"); 
    jLabel2.setBackground(Color.red);
    jLabel2.setOpaque(true);
    pannel.add(jLabel2);

    f.getContentPane().add(pannel);
    f.setVisible(true);
  }
}

Dans l'exemple, les 2 JLabel ont le fond rouge demandé par la méthode setBackground(). Seul le deuxième affiche un fond rouge car il est rendu opaque avec la méthode setOpaque().

Il est possible d'associer un raccourci clavier au JLabel qui permet de donner le focus à un autre composant. La méthode setDisplayedMnemonic() permet de définir le raccourci clavier. Celui-ci sera activé en utilisant la touche Alt avec le caractère fourni en paramètre. La méthode setLabelFor() permet d'associer le composant fourni en paramètre au raccourci.

Exemple ( code Java 1.1 ) :
import javax.swing.*;
import java.awt.*;

public class TestJLabel3 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300,100);
    JPanel pannel = new JPanel(); 

    JButton bouton = new JButton("saisir");
    pannel.add(bouton);

    JTextField jEdit = new JTextField("votre nom");

    JLabel jLabel1 =new JLabel("Nom : "); 
    jLabel1.setBackground(Color.red);
    jLabel1.setDisplayedMnemonic('n');
    jLabel1.setLabelFor(jEdit);
    pannel.add(jLabel1);
    pannel.add(jEdit);

    f.getContentPane().add(pannel);
    f.setVisible(true);
  }
}

Dans l'exemple, à l'ouverture de la fenêtre, le focus est sur le bouton. Un appui sur Alt+'n' donne le focus au champ de saisie.

 

38.4.3. Les panneaux : la classe JPanel

La classe JPanel est un conteneur utilisé pour regrouper et organiser des composants grâce à un gestionnaire de présentation (layout manager). Le gestionnaire par défaut d'un JPanel est un objet de la classe FlowLayout.

 

38.5. Les boutons

Il existe plusieurs boutons définis par Swing.

 

38.5.1. La classe AbstractButton

C'est une classe abstraite dont héritent les boutons Swing JButton, JMenuItem et JToggleButton.

Cette classe définit de nombreuses méthodes dont les principales sont :

Méthode

Rôle

AddActionListener

Associer un écouteur sur un événement de type ActionEvent

AddChangeListener

Associer un écouteur sur un événement de type ChangeEvent

AddItemListener

Associer un écouteur sur un événement de type ItemEvent

doClick()

Déclencher un clic par programmation

getText()

Obtenir le texte affiché par le composant

setDisabledIcon()

Associer une icône affichée lorsque le composant a l'état désélectionné

setDisabledSelectedIcon()

Associer une icône affichée lors du passage de la souris sur le composant à l'état désélectionné

setEnabled()

Activer/désactiver le composant

setMnemonic() 

Associer un raccourci clavier

setPressedIcon()

Associer une icône affichée lorsque le composant est cliqué

setRolloverIcon()

Associer une icône affichée lors du passage de la souris sur le composant

setRolloverSelectedIcon()

Associer une icône affichée lors du passage de la souris sur le composant à l'état sélectionné

setSelectedIcon()

Associer une icône affichée lorsque le composant a l'état sélectionné

setText()

Mettre à jour le texte du composant

isSelected()

Indiquer si le composant est dans l'état sélectionné

setSelected()

Définir l'état du composant (sélectionné ou non selon la valeur fournie en paramètre


Tous les boutons peuvent afficher du texte et/ou une image.

Il est possible de préciser une image différente lors du passage de la souris sur le composant et lors de l'enfoncement du bouton : dans ce cas, il faut créer trois images pour chacun des états (normal, enfoncé et survolé). L'image normale est associée au bouton grâce au constructeur, l'image enfoncée grâce à la méthode setPressedIcon() et l'image lors d'un survol grâce à la méthode setRolloverIcon(). Il suffit enfin d'appeler la méthode setRolloverEnable() avec en paramètre la valeur true.

Exemple ( code Java 1.1 ) :
import javax.swing.*;
import java.awt.event.*;

public class swing4 extends JFrame {

   public swing4() {
     super("titre de l'application");

     WindowListener l = new WindowAdapter() {
       public void windowClosing(WindowEvent e){
         System.exit(0);
       }
     };
     addWindowListener(l);

     ImageIcon imageNormale = new ImageIcon("arrow.gif");
     ImageIcon imagePassage =  new ImageIcon("arrowr.gif");
     ImageIcon imageEnfoncee = new ImageIcon("arrowy.gif");

     JButton bouton = new JButton("Mon bouton",imageNormale);
     bouton.setPressedIcon(imageEnfoncee);
     bouton.setRolloverIcon(imagePassage);
     bouton.setRolloverEnabled(true);
     getContentPane().add(bouton, "Center");

     JPanel panneau = new JPanel();
     panneau.add(bouton);
     setContentPane(panneau);
     setSize(200,100);
     setVisible(true);
   }

   public static void main(String [] args){
      JFrame frame = new swing4();
   }
}

Un bouton peut recevoir des événements de type ActionEvents (le bouton a été activé), ChangeEvents, et ItemEvents.

Exemple ( code Java 1.1 ) : fermeture de l'application lors de l'activation du bouton
import javax.swing.*;
import java.awt.event.*;

public class TestJButton3 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300,100);
    JPanel pannel = new JPanel(); 

    JButton bouton1 = new JButton("Bouton1");
    bouton1.addActionListener( new ActionListener() {
       public void actionPerformed(ActionEvent e) {
         System.exit(0);
       }
    }
    );

    pannel.add(bouton1);
    f.getContentPane().add(pannel);
    f.setVisible(true);
  }
}

Pour de plus amples informations sur la gestion des événements, voir le chapitre correspondant.

 

38.5.2. La classe JButton

JButton est un composant qui représente un bouton : il peut contenir un texte et/ou une icône.

Les constructeurs sont :

Constructeur Rôle
JButton()  
JButton(String) préciser le texte du bouton
JButton(Icon) préciser une icône
JButton(String, Icon) préciser un texte et une icône

Il ne gère pas d'état. Toutes les indications concernant le contenu du composant JLabel sont valables pour le composant JButton.

Exemple ( code Java 1.1 ) : un bouton avec une image
import javax.swing.*;
import java.awt.event.*;

public class swing3 extends JFrame {

   public swing3() {

      super("titre de l'application");

      WindowListener l = new WindowAdapter() {
         public void windowClosing(WindowEvent e){
            System.exit(0);
         }
      };
      addWindowListener(l);

      ImageIcon img = new ImageIcon("tips.gif");
      JButton bouton = new JButton("Mon bouton",img);

      JPanel panneau = new JPanel();
      panneau.add(bouton);
      setContentPane(panneau);
      setSize(200,100);
      setVisible(true);
  }

  public static void main(String [] args){
      JFrame frame = new swing3();
  }

}

L'image gif peut être une animation.

Dans un conteneur de type JRootPane, il est possible de définir un bouton par défaut grâce à sa méthode setDefaultButton().

Exemple ( code Java 1.1 ) : définition d'un bouton par défaut dans un JFrame
import javax.swing.*;
import java.awt.*;

public class TestJButton2 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300,100);
    JPanel pannel = new JPanel(); 
    JButton bouton1 = new JButton("Bouton 1");
    pannel.add(bouton1);

    JButton bouton2 = new JButton("Bouton 2");
    pannel.add(bouton2);

    JButton bouton3 = new JButton("Bouton 3");
    pannel.add(bouton3);

    f.getContentPane().add(pannel);
    f.getRootPane().setDefaultButton(bouton3);
    f.setVisible(true);
  }
}

Le bouton par défaut est activé par un appui sur la touche Entrée alors que le bouton actif est activé par un appui sur la barre d'espace.

La méthode isDefaultButton() de JButton permet de savoir si le composant est le bouton par défaut.

 

38.5.3. La classe JToggleButton

Cette classe définit un bouton à deux états : c'est la classe mère des composants JCheckBox et JRadioButton.

La méthode setSelected() héritée de AbstractButton permet de mettre à jour l'état du bouton. La méthode isSelected() permet de connaître cet état.

 

38.5.4. La classe ButtonGroup

La classe ButtonGroup permet de gérer un ensemble de boutons en garantissant qu'un seul bouton du groupe sera sélectionné.

Pour utiliser la classe ButtonGroup, il suffit d'instancier un objet et d'ajouter des boutons (objets héritant de la classe AbstractButton) grâce à la méthode add(). Il est préférable d'utiliser des objets de la classe JToggleButton ou d'une de ses classes filles car elles sont capables de gérer leurs états.

Exemple ( code Java 1.1 ) :
import javax.swing.*;

public class TestGroupButton1 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300,100);
    JPanel pannel = new JPanel();

    ButtonGroup groupe = new ButtonGroup();
    JRadioButton bouton1 = new JRadioButton("Bouton 1");
    groupe.add(bouton1);
    pannel.add(bouton1);
    JRadioButton bouton2 = new JRadioButton("Bouton 2");
    groupe.add(bouton2);
    pannel.add(bouton2);
    JRadioButton bouton3 = new JRadioButton("Bouton 3");
    groupe.add(bouton3);
    pannel.add(bouton3);

    f.getContentPane().add(pannel);
    f.setVisible(true);
  }
}

 

38.5.5. Les cases à cocher : la classe JCheckBox

Les constructeurs sont les suivants :

Constructeur

Rôle

JCheckBox(String)

précise l'intitulé

JCheckBox(String, boolean)

précise l'intitulé et l'état

JCheckBox(Icon)

spécifie l'icône utilisée

JCheckBox(Icon, boolean)

précise l'intitulé et l'état du bouton

JCheckBox(String, Icon)

précise l'intitulé et l'icône

JCheckBox(String, Icon, boolean)

précise l'intitulé, une icône et l'état


Un groupe de cases à cocher peut être défini avec la classe ButtonGroup. Dans ce cas, un seul composant du groupe peut être sélectionné. Pour l'utiliser, il faut créer un objet de la classe ButtonGroup et utiliser la méthode add() pour ajouter un composant au groupe.

Exemple ( code Java 1.1 ) :
import javax.swing.*;

public class TestJCheckBox1 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300,100);
    JPanel pannel = new JPanel();

    JCheckBox bouton1 = new JCheckBox("Bouton 1");
    pannel.add(bouton1);
    JCheckBox bouton2 = new JCheckBox("Bouton 2");
    pannel.add(bouton2);
    JCheckBox bouton3 = new JCheckBox("Bouton 3");
    pannel.add(bouton3);

    f.getContentPane().add(pannel);
    f.setVisible(true);
  }
}

 

38.5.6. Les boutons radio : la classe JRadioButton

Un objet de type JRadioButton représente un bouton radio d'un groupe de boutons . A un instant donné, un seul des boutons radio associés à un même groupe peut être sélectionné. La classe JRadioButton hérite de la classe AbstractButton.

Un bouton radio possède un libellé et éventuellement une icône qui peut être précisée, pour chacun des états du bouton, en utilisant les méthodes setIcon(), setSelectedIcon() et setPressedIcon().

Exemple ( code Java 1.1 ) :
import javax.swing.*;

public class TestJRadioButton1 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300,100);
    JPanel pannel = new JPanel();
    JRadioButton bouton1 = new JRadioButton("Bouton 1");
    pannel.add(bouton1);
    JRadioButton bouton2 = new JRadioButton("Bouton 2");
    pannel.add(bouton2);
    JRadioButton bouton3 = new JRadioButton("Bouton 3");
    pannel.add(bouton3);

    f.getContentPane().add(pannel);
    f.setVisible(true);
  }
}

La méthode isSelected() permet de savoir si le bouton est sélectionné ou non.

La classe JRadioButton possède plusieurs constructeurs :

Constructeur

Rôle

JRadioButton()

Créer un bouton non sélectionné sans libellé

JRadioButton(Icon)

Créer un bouton non sélectionné sans libellé avec l'icône fournie en paramètre

JRadioButton(Icon, boolean)

Créer un bouton sans libellé avec l'icône et l'état fournis en paramètres

JRadioButton(String)

Créer un bouton non sélectionné avec le libellé fourni en paramètre

JRadioButton(String, boolean)

Créer un bouton avec le libellé et l'état fournis en paramètres

JRadioButton(String, Icon)

Créer un bouton non sélectionné avec le libellé et l'icône fournis en paramètres

JRadioButton(String, Icon, boolean)

Créer un bouton avec le libellé, l'icône et l'état fournis en paramètres


Un groupe de boutons radio est encapsulé dans un objet de type ButtonGroup.

Il faut ajouter tous les JRadioButton du groupe en utilisant la méthode add() de la classe ButtonGroup. Lors de la sélection d'un bouton, c'est l'objet de type ButtonGroup qui se charge de déselectionner le bouton précédemment sélectionné dans le groupe.

Un groupe n'a pas l'obligation d'avoir un bouton sélectionné.

Exemple :
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.GridLayout;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.border.Border;

public class TestJRadioButton extends JFrame {
  public static void main(String args[]) {
    TestJRadioButton app = new TestJRadioButton();
    app.init();
  }

  public void init() {
    this.setTitle("Test radio boutons");
   
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    JPanel panel = new JPanel(new GridLayout(0,1));
    Border border = BorderFactory.createTitledBorder("Sélection");
    panel.setBorder(border);
    ButtonGroup group = new ButtonGroup();
    JRadioButton radio1 = new JRadioButton("Choix 1", true);
    JRadioButton radio2 = new JRadioButton("Choix 2");
    JRadioButton radio3 = new JRadioButton("Choix 3");
    group.add(radio1);
    panel.add(radio1);
    group.add(radio2);
    panel.add(radio2);
    group.add(radio3);
    panel.add(radio3);
    Container contentPane = this.getContentPane();
    contentPane.add(panel, BorderLayout.CENTER);
    this.setSize(300, 150);
    this.setVisible(true);
  }
}

Exemple :
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.KeyEvent;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.border.Border;

public class TestJRadioButton extends JFrame {
  public static void main(String args[]) {
    TestJRadioButton app = new TestJRadioButton();
    app.init();
  }

  public void init() {
    this.setTitle("Test radio boutons");
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    JPanel panel = new JPanel(new GridLayout(0, 1));
    Border border = BorderFactory.createTitledBorder("Sélection");
    panel.setBorder(border);
    
    ButtonGroup group = new ButtonGroup();
    JRadioButton radio1 = new JRadioButton("Choix 1");
    radio1.setMnemonic(KeyEvent.VK_1);
    radio1.setActionCommand("Choix_1");
    radio1.setSelected(true);
    
    JRadioButton radio2 = new JRadioButton("Choix 2");
    radio2.setMnemonic(KeyEvent.VK_2);
    radio2.setActionCommand("Choix_2");
    
    JRadioButton radio3 = new JRadioButton("Choix 3");
    radio3.setMnemonic(KeyEvent.VK_3);
    radio3.setActionCommand("Choix_3");
    
    group.add(radio1);
    panel.add(radio1);
    group.add(radio2);
    panel.add(radio2);
    group.add(radio3);
    panel.add(radio3);
    
    Container contentPane = this.getContentPane();
    contentPane.add(panel, BorderLayout.CENTER);
    this.setSize(300, 150);
    this.setVisible(true);
  }
}

Lors de la sélection d'un bouton du groupe, il y a plusieurs événements qui peuvent être émis :

Exemple :
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.border.Border;

public class TestJRadioButton extends JFrame implements ActionListener, ItemListener {
  public static void main(String args[]) {
    TestJRadioButton app = new TestJRadioButton();
    app.init();
  }

  public void init() {
    this.setTitle("Test radio boutons");
   
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    JPanel panel = new JPanel(new GridLayout(0, 1));
    Border border = BorderFactory.createTitledBorder("Sélection");
    panel.setBorder(border);
    ButtonGroup group = new ButtonGroup();
    
    JRadioButton radio1 = new JRadioButton("Choix 1");
    radio1.setMnemonic(KeyEvent.VK_1);
    radio1.setActionCommand("Choix_1");
    radio1.setSelected(true);
    
    JRadioButton radio2 = new JRadioButton("Choix 2");
    radio2.setMnemonic(KeyEvent.VK_2);
    radio2.setActionCommand("Choix_2");
    
    JRadioButton radio3 = new JRadioButton("Choix 3");
    radio3.setMnemonic(KeyEvent.VK_3);
    radio3.setActionCommand("Choix_3");
    
    group.add(radio1);
    panel.add(radio1);
    group.add(radio2);
    panel.add(radio2);
    group.add(radio3);
    panel.add(radio3);
    
    radio1.addActionListener(this);
    radio2.addActionListener(this);
    radio3.addActionListener(this);
    radio1.addItemListener(this);
    radio2.addItemListener(this);
    radio3.addItemListener(this);
    
    Container contentPane = this.getContentPane();
    contentPane.add(panel, BorderLayout.CENTER);
    this.setSize(300, 150);
    this.setVisible(true);
  }

  @Override
  public void actionPerformed(ActionEvent e) {
    System.out.println("Clic sur le bouton : " + e.getActionCommand());
  }

  @Override
  public void itemStateChanged(ItemEvent e) {
    System.out.print("Bouton " + ((JRadioButton) e.getItem()).getActionCommand());
    if (e.getStateChange() == ItemEvent.DESELECTED)
      System.out.println(" deselectionne");
    if (e.getStateChange() == ItemEvent.SELECTED)
      System.out.println(" selectionne");
  }
}

La méthode getSelection() de la classe ButtonGroup renvoie le modèle du bouton radio sélectionné encapsulé dans un objet de type ButtonModel.

Pour déterminer le bouton sélectionné, il faut parcourir les boutons du groupe et comparer leurs modèles.

Exemple :
  public static JRadioButton getBoutonSelectionne(ButtonGroup group) {
    JRadioButton result = null;
    for (Enumeration e = group.getElements(); e.hasMoreElements();) {
      JRadioButton bouton = (JRadioButton) e.nextElement();
      if (bouton.getModel() == group.getSelection()) {
        result = bouton;
        break;
      }
    }
    return result;
  }

 

38.6. Les composants de saisie de texte

Swing possède plusieurs composants pour permettre la saisie de texte.

 

38.6.1. La classe JTextComponent

La classe abstraite JTextComponent est la classe mère de tous les composants permettant la saisie de texte.

Les données du composant (le modèle dans le motif de conception MVC) sont encapsulées dans un objet qui implémente l'interface Document. Deux classes implémentant cette interface sont fournies en standard : PlainDocument pour du texte simple et StyledDocument pour du texte riche pouvant contenir entre autres plusieurs polices de caractères, des couleurs, des images, ...

La classe JTextComponent possède de nombreuses méthodes dont les principales sont :

Méthode Rôle
void copy() Copier le contenu du texte et le mettre dans le presse papier système
void cut() Couper le contenu du texte et le mettre dans le presse papier système
Document getDocument() Renvoyer l'objet de type Document qui encapsule le texte saisi
String getSelectectedText() Renvoyer le texte sélectionné dans le composant
int getSelectionEnd() Renvoyer la position de la fin de la sélection
int getSelectionStart() Renvoyer la position du début de la sélection
String getText() Renvoyer le texte saisi
String getText(int, int) Renvoyer une portion du texte débutant à partir de la position donnée par le premier paramètre et la longueur donnée dans le second paramètre
bool isEditable() Renvoyer un booléen qui précise si le texte est éditable ou non
void paste() Coller le contenu du presse papier système dans le composant
void select(int,int) Sélectionner une portion du texte dont les positions de début et de fin sont fournies en paramètres
void setCaretPosition(int) Déplacer le curseur dans le texte à la position précisé en paramètre
void setEditable(boolean) Permet de préciser si les données du composant sont éditables ou non
void setSelectionEnd(int) Modifier la position de la fin de la sélection
void setSelectionStart(int) Modifier la position du début de la sélection
void setText(String) Modifier le contenu du texte

Toutes ces méthodes sont donc accessibles grâce à l'héritage pour tous les composants de saisie de texte proposés par Swing.

 

38.6.2. La classe JTextField

La classe javax.Swing.JTextField est un composant qui permet la saisie d'une seule ligne de texte simple. Son modèle utilise un objet de type PlainDocument.

Exemple ( code Java 1.1 ) :
import javax.swing.*;

public class JTextField1 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300, 100);
    JPanel pannel = new JPanel();

    JTextField  testField1 = new JTextField ("mon texte");

    pannel.add(testField1);
    f.getContentPane().add(pannel);
    f.setVisible(true);
  }
}

La propriété horizontalAligment permet de préciser l'alignement du texte dans le composant en utilisant les valeurs JTextField.LEFT , JTextField.CENTER ou JTextField.RIGHT.

 

38.6.3. La classe JPasswordField

La classe JPasswordField permet la saisie d'un texte dont tous les caractères saisis seront affichés sous la forme d'un caractère particulier ('*' par défaut). Cette classe hérite de la classe JTextField.

Exemple ( code Java 1.1 ) :
import java.awt.Dimension;

import javax.swing.*;

public class JPasswordField1 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300, 100);
    JPanel pannel = new JPanel();

    JPasswordField  passwordField1 = new JPasswordField ("");
    passwordField1.setPreferredSize(new Dimension(100,20 ));

    pannel.add(passwordField1);
    f.getContentPane().add(pannel);
    f.setVisible(true);
  }
}

La méthode setEchoChar(char) permet de préciser le caractère qui sera montré lors de la saisie.

Il ne faut pas utiliser la méthode getText() qui est déclarée deprecated mais la méthode getPassword() pour obtenir la valeur du texte saisi.

Exemple ( code Java 1.1 ) :
import java.awt.Dimension;
import java.awt.event.*;

import javax.swing.*;

public class JPasswordField2 implements ActionListener {
  
  JPasswordField passwordField1 = null; 
  
  public static void main(String argv[]) {
    JPasswordField2 jpf2 = new JPasswordField2();
    jpf2.init();
  }
  
  public void init() {
    JFrame f = new JFrame("ma fenetre");
    f.setSize(300, 100);
    JPanel pannel = new JPanel();

    passwordField1 = new JPasswordField("");
    passwordField1.setPreferredSize(new Dimension(100, 20));
    pannel.add(passwordField1);

    JButton bouton1 = new JButton("Afficher");
    bouton1.addActionListener(this);
    
    pannel.add(bouton1);
    f.getContentPane().add(pannel);
    f.setVisible(true);
  }
  
  public void actionPerformed(ActionEvent e) {
    
    System.out.println("texte saisie = " + String.copyValueOf(passwordField1.getPassword()));
  }

}

Les méthodes copy() et cut() sont redéfinies pour n'émettre qu'un bip. Elles empêchent l'exportation du contenu du champ.

 

38.6.4. La classe JFormattedTextField

Le JDK 1.4 propose la classe JFormattedTextField pour faciliter la création d'un composant de saisie personnalisé. Cette classe hérite de la classe JTextField.

 

38.6.5. La classe JEditorPane

Ce composant permet la saisie de texte riche multilignes. Ce type de texte peut contenir des informations de mise en pages et de formatage. En standard, Swing propose le support des formats RTF et HTML.

Exemple ( code Java 1.1 ) : affichage de la page de Google avec gestion des hyperliens
import java.net.URL;
import javax.swing.*;
import javax.swing.event.*;

public class JEditorPane1 {

  public static void main(String[] args) {
    final JEditorPane editeur;
    JPanel pannel = new JPanel();

    try {
      editeur = new JEditorPane(new URL("http://google.fr"));
      editeur.setEditable(false);
      editeur.addHyperlinkListener(new HyperlinkListener() {
        public void hyperlinkUpdate(HyperlinkEvent e) {
          if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
            URL url = e.getURL();
            if (url == null)
              return;
            try {
              editeur.setPage(e.getURL());
            } catch (Exception ex) {
              ex.printStackTrace();
            }
          }
        }
      });

      pannel.add(editeur);
    } catch (Exception e1) {
      e1.printStackTrace();
    }
    JFrame f = new JFrame("ma fenetre");
    f.setSize(500, 300);

    f.getContentPane().add(pannel);
    f.setVisible(true);

  }
}

 

38.6.6. La classe JTextPane

 

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

38.6.7. La classe JTextArea

La classe JTextArea est un composant qui permet la saisie de texte simple en mode multiligne. Le modèle utilisé par ce composant est le PlainDocument : il ne peut donc contenir que du texte brut sans éléments multiples de formatage.

JTexteArea propose plusieurs méthodes pour ajouter du texte dans son modèle :

La méthode replaceRange() permet de remplacer la partie de texte occupant les index donnés en paramètres par la chaîne fournie.

La propriété rows permet de définir le nombre de lignes affichées par le composant : cette propriété peut donc être modifiée lors d'un redimensionnement du composant. La propriété lineCount en lecture seule permet de savoir le nombre de lignes qui composent le texte. Il ne faut pas confondre ces deux propriétés.

Exemple ( code Java 1.1 ) :
import javax.swing.*;

public class JTextArea1 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300, 100);
    JPanel pannel = new JPanel();

    JTextArea  textArea1 = new JTextArea ("mon texte");

    pannel.add(textArea1);
    f.getContentPane().add(pannel);
    f.setVisible(true);
  }
}

Par défaut, la taille du composant augmente au fur et à mesure de l'augmentation de la taille du texte qu'il contient. Pour éviter cet effet, il faut encapsuler le JTextArea dans un JScrollPane.

Exemple ( code Java 1.1 ) :
import java.awt.Dimension;
import javax.swing.*;

public class JTextArea1 {

  public static void main(String argv[]) {

    JFrame f = new JFrame("ma fenetre");
    f.setSize(300, 100);
    JPanel pannel = new JPanel();
    JTextArea  textArea1 = new JTextArea ("mon texte");
    JScrollPane scrollPane = new JScrollPane(textArea1);
    scrollPane.setPreferredSize(new Dimension(200,70));
    pannel.add(scrollPane);
    f.getContentPane().add(pannel);
    f.setVisible(true);
  }
}

 

 

38.7. Les onglets

La classe javax.swing.JTabbedPane encapsule un ensemble d'onglets. Chaque onglet est constitué d'un titre, d'un composant et éventuellement d'une image.

Pour utiliser ce composant, il faut :

Exemple ( code Java 1.1 ) :
import java.awt.Dimension;
import java.awt.event.KeyEvent;

import javax.swing.*;

public class TestJTabbedPane1 {

  public static void main(String[] args) {
    JFrame f = new JFrame("Test JTabbedPane");
    f.setSize(320, 150);
    JPanel pannel = new JPanel();

    JTabbedPane onglets = new JTabbedPane(SwingConstants.TOP);

    JPanel onglet1 = new JPanel();
    JLabel titreOnglet1 = new JLabel("Onglet 1");
    onglet1.add(titreOnglet1);
    onglet1.setPreferredSize(new Dimension(300, 80));
    onglets.addTab("onglet1", onglet1);

    JPanel onglet2 = new JPanel();
    JLabel titreOnglet2 = new JLabel("Onglet 2");
    onglet2.add(titreOnglet2);
    onglets.addTab("onglet2", onglet2);

    onglets.setOpaque(true);
    pannel.add(onglets);
    f.getContentPane().add(pannel);
    f.setVisible(true);

  }
}

A partir du JDK 1.4, il est possible d'ajouter un raccourci clavier sur chacun des onglets en utilisant la méthode setMnemonicAt(). Cette méthode attend deux paramètres : l'index de l'onglet concerné (le premier commence à 0) et la touche du clavier associée sous la forme d'une constance KeyEvent.VK_xxx. Pour utiliser ce raccourci, il suffit d'utiliser la touche désignée en paramètre de la méthode avec la touche Alt.

La classe JTabbedPane possède plusieurs méthodes qui permettent de définir le contenu de l'onglet :

Méthodes Rôles
addTab(String, Component) Permet d'ajouter un nouvel onglet dont le titre et le composant sont fournis en paramètres. Cette méthode possède plusieurs surcharges qui permettent de préciser une icône et une bulle d'aide
insertTab(String, Icon, Component, String, index) Permet d'insérer un onglet dont la position est précisée dans le dernier paramètre
remove(int) Permet de supprimer l'onglet dont l'index est fourni en paramètre
setTabPlacement Permet de préciser le positionnement des onglets dans le composant JTabbedPane. Les valeurs possibles sont les constantes TOP, BOTTOM, LEFT et RIGHT définies dans la classe JTabbedPane.

La méthode getSelectedIndex() permet d'obtenir l'index de l'onglet courant. La méthode setSelectedIndex() permet de définir l'onglet courant.

 

38.8. Le composant JTree

Le composant JTree permet de présenter des données sous une forme hiérarchique arborescente.

Au premier abord, le composant JTree peut sembler compliqué à mettre en oeuvre mais la compréhension de son mode de fonctionnement peut grandement faciliter son utilisation.

Il utilise le modèle MVC en proposant une séparation des données (data models) et du rendu de ces données (cell renderers).

Dans l'arbre, les éléments qui ne possèdent pas d'élément fils sont des feuilles (leaf). Chaque élément est associé à un objet (user object) qui va permettre de déterminer le libellé affiché dans l'arbre en utilisant la méthode toString().

 

38.8.1. La création d'une instance de la classe JTree

La classe JTree possède 7 constructeurs. Tous ceux qui attendent au moins un paramètre acceptent une collection pour initialiser tout ou partie du modèle de données de l'arbre :

public JTree();
public JTree(Hashtable value);
public JTree(Vector value);
public JTree(Object[] value);
public JTree(TreeModel model);
public JTree(TreeNode rootNode);
public JTree(TreeNode rootNode, boolean askAllowsChildren);

Lorsqu'une instance de JTree est créée avec le constructeur par défaut, l'arbre obtenu contient des données par défaut.

Exemple ( code Java 1.1 ) :
import javax.swing.JFrame;
import javax.swing.JTree;

public class TestJtree extends JFrame {

  private javax.swing.JPanel jContentPane = null;
  private JTree              jTree        = null;

  private JTree getJTree() {
    if (jTree == null) {
      jTree = new JTree();
    }
    return jTree;
  }

  public static void main(String[] args) {
    TestJtree testJtree = new TestJtree();
    testJtree.setVisible(true);
  }

  public TestJtree() {
    super();
    initialize();
  }

  private void initialize() {
    this.setSize(300, 200);
    this.setContentPane(getJContentPane());
    this.setTitle("JFrame");
  }

  private javax.swing.JPanel getJContentPane() {
    if (jContentPane == null) {
      jContentPane = new javax.swing.JPanel();
      jContentPane.setLayout(new java.awt.BorderLayout());
      jContentPane.add(getJTree(), java.awt.BorderLayout.CENTER);
    }
    return jContentPane;
  }
}

Les trois constructeurs qui attendent en paramètre une collection permettent de créer un arbre avec une racine non affichée qui va contenir comme noeuds fils directs tous les éléments contenus dans la collection.

Exemple ( code Java 1.1 ) :
      String[] racine = {"noeud 1","noeud 2","noeud3","noeud 4"};
      jTree = new JTree(racine);

Dans ce cas, la racine n'est pas affichée. Pour l'afficher, il faut utiliser la méthode setRootVisible()

Exemple ( code Java 1.1 ) :
jTree.setRootVisible(true);

Dans ce cas elle se nomme root et possède un commutateur qui permet de refermer ou d'étendre la racine. Pour supprimer ce commutateur, il faut utiliser la méthode jTree.setShowsRootHandles(false)

L'utilisation de l'une ou l'autre des collections n'est pas équivalente. Par exemple, l'utilisation d'une hashtable ne garantit pas l'ordre des noeuds puisque par définition cette collection ne gère pas un ordre précis.

Généralement, la construction d'un arbre utilise un des constructeurs qui attend en paramètre un objet de type TreeModel ou TreeNode car ces deux objets permettent d'avoir un contrôle sur l'ensemble des données de l'arbre. Leur utilisation sera détaillée dans la section consacrée à la gestion des données de l'arbre.

En fonction du nombre d'éléments et de l'état étendu ou non d'un ou plusieurs éléments, la taille de l'arbre peut varier : il est donc nécessaire d'inclure le composant JTree dans un composant JScrollPane

Exemple ( code Java 1.1 ) :
...

  private JScrollPane        jScrollPane  = null;

...

  private JScrollPane getJScrollPane() {
    if (jScrollPane == null) {
      jScrollPane = new JScrollPane();
      jScrollPane.setViewportView(getJTree());
    }
    return jScrollPane;
  }

...

  private javax.swing.JPanel getJContentPane() {
    if (jContentPane == null) {
      jContentPane = new javax.swing.JPanel();
      jContentPane.setLayout(new java.awt.BorderLayout());
      jContentPane.add(getJScrollPane(), java.awt.BorderLayout.CENTER);
    }
    return jContentPane;
  }

L'utilisateur peut sélectionner un noeud en cliquant sur son texte ou son icône. Un double clic sur le texte ou l'icône d'un noeud permet de l'étendre ou le refermer selon son état.

 

38.8.2. La gestion des données de l'arbre

Chaque arbre commence par un noeud racine. Par défaut, la racine et ses noeuds fils directs sont visibles. Chaque noeud de l'arbre peut avoir zéro ou plusieurs noeuds fils. Un noeud sans noeud fils est appelé une feuille de l'arbre (leaf)

En application du modèle MVC, le composant JTree ne gère pas directement chaque noeud et la façon dont ceux-ci sont organisés et stockés mais il utilise un objet dédié de type TreeModel.

Ainsi, comme dans d'autres composants Swing, le composant JTree manipule des objets implémentant des interfaces. Une classe qui encapsule les données de l'arbre doit implémenter l'interface TreeModel. Chaque noeud de l'arbre doit implémenter l'interface TreeNode.

Pour préciser les données contenues dans l'arbre, il faut créer un objet qui va encapsuler ces données et les passer au constructeur de la classe Jtree. Cet objet peut être de type TreeNode ou TreeModel. Un TreeModel stocke les données de chaque noeud dans un objet de type TreeNode.

Généralement, le plus simple est de définir un type TreeNode personnalisé. Swing propose pour cela l'objet DefaultMutableTreeNode. il suffit d'en créer une instance pour stocker les données et l'utiliser lors de l'appel du constructeur de la classe JTree.

La classe DefaultMutableTreeNode implémente l'interface MutableTreeNode qui elle-même hérite de l'interface TreeNode

Exemple ( code Java 1.1 ) :
import javax.swing.tree.DefaultMutableTreeNode;

...

  private JTree getJTree() {

    if (jTree == null) {
      DefaultMutableTreeNode racine = new DefaultMutableTreeNode("Racine de l'arbre");
      DefaultMutableTreeNode noeud1 = new DefaultMutableTreeNode("Noeud 1");
      racine.add(noeud1);
      DefaultMutableTreeNode noeud2 = new DefaultMutableTreeNode("Noeud 2");
      racine.add(noeud2);
      jTree = new JTree(racine);
    }
    return jTree;
  }

Dans ce cas, une instance de la classe DefaultTreeModel est créée avec la racine fournie en paramètre du constructeur de la classe JTree.

Une autre solution permet de créer une instance de la classe DefaultTreeModel et de la passer en paramètre du constructeur de la classe JTree.

La méthode setModel() de la classe JTree permet d'associer un modèle de données à l'arbre.

 

38.8.2.1. L'interface TreeNode

Chaque noeud de l'arbre stocké dans le modèle de données implémente l'interface TreeNode.

Cette interface définit 7 méthodes dont la plupart concernent les relations entre les noeuds :

Méthode Rôle
Enumeration children() renvoie une collection des noeuds fils
boolean getAllowsChildren() Renvoie un booléen qui precise si le noeud peut avoir des noeuds fils
TreeNode getChildAt(int index) Renvoie le noeud fils correspondant à l'index fourni en paramètre
int getChildCount() renvoie le nombre de noeuds fils directs du noeud
int getIndex(TreeNode child) renvoie l'index du noeud passé en paramètre
TreeNode getParent() renvoie le noeud père
boolean isLeaf() renvoie un booléen qui précise si le noeud est une feuille

Chaque noeud ne peut avoir qu'un seul père (hormis le noeud racine qui ne possède pas de père) et autant de noeuds fils que souhaité. La méthode getParent() permet de renvoyer le noeud père. Elle renvoie null lorsque cette méthode est appelée sur le noeud racine.

La méthode getChildCount() renvoie le nombre de noeuds fils directs du noeud.

La méthode getAllowsChildren() permet de préciser si le noeud peut avoir des noeuds enfants : si elle renvoie false alors le noeud sera toujours une feuille et ne pourra donc jamais avoir de noeuds fils.

La méthode isLeaf() renvoie un booléen précisant si le noeud est une feuille ou non. Une feuille est un noeud qui ne possède pas de noeud fils.

Les noeuds fils sont ordonnés car l'ordre de représentation des données peut être important dans la représentation de données hiérarchiques. La méthode getChildAt() renvoie le noeud fils dont l'index est fourni en paramètre de la méthode. La méthode getIndex() renvoie l'index du noeud fils passé en paramètre.

 

38.8.2.2. L'interface MutableTreeNode

Les 7 méthodes définies par l'interface TreeNode ne permettent que de lire des valeurs. Pour mettre à jour un noeud, il est nécéssaire d'utiliser l'interface MutableTreeNode qui hérite de la méthode TreeNode. Elle définit en plus plusieurs méthodes permettant de mettre à jour le noeud.

void insert(MutableTreeNode child, int index);
void remove(int index);
void remove(MutableTreeNode node);
void removeFromParent();
void setParent(MutableTreeNode parent);
void setUserObject(Object userObject);

La méthode insert() permet d'ajouter le noeud fourni en paramètre comme noeud fils à la position précisée par le second paramètre.

Il existe deux surcharges de la méthode remove() qui permettent de déconnecter un noeud fils de son père. La première surcharge attend en paramètre l'index du noeud fils. La seconde surcharge attend en paramètre le noeud à déconnecter. Dans tous les cas, il est nécessaire d'utiliser cette méthode sur le noeud père.

La méthode removeFromParent() appelée à partir d'un noeud permet de supprimer le lien entre le noeud et son père.

La méthode setParent() permet de préciser le père du noeud.

La méthode setUserObject() permet d'associer un objet au noeud. L'appel à la méthode toString() de cet objet permettra de déterminer le libellé du noeud qui sera affiché.

 

38.8.2.3. La classe DefaultMutableTreeNode

Généralement, les noeuds créés dans le modèle sont des instances de la classe DefaultMutableTreeNode. Cette classe implémente l'interface MutableTreeNode ce qui permet d'obtenir une instance d'un noeud modifiable.

Le plus souvent, les noeuds fournis en paramètres des méthodes proposées par Swing sont de type TreeNode. Si l'instance du noeud est de type DefaultTreeNode, il est possible de faire un cast pour accéder à toutes ses méthodes.

La classe propose trois constructeurs dont deux attendent en paramètre l'objet qui sera associé au noeud. L'un des deux attend en plus un booléen qui permet de préciser si le noeud peut avoir des noeuds fils.

Constructeur  Rôle
public DefaultMutableTreeNode() Créer un noeud sans objet associé. Cette association pourra être faite avec la méthode setObject()
public DefaultMutableTreeNode(Object userObject) Créer un noeud en précisant l'objet qui lui sera associé et qui pourra avoir des noeuds fils
public DefaultMutableTreeNode(Object userObject, boolean allowsChildren) Créer un noeud dont le booléen précise s'il pourra avoir des fils

Pour ajouter une instance de la classe DefaultMutableTreeNode dans le modèle de l'arbre, il est possible d'utiliser la méthode insert() de l'interface MutuableTreeNode ou utiliser la méthode add() de la classe DefaultMutableTreeNode. Celle-ci attend en paramètre une instance du noeud fils à ajouter. Elle ajoute le noeud après le dernier noeud fils, ce qui évite d'avoir à garder une référence sur la position où insérer le noeud.

Exemple ( code Java 1.1 ) :
DefaultMutableTreeNode racineNode = new DefaultMutableTreeNode(); 
DefaultMutableTreeNode division1 = new DefaultMutableTreeNode("Division 1"); 
DefaultMutableTreeNode division2 = new DefaultMutableTreeNode("Division 2"); 
racineNode.add(division1);
racineNode.add(division2);
jTree.setModel(new DefaultTreeModel(racineNode)); 

Il est aussi possible de définir sa propre classe qui implémente l'interface MutableTreeNode : une possibilité est de définir une classe fille de la classe DefaultMutableTreeNode.

 

38.8.3. La modification du contenu de l'arbre

Les modifications du contenu de l'arbre peuvent se faire au niveau du modèle (DefaultTreeModel) ou au niveau du noeud.

La méthode getModel() de la classe JTree permet d'obtenir une référence sur l'instance de la classe TreeModel qui encapsule le modèle de données.

Il est ainsi possible d'accéder à tous les noeuds du modèle pour les modifier.

Exemple ( code Java 1.1 ) :
jTree = new JTree();
Object noeudRacine = jTree.getModel().getRoot();
((DefaultMutableTreeNode)noeudRacine).setUserObject("Racine de l'arbre"); 

L'interface TreeModel ne propose rien pour permettre la mise à jour du modèle. Pour cela, il faut utiliser une instance de la classe DefaultTreeModel.

Elle propose plusieurs méthodes pour ajouter ou supprimer un noeud :

void insertNodeInto(MutableTreeNode child, MutableTreeNode parent, int index)
void removeNodeFromParent(MutableTreeNode parent)

L'avantage de ces deux méthodes est qu'elles mettent à jour le modèle mais aussi qu'elles mettent à jour la vue en appelant respectivement les méthodes nodesWereInserted() et nodesWereRemoved() de la classe DefaultTreeModel.

Ces deux méthodes sont donc pratiques pour faire des mises à jour mineures mais elles sont peut adaptées pour de nombreuses mises à jour puisqu'elles déclenchent un événement à chacune de leurs utilisations.

 

38.8.3.1. Les modifications des noeuds fils

La classe DefaultMutableTreeNode propose plusieurs méthodes pour mettre à jour le modèle à partir du noeud qu'elle encapsule.

void add(MutableTreeNode child)
void insert(MutableTreeNode child, int index)
void remove(int index)
void remove(MutableTreeNode child)
void removeAllChildren()
void removeFromParent()

Toutes ces méthodes sauf la dernière agissent sur un ou plusieurs noeuds fils. Ces méthodes agissent simplement sur la structure du modèle. Elles ne provoquent pas un affichage par la partie vue de ces changements. Pour cela il est nécessaire d'utiliser une des méthodes suivantes proposées par la classe DefaultTreeModel :

Méthode  Rôle
void reload()  rafraichir toute l'arborescence à partir du modèle
void reload(TreeNode node)  rafraichir toute l'arborescence à partir du noeud précisé en paramètre
void nodesWereInserted(TreeNode node, int[] childIndices) pour le noeud précisé, cette méthode rafraichit les noeuds fils ajoutés dont les index sont fournis en paramètre
void nodesWereRemoved(TreeNode node,int[] childIndices, Object[] removedChildren) pour le noeud précisé, cette méthode rafraichit les noeuds fils supprimés dont les index sont fournis en paramètre
void nodeStructureChanged(TreeNode node) cette méthode est identique à la méthode reload()

 

38.8.3.2. Les événements émis par le modèle

Il est possible d'enregistrer un listener de type TreeModelListener sur un objet de type DefaultTreeModel.

L'interface TreeModelListener définit quatre méthodes pour répondre à des événements particuliers :

Méthode 

Rôle

void treeNodesChanged(TreeModelEvent) la méthode nodeChanged() ou nodesChanged() est utilisée
void treeStructureChanged(TreeModelEvent) la méthode reload() ou nodeStructureChanged() est utilisée
void treeNodesInserted(TreeModelEvent) la méthode nodeWhereInserted() est utilisée

void treeNodesRemoved(TreeModelEvent)

la méthode nodeWhereRemoved() est utilisée


Toutes ces méthodes ont un objet de type TreeModelEvent qui encapsule l'événement.

La classe TreeModelEvent propose cinq méthodes pour obtenir des informations sur les noeuds impactés par l'événement.

Méthode Rôle
Object getSource() renvoie une instance sur le modèle de l'arbre (généralement un objet de type DefaultTreeModel)
TreePath getTreePath() renvoie le chemin du noeud affecté par l'événement
Object[] getPath() Renvoie la succession de noeuds de la racine au noeud parent des noeuds impactés
Object[] getChildren()  
int[] getChildIndices() retourne les index des noeuds modifiés

Dans la méthode treeStructureChanged(), seules les méthodes getPath() et getTreePath() fournissent des informations utiles en retournant le noeud qui a été modifié.

Dans la méthode treeNodesChanged(), treeNodesRemoved() et treeNodesInserted() les méthodes getPath() et getTreePath() renvoient le noeud père des noeuds affectés. Les méthodes getChildIndices() et getChidren() renvoient respectivement un tableau des index des noeuds fils modifiés et un tableau de ces noeuds fils.

Dans ces méthodes, les méthodes getPath() et getTreePath() renvoient le noeud père des noeuds affectés.

Comme l'objet JTree enregistre ses propres listeners, il n'est pas nécessaire la plupart du temps, d'enregistrer ces listeners hormis pour des besoins spécifiques.

 

38.8.3.3. L'édition d'un noeud

Par défaut, le composant JTree est readonly. Il est possible d'autoriser l'utilisateur à modifier le libellé des noeuds en utilisant la méthode setEditable() avec le paramètre true : jTtree.setEditable(true);  

Pour éditer un noeud, il faut

Pour valider les modifications, il suffit d'appuyer sur la touche « Entree ».

Pour annuler les modifications, il suffit d'appuyer sur la touche « Esc »

Il est possible d'enregistrer un listener de type TreeModelListerner pour assurer des traitements lors d'événements liés à l'édition d'un noeud.

L'interface TreeModelListener définit la méthode treeNodesChanged() qui permet de traiter les événements de type TreeModelEvent liés à la modification d'un noeud.

Exemple ( code Java 1.1 ) :
      jTree.setEditable(true);
      jTree.getModel().addTreeModelListener(new TreeModelListener() {

        public void treeNodesChanged(TreeModelEvent evt) {
          System.out.println("TreeNodesChanged");
          Object[] noeuds = evt.getChildren();
          int[] indices = evt.getChildIndices();
          for (int i = 0; i < noeuds.length; i++) {
            System.out.println("Index " + indices[i] + ", nouvelle valeur : "
                + noeuds[i]);
          }
        }

        public void treeStructureChanged(TreeModelEvent evt) {
          System.out.println("TreeStructureChanged");
        }

        public void treeNodesInserted(TreeModelEvent evt) {
          System.out.println("TreeNodesInserted");
        }

        public void treeNodesRemoved(TreeModelEvent evt) {
          System.out.println("TreeNodesRemoved");
        }

      });

 

38.8.3.4. Les éditeurs personnalisés

Il est possible de définir un éditeur particulier pour éditer la valeur d'un noeud. Un éditeur particulier doit implémenter l'interface TreeCellEditor.

Cette interface hérite de l'interface CellEditor qui définit plusieurs méthodes utiles pour la création d'un éditeur dédié :

Object getCellEditorValue();
boolean isCellEditable(EventObject);
boolean shouldSelectCell(EventObject);
boolean stopCellEditing();
void cancelCellEditing();
void addCellEditorListener( CellEditorListener);
void removeCellEditorListener( CellEditorListener);

L'interface TreeCellEditor ne définit qu'une seule méthode :

Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row);

Cette méthode renvoie un composant qui va permettre l'édition de la valeur du noeud.

La valeur initiale est fournie dans le second paramètre de type Object. Les trois arguments de type booléen suivants permettent respectivement de savoir si le noeud est sélectionné, est étendu et est une feuille.

Swing propose une implémentation de cette interface dans la classe DefaultCellEditor qui permet de modifier la valeur du noeud sous la forme d'une zone de texte, d'une case à cocher ou d'une liste déroulante grâce à trois constructeurs :

public DefaultCellEditor(JTextField text); public DefaultCellEditor(JCheckBox box); public DefaultCellEditor(JComboBox combo);

La méthode setCellEditor() de la classe JTree permet d'associer le nouvel éditeur à l'arbre.

Exemple ( code Java 1.1 ) :
      jTree.setEditable(true);       
      String[] elements = { "Element 1", "Element 2", "Element 3", "Element  4"}; 
      JComboBox jCombo = new JComboBox(elements);       
      DefaultTreeCellEditor editor = new DefaultTreeCellEditor(jTree,          
	       new DefaultTreeCellRenderer(), new DefaultCellEditor(jCombo)); 
      jTree.setCellEditor(editor); 

 

38.8.3.5. La définition des noeuds éditables

Par défaut, si la méthode setEditable(true) est utilisée alors tous les noeuds sont modifiables.

Il est possible de définir les noeuds de l'arbre qui sont éditables en créant une classe fille de la classe JTree et en redéfinissant la méthode isPathEditable().

Cette méthode est appelée avant chaque édition d'un noeud. Elle attend en paramètre un objet de type TreePath qui encapsule le chemin du noeud à éditer.

Par défaut, elle renvoie le résultat de l'appel à la méthode isEditable(). Il est d'ailleurs important, lors de la redéfinition de la méthode isPathEditable(), de tenir compte du résultat de la méthode isEditable() pour s'assurer que l'arbre est modifiable avant de vérifier si le noeud peut être modifié.

 

38.8.4. La mise en oeuvre d'actions sur l'arbre

 

38.8.4.1. Etendre ou refermer un noeud

Pour étendre un noeud et ainsi voir ses fils, l'utilisateur peut double cliquer sur l'icône ou sur le libellé du noeud. Il peut aussi cliquer sur le petit commutateur à gauche de l'icône.

Enfin, il est possible d'utiliser le clavier pour naviguer dans l'arbre à l'aide des touches flèches haut et bas et des touches flèches droite et gauche pour respectivement étendre ou refermer un noeud. Lors d'un appui sur la flèche gauche, si le noeud est déjà fermé alors c'est le noeud père qui est sélectionné. De la même façon, lors d'un appui sur la flèche droite, si le noeud est étendu alors le premier noeud fils est sélectionné.

La touche HOME permet de sélectionner le noeud racine. La touche END permet de sélectionner le noeud qui est la dernière feuille du dernier noeud. Les touches PAGEUP et PAGEDOWN permettent de parcourir rapidement les noeuds de l'arbre.

Depuis Java 2 version 1.3, la méthode setToggleClickCount() permet de préciser le nombre de clics nécessaires pour étendre ou refermer un noeud.

La classe JTree propose plusieurs méthodes liées aux actions permettant d'étendre ou de refermer un noeud.

Méthode

Rôle

public void expandRow (int row)

Etendre le noeud dont l'index est fourni en paramètre

public void collapseRow(int row)

Refermer le noeud dont l'index est fourni en paramètre

public void expandPath(TreePath path)

Etendre le noeud encapsulé dans la classe TreePath fournie en paramètre

public void collapsePath(TreePath path)

Refermer le noeud encapsulé dans la classe TreePath fournie en paramètre

public boolean isExpanded(int row)

Renvoie un booléen qui précise si le noeud dont l'index est fourni en paramètre est étendu

public boolean isCollapsed (int row)

Renvoie un booléen qui précise si le noeud dont l'index est fourni en paramètre est refermé

public boolean isExpanded(TreePath path)

Renvoie un booléen qui précise si le noeud encapsulé dans la classe TreePath fournie en paramètre est étendu

public boolean isCollapsed (TreePath path)

Renvoie un booléen qui précise si le noeud encapsulé dans la classe TreePath fournie en paramètre est refermé


Par défaut, le noeud racine est étendu.

Les méthodes expandRow() et expandPath() ne permettent que d'étendre les noeuds fils directs du noeud sur lesquel elles sont appliquées. Pour étendre les noeuds sous-jacents il est nécessaire d'écrire du code pour réaliser l'opération sur chaque noeud concerné de façon récursive.

Pour refermer tous les noeudsx et ne laisser que le noeud racine, il faut utiliser la méthode collapseRow() en lui passant 0 comme paramètre puisque le noeud racine est toujours le premier noeud.

Exemple ( code Java 1.1 ) :
jTree.collapseRow(0); 

La classe JTree propose deux méthodes pour forcer un noeud à être visible : scrollPathToVisible() et scrollRowToVisible(). Celles-ci ne peuvent fonctionner que si le composant JTree est inclus dans un conteneur JScrollPane pour permettre au composant de scroller.

Exemple ( code Java 1.1 ) :
      jTree.addTreeExpansionListener(new TreeExpansionListener() { 
        public void treeExpanded(TreeExpansionEvent evt) {
          System.out.println("treeExpanded : path=" + evt.getPath());
          jTree.scrollPathToVisible(evt.getPath());
        }

 

38.8.4.2. La détermination du noeud sélectionné

Pour déterminer le noeud sélectionné, il suffit d'utiliser la méthode getLastSelectedPathComponent() de la classe JTree et de caster la valeur retournée dans le type du noeud, généralement de type DefaultMutableTreeNode. La méthode getObject() du noeud permet d'obtenir l'objet associé au noeud. Si l'objet associé est simplement une chaîne de caractères ou si la valeur nécessaire est simplement le libellé du noeud, il suffit d'utiliser la méthode toString().

Exemple ( code Java 1.1 ) : un bouton qui précise lors d'un clic le noeud sélectionné
  ...
  private JButton getJButton() {
    if (jButton == null) {
      jButton = new JButton();
      jButton.addActionListener(new java.awt.event.ActionListener() {
        public void actionPerformed(java.awt.event.ActionEvent e) {
          System.out.println("actionPerformed()");
          System.out.println("Noeud sélectionné : "
              + jTree.getLastSelectedPathComponent().toString());
        }
      });
    }

 

38.8.4.3. Le parcours des noeuds de l'arbre

Il peut être nécessaire de parcourir tout ou partie des noeuds de l'arbre pour par exemple faire une recherche dans l'arborescence.

Si l'arbre est composé de noeuds de type DefaultMutableTreenode alors l'interface TreeNode propose plusieurs méthodes pour obtenir une énumération des noeuds. L'ensemble, ou seulement une partie des données, peut être parcouru dans les deux sens et selon deux types de présentation des valeurs.

Enumeration preorderEnumeration();
Enumeration postorderEnumeration();
Enumeration breadthFirstEnumeration();
Enumeration depthFirstEnumeration();

Dans l'exemple ci-dessous, l'arborescence suivante est utilisée :

Exemple ( code Java 1.1 ) : un bouton qui précise lors d'un clic le noeud sélectionné
    Enumeration e = ((DefaultMutableTreeNode)jTree.getModel().getRoot()).preorderEnumeration();
    while (e.hasMoreElements()) {
       System.out.println(e.nextElement() + " ");     
    }

Résultat :

preorder postorder breadthFirst depthFirst
Racine de l'arbre
colors
blue
violet
red
yellow
sports
basketball
soccer
football
hockey
food
hot dogs
pizza
ravioli
bananas
blue
violet
red
yellow
colors
basketball
soccer
football
hockey
sports
hot dogs
pizza
ravioli
bananas
food
Racine de l'arbre
Racine de l'arbre
colors
sports
food
blue
violet
red
yellow
basketball
soccer
football
hockey
hot dogs
pizza
ravioli
bananas
blue
violet
red
yellow
colors
basketball
soccer
football
hockey
sports
hot dogs
pizza
ravioli
bananas
food
Racine de l'arbre

La méthode pathFromAncestorEnumeration(TreeNode ancestor) renvoie une énumération des noeuds entre le noeud sur lequel la méthode est appelée et le noeud fourni en paramètre. Ainsi le noeud fourni en paramètre doit obligatoirement être un noeud fils direct ou indirect du noeud sur lequel la méthode est appelée. Dans le cas contraire, une exception de type IllegalArgumentException est levée.

 

38.8.5. La gestion des événements

Il est possible d'attacher des listeners pour répondre aux événements liés à la sélection d'un élément ou l'extension ou la refermeture d'un noeud.

38.8.5.1. La classe TreePath

Durant son utilisation, le composant JTree ne gère pas directement les noeuds du modèle de données. La manipulation de ces noeuds se fait via un index ou une instance de la classe TreePath.

L'utilisation de l'index est assez délicate car seul le noeud racine de l'arbre possède toujours le même index 0. Pour les autres noeuds, la valeur de l'index dépend de l'état étendu/refermé de chaque noeud puisque seuls les noeuds affichés possèdent un index. Il est donc préférable d'utiliser la classe TreePath.

Le modèle de données utilise des noeuds mais l'interface de l'arbre utilise une autre représentation sous la forme de la classe TreePath.

La classe DefaultMutableTreeNode est la représentation physique d'un noeud, la classe TreePath est la représentation logique. Elle encapsule le chemin du noeud dans l'arborescence.

Cette classe contient plusieurs méthodes :

public Object getLastPathComponent();
public Object getPathComponent(int index);
public int getPathCount();
public Object[] getPath();
public TreePath getParentPath();
public TreePath pathByAddingChild(Object child);
public boolean isDescendant(TreePath treePath)

La méthode getPath() renvoie un tableau d'objets contenant chaque noeud qui compose le chemin encapsulé par la classe TreePath.

La méthode getLastPathComponent() renvoie le dernier noeud du chemin.

La méthode getPathCount() renvoie le nombre de noeuds qui composent le chemin.

La méthode getPathComponent() permet de renvoyer le noeud dont l'index dans le chemin est fourni en paramètre. L'élément avec l'index 0 est toujours le noeud racine de l'arbre.

La méthode getParentPath() renvoie une instance de la classe TreePath qui encapsule le chemin vers le noeud père du chemin encapsulé.

La méthode pathByAddingChild() renvoie une instance de la classe TreePath qui encapsule le chemin issu de l'ajout d'un noeud fils fourni en paramètre.

La méthode idDescendant() renvoie un booléen qui précise si le chemin passé en paramètre est un descendant du chemin encapsulé.

La classe TreePath ne permet pas de gérer le contenu de chaque noeud mais uniquement son chemin dans l'arborescence. Pour accéder au noeud à partir de son chemin, il faut utiliser la méthode getLastPathComponent(). Pour obtenir un noeud inclus dans le chemin, il faut utiliser la getPathComponent() ou getPath(). Toutes ces méthodes renvoient un objet ou un tableau de type Object. Il est donc nécessaire de réaliser un cast vers le type de noeud utilisé, généralement de type DefaultMutableTreeNode.

A partir d'un noeud de type DefaultMutableTreeNode, il est possible d'obtenir l'objet TreePath encapsulant le chemin du noeud. La méthode getPath() permet d'obtenir un tableau d'objets de type TreeNode qu'il suffit de passer au constructeur de la classe TreePath.

Exemple ( code Java 1.1 ) :
TreeNode[] chemin = noeud.getPath(); 
TreePath path = new TreePath(chemin);

 

38.8.5.2. La gestion de la sélection d'un noeud

La gestion de la sélection de noeud dans un composant JTree est déléguée à un modèle de sélection sous la forme d'une classe qui implémente l'interface TreeSelectionModel. Par défaut, le composant JTree utilise une instance de la classe DefaultTreeSelectionModel.

Le modèle de sélection peut être configuré selon trois modes :

Pour empêcher la sélection d'un noeud dans l'arbre, il faut supprimer son modèle de sélection en passant null à la méthode setSelectionModel().

Exemple ( code Java 1.1 ) :
JTree jTree = new JTree()jTree.setSelectionModel(null);

La sélection d'un noeud peut être réalisée par l'utilisateur ou par l'application : le modèle de sélection s'assure que celle-ci est réalisée en respectant le mode de sélection du modèle.

L'utilisateur peut utiliser la souris pour sélectionner un noeud ou appuyer sur la touche Espace sur le noeud courant pour le sélectionner. Il est possible de sélectionner plusieurs noeuds en fonction du mode en maintenant la touche CTRL enfoncée. Avec la touche SHIFT, il est possible selon le mode de sélectionner tous les noeuds entre un premier noeud sélectionné et le noeud courant.

La sélection d'un noeud génère un événement de type TreeSelectionEvent.

Le dernier noeud sélectionné peut être obtenu en utilisant les méthodes getLeadSelectionPath() ou getLeadSelectionRow().

Par défaut la sélection d'un noeud entraine l'extension des noeuds ascendants correspondant afin de les rendre visibles. Pour empêcher ce comportement, il faut utiliser la méthode setExpandSelectedPath() en lui fournissant la valeur false en paramètre.

public void setExpandsSelectedPaths(boolean cond);

Les classes DefaultTreeSelectionModel et JTree possèdent plusieurs méthodes pour gérer la sélection de noeuds. Certaines de ces méthodes sont communes à ces deux classes.

Méthode

Rôle

int getSelectionMode()

renvoie le mode de sélection

void setSelectionMode(int mode)

mettre à jour le mode de sélection

Object getLastSelectedPathComponent()

renvoie le premier noeud de la sélection courante ou null si aucun noeud n'est sélectionné
JTree uniquement

TreePath getAnchorSelectionPath()

JTree uniquement

void setAnchorSelectionPath(TreePathpath)

JTree uniquement

TreePath getLeadSelectionPath()

renvoie le dernier path ajouté à la sélection ou identifié comme tel

setLeadSelectionPath()

fait de newPath le dernier Path ajouté

int getMaxSelectionRow()

Renvoie le plus grand index de la sélection

int getMinSelectionRow()

Renvoie le plus petit index de la sélection

int getSelectionCount()

Renvoie le nombre de noeuds inclus dans la sélection

TreePath getSelectionPath()

Renvoie le chemin du premier élément sélectionné

TreePath[] getSelectionPaths()

Renvoie un tableau des chemins des noeuds inclus dans la sélection

int[] getSelectionRows()

Renvoie un tableau des index des noeuds inclus dans la sélection

Boolean isPathSelected (TreePath path)

Renvoie un booléen si le noeud dont le chemin est fourni en paramètre est inclus dans la sélection

Boolean isRowSelected(int row)

Renvoie un booléen si le noeud dont l'index est fourni en paramètre est inclus dans la sélection

boolean isSelectionEmpty()

Renvoie un booléen qui précise si la sélection est vide

void clearSelection()

Vide la sélection

void removeSelectionInterval (int row0, int row1)

Enlève de la sélection les noeuds dans l'intervalle des index fournis en paramètre

void removeSelectionPath(TreePath path)

Enlève de la sélection le noeud dont le chemin est fourni en paramètre

void removeSelectionRow (int row)

Enlève de la sélection le noeud dont l'index est fourni en paramètre
JTree uniquement

void removeSelectionRows(int[] rows)

Enlève de la sélection les noeuds dont les index sont fournis en paramètre
JTree uniquement

void addSelectionInterval(int row0, int row1)

Ajouter à la sélection les noeuds dont l'intervalle des index est fourni en paramètre

void addSelectionPath(TreePath path)

Ajouter à la sélection le noeud dont le chemin est fourni en paramètre

addSelectionPaths(TreePath[] path)

Ajouter à la sélection les noeuds dont les chemins sont fournis en paramètre

void addSelectionRow(int row)

Ajouter à la sélection le noeud dont l'index est fourni en paramètre

void addSelectionRows(int[] row)

Ajouter à la sélection les noeuds dont les index sont fournis en paramètre

void setSelectionInterval(int row0, int row1)

Définir la sélection avec les noeuds dont les index sont fournis en paramètre
JTree uniquement

setSelectionPath(TreePath path)

Définir la sélection avec le noeud dont le chemin est fourni en paramètre

void setSelectionPaths (TreePath[] path)

Définir la sélection avec les noeuds dont les chemins sont fournis en paramètre

void setSelectionRow(int row)

Définir la sélection avec le noeud dont l'index est fourni en paramètre

void setSelectionRows(int[] row)

Définir la sélection avec les noeuds dont les index sont fournis en paramètre
JTree uniquement

 

38.8.5.3. Les événements lies à la sélection de noeuds

Lors de la sélection d'un noeud, un événement de type TreeSelectionEvent est émis. Pour traiter cet événement, le composant doit enregistrer un listener de type TreeSelectionListener.

L'interface TreeSelectionListener définit une seule méthode :

public void valueChanged(TreeSelectionEvent evt)

Exemple ( code Java 1.1 ) :
jTree.addTreeSelectionListener(new javax.swing.event.TreeSelectionListener() {
      public void valueChanged(javax.swing.event.TreeSelectionEvent e) {

        DefaultMutableTreeNode noeud = (DefaultMutableTreeNode) jTree
            .getLastSelectedPathComponent();
        if (noeud == null)
          return;
        System.out.println("valueChanged() : " + noeud);
      }
});

La classe TreeSelectionEvent possède plusieurs méthodes pour obtenir des informations sur la sélection.

Méthode

Rôle

public TreePath[] getPaths()

Renvoie un tableau des chemins des noeuds sélectionnés

public boolean isAddedPath (TreePath path)

Renvoie true si le noeud sélectionné est ajouté à la sélection.
Renvoie false si le noeud sélectionné est retiré de la sélection

TreePath getPath()

Renvoie le chemin du premier noeud sélectionné

boolean isAddedPath()

Renvoie true si le premier noeud sélectionné est ajouté à la sélection.
Renvoie false si le premier noeud sélectionné est retiré de la sélection

TreePath getOldLeadSelection()

Renvoie l'ancien lead path

TreePath getNewLeadSelection()

Renvoie le leader actuel de la sélection


Un listener de type TreeSelectionListener est enregistré en utilisant la méthode addTreeSelectionListener() de la classe JTree.

Exemple ( code Java 1.1 ) :
jTree.addTreeSelectionListener(new TreeSelectionListener() {

  public void valueChanged(TreeSelectionEvent e) {

    Object obj = jTree.getLastSelectedPathComponent();
    System.out.println("getLastSelectedPathComponent=" + obj);
    System.out.println("getPath=" + e.getPath());
    System.out.println("getNewLeadSelectionPath="
        + e.getNewLeadSelectionPath());
    System.out.println("getOldLeadSelectionPath="
        + e.getOldLeadSelectionPath());
    TreePath[] paths = e.getPaths();

    for (int i = 0; i < paths.length; i++) {
      System.out.println("Path " + i + "=" + paths[i]);
    }
  }
});

Un événement de type TreeSelectionEvent n'est émis que si un changement intervient dans la sélection : lors d'un clic sur un noeud, celui-ci est sélectionné et un événement est émis. Lors d'un nouveau clic sur ce même noeud, le noeud est toujours sélectionné mais l'événement n'est pas émis puisque la sélection n'est pas modifiée.

Dans un listener pour gérer les événements de la souris, il est possible d'utiliser la méthode getPathForLocation() pour déterminer le chemin d'un noeud à partir des coordonnées de la souris qu'il faut lui fournir en paramètre.

La méthode getPathForLocation() renvoie null si l'utilisateur clique en dehors d'un noeud dans l'arbre.

Exemple ( code Java 1.1 ) :
jTree.addMouseListener(new MouseAdapter() { 
  public void mouseClicked(MouseEvent evt) { 
      TreePath path =
         jTree.getPathForLocation(evt.getX(), evt.getY()); 
      if (path != null) { 
         System.out.println("path= " + path.getLastPathComponent());
       }
  } 
});

Plusieurs autres méthodes peuvent aussi être utilisées dans ce contexte.

Méthode

Rôle

TreePath getClosestPathForLocation(int x, int y)

Retourne le chemin du noeud le plus proche des coordonnées fournies en paramètre

int getClosestRowForLocation(int x, int y)

Retourne l'index du noeud le plus proche des coordonnées fournies en paramètre

Rectangle getPathBounds(TreePath path)

Renvoie un objet de type Rectangle qui représente la surface du noeud dont le chemin est fourni en paramètre

TreePath getPathForLocation(int x, int y)

Retourne le chemin du noeud dont la surface contient les coordonnées fournies en paramètre. Renvoie null si ces coordonnées ne correspondent à aucun noeud

TreePath getPathForRow(int row)

Renvoie le chemin du noeud dont l'index est fourni en paramètre

Rectangle getRowBounds(int row)

Renvoie un objet de type Rectangle qui représente la surface du noeud dont l'index est fourni en paramètre

int getRowForLocation(int x, int y)

Renvoie l'index du noeud à la position fournie

 

38.8.5.4. Les événements lorsqu'un noeud est étendu ou refermé

A chaque fois qu'un noeud est étendu ou refermé, un événement de type TreeExpansionEvent est émis. Il est possible de répondre à ces événements en mettant en place un listener de type TreeExpansionListener.

L'interface TreeExpansionListener propose deux méthodes :

public void treeExpanded(TreeExpansionEvent event) public void treeCollapsed(TreeExpansionEvent event)

La classe TreeExpansionEvent possède une propriété source qui contient une référence sur le composant JTree à l'origine de l'événement et une propriété path qui contient un objet de type TreePath encapsulant le chemin du noeud à l'origine de l'événement.

Les valeurs de ces deux propriétés peuvent être obtenues avec leurs getters respectifs : getSource() et getPath().

Exemple ( code Java 1.1 ) :
jTree.addTreeExpansionListener(new TreeExpansionListener() { 

  public void treeExpanded(TreeExpansionEvent evt) {
    System.out.println("expand, path=" +
                          evt.getPath());
  } 

  public void treeCollapsed(TreeExpansionEvent evt) {
    System.out.println("collapse, path=" +
                        evt.getPath());
  } 

});

Un seul événement est généré à chaque fois qu'un noeud est étendu ou refermé : il n'y a pas d'événements émis pour les éventuels noeuds fils qui sont étendus ou refermés suite à l'action.

 

38.8.5.5. Le contrôle des actions pour étendre ou refermer un noeud

Il peut être utile de recevoir un événement avant qu'un noeud ne soit étendu ou refermé. Un listener de type TreeWillExpandListener() peut être mis en place pour recevoir un événement de type TreeExpansionEvent lors d'une tentative pour étendre ou refermer un noeud.

L'interface TreeWillExpandListener définit deux méthodes :

public void treeWillCollapse(TreeExpansionEvent evt) throws ExpandVetoException;
public void treeWillExpand(TreeExpansionEvent evt) throws ExpandVetoException;

Les deux méthodes peuvent lever une exception de type ExpandVetoException. Cette exception est levée si, pendant l'exécution d'une de ces méthodes, des conditions sont remplies pour empêcher l'action demandée par l'utilisateur. Si l'exception n'est pas levée à la fin des traitements de la méthode alors l'action est réalisée.

Exemple ( code Java 1.1 ) : empécher tous les noeuds étendus de se refermer
jTree.addTreeWillExpandListener(new TreeWillExpandListener() {

  public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
    throw new ExpandVetoException(event);
  }

  public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
  } 

});

 

38.8.6. La personnalisation du rendu

Le rendu du composant JTree dépend bien sûr dans un premier temps du look and feel utilisé mais il est aussi possible de personnaliser plus finement le rendu des noeuds du composant.

Il est possible de préciser la façon dont les lignes reliant les noeuds sont rendues via une propriété client nommée lineStyle. Cette propriété peut prendre trois valeurs :

Valeur

Rôle

Angled

Une ligne à angle droit relie chaque noeud fils à son noeud père

None

Aucune ligne n'est affichée entre les noeuds

Horizontal

Une simple ligne horizontale sépare les noeuds enfants du noeud racine


Pour préciser la valeur de la propriété que le composant doit utiliser, il faut utiliser la méthode putClientProperty() qui attend deux paramètres sous forme de chaînes de caractères :

Exemple ( code Java 1.1 ) :
jTree = new JTree(racine);
jTree.putClientProperty("JTree.lineStyle","Horizontal");

None

Angled

Horizontal


Il est possible de modifier l'apparence de la racine de l'arbre grâce à deux méthodes de la classe JTree : setRootVisible() et setShowsRootHandles().

La méthode setRootVisible() permet de préciser avec son booléen en paramètre si la racine est affichée ou non.

Exemple ( code Java 1.1 ) :
JTree jtree = new JTree();
jtree.setShowsRootHandles(false);
jtree.setRootVisible(true);

 

38.8.6.1. Personnaliser le rendu des noeuds

Il est possible d'obtenir un contrôle total sur le rendu de chaque noeud en définissant un objet qui implémente l'interface TreeCellRenderer. Attention, le rendu personnalisé est parfois dépendant du look & feel utilisé.

L'interface TreeCellRenderer ne définit qu'une seule méthode :

Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus)

Cette méthode envoie un composant qui va encapsuler le rendu du noeud. Le premier argument de type JTree encapsule le composant JTree lui-même. L'argument de type Object encapsule le noeud dont le rendu doit être généré.

La méthode getCellRenderer() renvoie un objet qui encapsule le TreeCellRenderer. Il est nécessaire de réaliser un cast vers le type de cet objet.

Swing propose une classe de base DefaultTreeCellRenderer pour le rendu. Elle propose plusieurs méthodes pour permettre de définir le rendu.

Méthode

Rôle

void setBackgroundNonSelectionColor(Color)

Permet de définir la couleur de fond du noeud lorsqu'il n'est pas sélectionné

void setBackgroundSelectionColor(Color)

Permet de définir la couleur de fond du noeud lorsqu'il est sélectionné

void setBorderSelectionColor(Color)

Permet de définir la couleur de la bordure du noeud lorsqu'il est sélectionné. Il n'est pas possible de définir une bordure pour un noeud sélectionné

void setTextNonSelectionColor(Color)

Permet de définir la couleur du texte du noeud lorsqu'il n'est pas sélectionné

void setTextSelectionColor(Color)

Permet de définir la couleur du texte du noeud lorsqu'il est sélectionné

void setFont(Font)

Permet de définir la police de caractère utilisé pour afficher le texte du noeud

void setClosedIcon(Icon)

Permet de définir l'icône associée au noeud lorsque celui-ci est fermé

void setOpenIcon(Icon)

Permet de définir l'icône associée au noeud lorsque celui-ci est étendu

void setLeafIcon(Icon)

Permet de définir l'icône associée au noeud lorsque celui-ci est une feuille


Un composant ne peut avoir qu'une seule instance de type TreeCellRenderer. Cette instance sera donc appelée pour définir le rendu de chaque noeud.

Exemple ( code Java 1.1 ) :
TreeCellRenderer cellRenderer = jTree.getCellRenderer();
if (cellRenderer instanceof DefaultTreeCellRenderer) {  
  DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer)cellRenderer; 
  renderer.setBackgroundNonSelectionColor(Color.gray);  
  renderer.setBackgroundSelectionColor(Color.black);  
  renderer.setTextSelectionColor(Color.white);   
  renderer.setTextNonSelectionColor(Color.black); 
  jTree.setBackground(Color.gray);
}  

Résultat :

Pour modifier les icônes utilisées par les différents éléments de l'arbre, il faut utiliser les méthodes setOpenIcon(), setClosedIcon() et setLeafIcon().

Méthode

Rôle

setOpenIcon()

précise l'icône pour un noeud ouvert

setClosedIcon()

précise l'icône pour un noeud fermé

setLeafIcon()

précise l'icône pour une feuille


Pour simplement supprimer l'affichage de l'icône, il suffit de passer null à la méthode concernée.

Exemple ( code Java 1.1 ) :
DefaultTreeCellRenderer monRenderer = new DefaultTreeCellRenderer();
monRenderer.setOpenIcon(null);
monRenderer.setClosedIcon(null);
monRenderer.setLeafIcon(null);

Pour préciser une image, il faut créer une instance de la classe ImageIcon encapsulant l'image et la passer en paramètre de la méthode concernée.

Exemple ( code Java 1.1 ) :
    private Icon ourvertIcon = new ImageIcon("images/ouvert.gif");
    private Icon fermeIcon   = new ImageIcon("images/ferme.gif");
    private Icon feuilleIcon = new ImageIcon("images/feuille.gif");
...    
    DefaultTreeCellRenderer treeCellRenderer = new DefaultTreeCellRenderer();
    treeCellRenderer.setOpenIcon(ouvertIcon);    
    treeCellRenderer.setClosedIcon(fermeIcon);    
    treeCellRenderer.setLeafIcon(feuilleIcon);

Il est aussi possible de définir une classe qui hérite de la classe DefaultTreeCellRenderer. Cette classe propose une implémentation par défaut de l'interface TreeCellRenderer. Comme elle hérite de la classe JLabel, elle possède déjà de nombreuses méthodes pour assurer le rendu du noeud sous la forme d'un composant de type étiquette.

Exemple ( code Java 1.1 ) :
import java.awt.Color;
import java.awt.Component;

import javax.swing.JTree;
import javax.swing.tree.DefaultTreeCellRenderer;

public class MonTreeCellRenderer extends DefaultTreeCellRenderer {

  public Component getTreeCellRendererComponent(JTree tree, Object value,
      boolean selected, boolean expanded, boolean leaf, int row,
      boolean hasFocus) {


    super.getTreeCellRendererComponent(tree,value, selected, expanded,
        leaf, row,hasFocus);

    setBackgroundNonSelectionColor(Color.gray);
    setBackgroundSelectionColor(Color.black);
    setTextSelectionColor(Color.white);
    setTextNonSelectionColor(Color.black);

    return this;
  }
}

Une fois la classe de type DefaultTreeCellRenderer instanciée, il faut utiliser la méthode setCellRenderer() de la classe JTree pour indiquer à l'arbre d'utiliser cette classe pour le rendu.

Exemple ( code Java 1.1 ) :
    jTree.setCellRenderer(new MonTreeCellRenderer());

La création d'une classe fille de la classe DefaultTreeCellRenderer ne fonctionne correctement qu'avec les look and feel Metal et Windows car le look and feel Motif définit son propre Renderer.

 

38.8.6.2. Les bulles d'aides (Tooltips)

Le composant JTree ne propose pas de support pour les bulles d'aide en standard. Pour permettre à un composant JTree d'afficher une bulle d'aide, il faut :

L'enregistrement du composant auprès du ToolTipManager se fait en utilisant la méthode registerComponent() sur l'instance partagée.

Exemple ( code Java 1.1 ) :
ToolTipManager.sharedInstance().registerComponent(jTree); 
((JLabel)t.getCellRenderer()).setToolTipText("Arborescence des données");

L'inconvénient de cette méthode est que la bulle d'aide est toujours la même quelque soit la position de la souris sur tous les noeuds du composant. Pour assigner une bulle d'aide particulière à chaque noeud, il est nécessaire d'utiliser la méthode setToolTipText() dans la méthode getTreeCellRendererComponent() d'une instance fille de la classe DefaultTreeCellRenderer 

Exemple ( code Java 1.1 ) :
      jTree.setCellRenderer(new DefaultTreeCellRenderer() {

        public Component getTreeCellRendererComponent(JTree tree, Object value,
            boolean selected, boolean expanded, boolean leaf, int row,
            boolean hasFocus) {

          super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row,
              hasFocus);
          setToolTipText(value.toString());

          return this;
        }
      });

      ToolTipManager.sharedInstance().registerComponent(jTree);

 

38.9. Les menus

Les menus de Swing proposent certaines caractéristiques intéressantes en plus de celles proposées par un menu standard :

Les menus sont mis en oeuvre dans Swing avec un ensemble de classe :

Toutes ces classes héritent de façon directe ou indirecte de la classe JComponent.

Les éléments de menus cliquables héritent de la classe JAbstractButton.

JMenu hérite de la classe JMenuItem et non pas l'inverse car chaque JMenu contient un JMenuItem implicite qui encapsule le titre du menu.

La plupart des classes utilisées pour les menus implémentent l'interface MenuElement. Cette interface définit des méthodes pour la gestion des actions standards de l'utilisateur. Ces actions sont gérées par la classe MenuSelectionManager.

Exemple :
package com.jmdoudoux.test.swing.menu;

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

public class TestMenuSwing1 extends JMenuBar {

  public TestMenuSwing1() {

    // Listener générique qui affiche l'action du menu utilisé
    ActionListener afficherMenuListener = new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        System.out.println("Elément de menu [" + event.getActionCommand()
            + "] utilisé.");
      }
    };

    // Création du menu Fichier
    JMenu fichierMenu = new JMenu("Fichier");
    JMenuItem item = new JMenuItem("Nouveau", 'N');
    item.addActionListener(afficherMenuListener);
    fichierMenu.add(item);
    item = new JMenuItem("Ouvrir", 'O');
    item.addActionListener(afficherMenuListener);
    fichierMenu.add(item);
    item = new JMenuItem("Sauver", 'S');
    item.addActionListener(afficherMenuListener);
    fichierMenu.insertSeparator(1);
    fichierMenu.add(item);
    item = new JMenuItem("Quitter");
    item.addActionListener(afficherMenuListener);
    fichierMenu.add(item);

    // Création du menu Editer
    JMenu editerMenu = new JMenu("Editer");
    item = new JMenuItem("Copier");
    item.addActionListener(afficherMenuListener);
    item.setAccelerator(KeyStroke.getKeyStroke('C', Toolkit.getDefaultToolkit()
        .getMenuShortcutKeyMask(), false));
    editerMenu.add(item);
    item = new JMenuItem("Couper");
    item.addActionListener(afficherMenuListener);
    item.setAccelerator(KeyStroke.getKeyStroke('X', Toolkit.getDefaultToolkit()
        .getMenuShortcutKeyMask(), false));
    editerMenu.add(item);
    item = new JMenuItem("Coller");
    item.addActionListener(afficherMenuListener);
    item.setAccelerator(KeyStroke.getKeyStroke('V', Toolkit.getDefaultToolkit()
        .getMenuShortcutKeyMask(), false));
    editerMenu.add(item);

    // Création du menu Divers
    JMenu diversMenu = new JMenu("Divers");
    JMenu sousMenuDiver1 = new JMenu("Sous menu 1");

    item.addActionListener(afficherMenuListener);
    item = new JMenuItem("Sous menu 1 1");
    sousMenuDiver1.add(item);
    item.addActionListener(afficherMenuListener);
    JMenu sousMenuDivers2 = new JMenu("Sous menu 1 2");
    item = new JMenuItem("Sous menu 1 2 1");
    sousMenuDivers2.add(item);
    sousMenuDiver1.add(sousMenuDivers2);

    diversMenu.add(sousMenuDiver1);
    item = new JCheckBoxMenuItem("Validé");
    diversMenu.add(item);
    item.addActionListener(afficherMenuListener);
    diversMenu.addSeparator();
    ButtonGroup buttonGroup = new ButtonGroup();
    item = new JRadioButtonMenuItem("Cas 1");
    diversMenu.add(item);
    item.addActionListener(afficherMenuListener);
    buttonGroup.add(item);
    item = new JRadioButtonMenuItem("Cas 2");
    diversMenu.add(item);
    item.addActionListener(afficherMenuListener);
    buttonGroup.add(item);
    diversMenu.addSeparator();
    diversMenu.add(item = new JMenuItem("Autre",
        new ImageIcon("about_32.png")));
    item.addActionListener(afficherMenuListener);

    // ajout des menus à la barre de menus
    add(fichierMenu);
    add(editerMenu);
    add(diversMenu);
  }

  public static void main(String s[]) {
    JFrame frame = new JFrame("Test de menu");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setJMenuBar(new TestMenuSwing1());
    frame.setMinimumSize(new Dimension(250, 200));
    frame.pack();
    frame.setVisible(true);
  }
}

Résultat :

 

38.9.1. La classe JMenuBar

La classe JMenuBar encapsule une barre de menus qui contient zéro ou plusieurs menus.

La classe JMenuBar utilise la classe DefaultSingleSelectionModel comme modèle de données : un seul de ces menus peut être activé à un instant T.

Pour ajouter des menus à la barre de menus, il faut utiliser la méthode add() de la classe JMenuBar qui attend en paramètre l'instance du menu.

Pour ajouter la barre de menus à une fenêtre, il faut utiliser la méthode setJMenuBar() d'une instance des classes JFrame, JInternalFrame, JDialog ou JApplet.

Comme la classe JMenuBar hérite de la classe JComponent, il est aussi possible d'instancier plusieurs JMenuBar et de les insérer dans un gestionnaire de positionnement comme n'importe quel composant. Ceci permet aussi de placer le menu à sa guise.

Exemple :
...
  public static void main(String s[]) {
    JFrame frame = new JFrame("Test de menu");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    TestMenuSwing1 menu = new TestMenuSwing1();
    frame.getContentPane().add(menu, BorderLayout.SOUTH);
    frame.setMinimumSize(new Dimension(250, 200));
    frame.pack();
    frame.setVisible(true);
  }
...

Résultat :

Swing n'impose pas d'avoir un unique menu par fenêtre : il est possible d'avoir plusieurs menus dans une même fenêtre.

Exemple :
...
  public static void main(String s[]) {
    JFrame frame = new JFrame("Test de menu");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setJMenuBar(new TestMenuSwing1());
    TestMenuSwing1 menu = new TestMenuSwing1();
    frame.getContentPane().add(menu, BorderLayout.SOUTH);
    frame.setMinimumSize(new Dimension(250, 200));
    frame.pack();
    frame.setVisible(true);
  }
...

La classe JMenuBar ne possède qu'un seul constructeur sans paramètre.

Les principales méthodes de la classe JMenuBar sont :

Méthodes

Rôle

JMenu add(JMenu)

Ajouter un menu à la barre de menus

JMenu getMenu(int)

Obtenir le menu dont l'index est fourni en paramètre

int getMenuCount()

Obtenir le nombre de menus de la barre de menus

MenuElement[] getSubElements()

Obtenir un tableau de tous les menus

boolean isSelected()

Retourner true si un composant du menu est sélectionné

void setMenuHelp (JMenu)

Cette méthode n'est pas implémentée et lève systématiquement une exception

 

38.9.2. La classe JMenuItem

La classe JMenuItem encapsule les données d'un élément de menu (libellé et/ou image). Elle hérite de la classe AbstractButton. Le comportement est similaire mais différent de celui d'un bouton : avec la classe JMenuItem, le composant est considéré comme sélectionné dès que le curseur de la souris passe dessus.

Les éléments de menus peuvent  être associés à deux types de raccourcis clavier :

La méthode setAccelerator() permet d'associer un accelerator à un élément de type JMenuItem.

Un mnemonic peut être associé à un JMenuItm de deux façons :

Le mnemonic correspond à un caractère qui doit obligatoirement être contenu dans le libellé.

Un élément de menu peut contenir uniquement une image ou être composé d'un libellé et d'une image. Une image peut être associée à un JMenuItem de deux façons :

 

38.9.3. La classe JPopupMenu

La classe JPopupMenu encapsule un menu flottant qui n'est pas rattaché à une barre de menus mais à un composant.

La création d'un JPopMenu est similaire à la création d'un JMenu.

Il est préférable d'ajouter un élément de type JMenuItem grâce à la méthode add() de la classe JPopupMenu  mais on peut aussi ajouter n'importe quel élément qui hérite de la classe Component en utilisant une surcharge de la méthode add().

Il est possible d'ajouter un élément à un index précis en utilisant la méthode insert().

La méthode addSeparator() permet d'ajouter un élément séparateur.

Pour afficher un menu flottant, il faut ajouter un listener sur l'événement déclenchant et utiliser la méthode show() de la classe JPopupMenu.

Exemple :
package com.jmdoudoux.test.swing.menu;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;

public class TestMenuSwing2 extends JMenuBar {

  public JPopupMenu popup;

  public TestMenuSwing2() {

    JMenuItem item = null;
    
    // Listener générique qui affiche l'action du menu utilisé
    ActionListener afficherMenuListener = new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        System.out.println("Elément de menu [" + event.getActionCommand()
            + "] utilisé.");
      }
    };
    
    popup = new JPopupMenu();
    item = new JMenuItem("Copier");
    item.addActionListener(afficherMenuListener);
    popup.add(item);
    item = new JMenuItem("Couper");
    item.addActionListener(afficherMenuListener);
    popup.add(item);
    
  }

  public void processMouseEvent(MouseEvent e) {
  }

  public static void main(String s[]) {
    final JFrame frame = new JFrame("Test de menu divers");
    final JTextField texte = new JTextField();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    final TestMenuSwing2 tms = new TestMenuSwing2();
    frame.add(texte);

    texte.addMouseListener(new MouseAdapter() {

      public void mouseClicked(MouseEvent e) {
        System.out.println("mouse clicked");
        afficherPopup(e);
      }
            
      public void mousePressed(MouseEvent e) {
        System.out.println("mouse pressed");
        afficherPopup(e);
      }

      public void mouseReleased(MouseEvent e) {
        System.out.println("mouse released");
        afficherPopup(e);
      }

      private void afficherPopup(MouseEvent e) {
        if (e.isPopupTrigger()) {
          tms.popup.show(texte, e.getX(), e.getY());
        } 
      }
    });

    frame.setMinimumSize(new Dimension(250, 200));
    frame.pack();
    frame.setVisible(true);
  }
}

Le plus simple pour être multiplate-forme est de tester sur tous les événements de la souris ceux qui permettent l'affichage du menu flottant. Ce test est réalisé grâce à la méthode isPopupTrigger() de la classe MouseEvent.

La propriété invoker encapsule le composant à l'origine de l'affichage du menu déroulant.
La propriété borderPaint indique si la bordure du menu déroulant doit être dessinée.
La propriété visible indique si le menu déroulant est affiché.
La propriété location indique les coordonnées d'affichage du menu déroulant

Un objet de type JPopupMenu peut émettre des événements de type PopupMenuEvent. Ceux-ci sont traités par un listener de type PopupMenuListener qui définit trois méthodes :

Méthode  Rôle
popupMenuCanceled()  méthode appelée avant que l'affichage du menu déroulant ne soit annulé
popoupMenuWillBecomeInvisible()  méthode appelée avant que le menu déroulant ne devienne invisible
popoupMenuWillBecomeVisible()  méthode appelée avant que le menu déroulant ne devienne visible. Cette méthode permet de personnaliser l'affichage des éléments du menu en fonction du contexte (exemple : rendre actif ou non certains éléments du menu)

 

38.9.4. La classe JMenu

La classe JMenu encapsule un menu qui est attaché à un objet de type JMenuBar ou à un autre objet de type JMenu. Dans ce second cas, l'objet est un sous menu.

Il est possible d'ajouter un élément sous la forme d'un objet de type JMenuItem, Component ou Action en utilisant la méthode add(). Chaque élément du menu possède un index.

La méthode addSeparator() permet d'ajouter un élément de type séparateur.
La méthode remove() permet de supprimer un élément du menu en fournissant en paramètre l'instance de l'élément ou son index. Si la suppression réussie, les index des éléments suivants sont décrémentés d'une unité.

La classe JMenu possède plusieurs propriétés :

Propriété Rôle
popupMenu JPopupMenu qui encapsule les éléments du menu
topLevelMenu propriété en lecture seule qui précise si le menu est attaché à un JMenuBar. La valeur false indique que le menu est un sous-menu attaché à un autre menu
itemCount indique le nombre d'éléments du menu (incluant les séparateurs)
delay précise le temps en millisecondes avant l'affichage du menu
menuComponentCount indique le nombre de composants du menu
tearOff ne pas utiliser cette propriété qui lève une exception de type Error

La méthode getMenuComponent() permet d'obtenir le composant du menu dont l'index est fourni en paramètre.  La méthode getItem() permet d'obtenir le JMenuItem dont l'index est fourni en paramètre.

La méthode menuComponents() renvoie un tableau des composants du menu.

La méthode isMenuComponent() renvoie un booléen qui précise si le composant fourni en paramètre est inclus dans les éléments du menu.

Un événement de type MenuEvent est émis lorsque le titre du menu est cliqué. Un listener de type MenuListener permet de s'abonner à ces événements. L'interface MenuListener définie trois méthodes qui possèdent un paramètre de type MenuEvent :

Méthodes Rôle
menuCanceled() invoquée lorsque le menu est effacé
menuDeselected() invoquée lorsque le titre du menu est désélectionné
menuSelected() invoquée lorsque le titre du menu est sélectionné

 

38.9.5. La classe JCheckBoxMenuItem

Cette classe encapsule un élément du menu qui contient une case à cocher.

Elle possède de nombreux constructeurs qui permettent de préciser le texte, une icône et l'état de la case à cocher.

La propriété state() permet de connaître ou de définir l'état de la case à cocher.

 

38.9.6. La classe JRadioButtonMenuItem

Cette classe encapsule un élément de menu qui contient un bouton radio. A un instant donné, un seul des boutons radio associés à un même groupe peut être sélectionné.

La définition de ce groupe se fait en utilisant la classe ButtonGroup. C'est d'ailleurs cette classe qui propose la méthode getSelected() pour connaître le bouton radio sélectionné dans le groupe.

Exemple :
...
	diversMenu.addSeparator();
    ButtonGroup buttonGroup = new ButtonGroup();
    item = new JRadioButtonMenuItem("Cas 1");
    diversMenu.add(item);
    item.addActionListener(afficherMenuListener);
    buttonGroup.add(item);
    item = new JRadioButtonMenuItem("Cas 2");
    diversMenu.add(item);
    item.addActionListener(afficherMenuListener);
    buttonGroup.add(item);
    diversMenu.addSeparator();
...

 

38.9.7. La classe JSeparator

La méthode addSeparator() des classes JMenu et JPopupMenu instancie un objet de type JSeparator et l'ajoute à la liste des éléments du menu.

La classe JSeparator encapsule un séparateur dans un menu.

Remarque : L'utilisation de cette classe ne se limite pas aux menus car elle peut aussi être utilisée comme un composant de l'interface.

Exemple :
package com.jmdoudoux.test.swing.menu;

import java.awt.Dimension;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JTextField;

public class TestMenuSwing3 extends JPanel {

  public TestMenuSwing3() {
    super(true);

    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
    Box box1 = new Box(BoxLayout.X_AXIS);
    Box box2 = new Box(BoxLayout.X_AXIS);
    Box box3 = new Box(BoxLayout.X_AXIS);
    Box box4 = new Box(BoxLayout.X_AXIS);
    Box box5 = new Box(BoxLayout.X_AXIS);
    box1.add(new JButton("Bouton 1"));
    box1.add(new JButton("Bouton 2"));
    box1.add(new JButton("Bouton 3"));
    box2.add(new JSeparator());
    box3.add(new JTextField(""));
    box4.add(new JSeparator());
    box5.add(new JButton("Bouton 4"));
    box5.add(new JButton("Bouton 5"));
    box5.add(new JButton("bouton 6"));
    add(box1);
    add(box2);
    add(box3);
    add(box4);
    add(box5);
  }

  public static void main(String s[]) {
    JFrame frame = new JFrame("Test separator");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setContentPane(new TestMenuSwing3());
    frame.setMinimumSize(new Dimension(250, 200));
    frame.pack();
    frame.setVisible(true);
  }
}

Résultat :

 

38.10. L'affichage d'une image dans une application.

Pour afficher une image dans une fenêtre, il y a plusieurs solutions.

La plus simple consiste à utiliser le composant JLabel qui est capable d'afficher du texte mais aussi une image

Exemple :
package com.jmdoudoux.test;

import java.awt.BorderLayout;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class MonApp  extends JFrame {
  
  private static final long serialVersionUID = 1L;
  
  public MonApp(String titre) {
    super(titre);
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    init();
  }
  
  private void init()
  {
    JLabel label = new JLabel(new ImageIcon("Duke.gif") );
    this.add(label, BorderLayout.CENTER);
    this.pack();    
  }
  
  public static void main(String[] args) {
    MonApp app = new MonApp("Afficher image");
    app.setVisible(true);
  }
}

Dans l'exemple ci-dessus, le fichier contenant l'image doit être à la racine des fichiers class : aucun chemin n'est précisé donc c'est le chemin relatif au répertoire d'exécution de l'application qui est retenu. Il est possible de préciser un chemin absolu mais cela limite les possibilités de déploiement de l'application.

C:\MonApp\src>javac com/jmdoudoux/test/MonApp.java

C:\MonApp\src>java com.jmdoudoux.test.MonApp

Il est possible de définir un composant personnalisé qui hérite de la classe JPanel qui va se charger d'afficher l'image.

Historiquement, c'est la classe java.awt.Toolkit qui peut être utilisée pour charger une image.

Exemple :
package com.jmdoudoux.test;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Panel;
import java.awt.Toolkit;

/**
 * Composant qui affiche une image
 */
public class AfficheImage extends Panel {

  private static final long serialVersionUID = 1L;
  private Image image;

  public AfficheImage(String filename) {
    image = Toolkit.getDefaultToolkit().getImage("./duke.gif");
    try {
      MediaTracker mt = new MediaTracker(this);
      mt.addImage(image, 0);
      mt.waitForAll();
    } catch (Exception e) {
      e.printStackTrace();
    }
    this.setPreferredSize(new Dimension(image.getWidth(this), image
        .getHeight(this)));
  }

  public void paint(Graphics g) {
    g.drawImage(image, 0, 0, null);
  }
}

L'inconvénient d'utiliser la classe Toolkit pour charger une image est que ce chargement se fait de façon asynchrone. Il faut alors utiliser une instance de la classe MediaTracker pour patienter le temps du chargement de l'image et ainsi pouvoir déterminer sa taille pour la reporter sur la taille du composant.

A partir de Java 1.4, il est aussi possible d'utiliser la classe javax.imageio.ImageIO pour simplifier le code qui charge l'image.

Exemple :
package com.jmdoudoux.test;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Panel;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

/**
 * Composant qui affiche une image
 */ 
public class AfficheImage extends Panel {

  private static final long serialVersionUID = 1L;
  private BufferedImage image;

  public AfficheImage(String nomFichier) {
    
    try {
      image = ImageIO.read(new File(nomFichier));
      this.setPreferredSize(new Dimension(image.getWidth(),
        image.getHeight()));
    } catch (IOException ie) {
      ie.printStackTrace();
    }
  }

  public void paint(Graphics g) {
    g.drawImage(image, 0, 0, null);
  }
}

Il suffit alors d'utiliser le composant dans la fenêtre

Exemple :
package com.jmdoudoux.test;
import java.awt.BorderLayout;
import javax.swing.JFrame;

public class MonApp extends JFrame {

  private static final long serialVersionUID = 1L;

  public MonApp(String titre) {
    super(titre);
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    init();
  }

  private void init()
  {
    AfficheImage afficheImage = new AfficheImage("Duke.gif");
    this.setLayout(new BorderLayout());
    this.add(afficheImage, BorderLayout.CENTER);
    this.pack();
  }

  public static void main(String[] args) {
    MonApp app = new MonApp("Afficher image");
    app.setVisible(true);
  }
}

Malheureusement, ces deux solutions ne fonctionnent pas si l'application est packagée sous la forme d'une archive qui contient l'image car l'API java.io n'est pas capable de lire une ressource dans l'archive jar. Il faut utiliser le classloader pour charger l'image sous la forme d'une ressource. L'avantage de cette solution c'est qu'elle fonctionne que l'application soit packagée ou non.

Exemple :
package com.jmdoudoux.test;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Panel;
import java.awt.Toolkit;

/**
 * Composant qui affiche une image
 */
public class AfficheImage extends Panel {

  private static final long serialVersionUID = 1L;
  private Image image;

  public AfficheImage(String filename) {
    java.net.URL url = this.getClass().getResource("Duke.gif");
    image = Toolkit.getDefaultToolkit().getImage(url);
    try {
      MediaTracker mt = new MediaTracker(this);
      mt.addImage(image, 0);
      mt.waitForAll();
    } catch (Exception e) {
      e.printStackTrace();
    }
    this.setPreferredSize(new Dimension(image.getWidth(this), image
        .getHeight(this)));
  }

  public void paint(Graphics g) {
    g.drawImage(image, 0, 0, null);
  }
}

Le fichier contenant l'image doit être accessible par le classloader dans le classpath, par exemple :

 


  37. L'interception des actions de l'utilisateur 39. Le développement d'interfaces graphiques avec SWT Imprimer Sommaire Consulter avec table des matières Développons en Java   v 2.10  
Copyright (C) 1999-2016 .