Développons en Java v 2.40 Copyright (C) 1999-2023 Jean-Michel DOUDOUX. |
|||||||
Niveau : | 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 :
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.
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 |
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();
}
}
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();
}
}
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().
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);
}
}
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);
}
}
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.
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);
}
}
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);
}
}
);
}
}
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.
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.
Il existe plusieurs boutons définis par Swing.
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.
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.
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.
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);
}
}
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);
}
}
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;
}
Swing possède plusieurs composants pour permettre la saisie de texte.
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.
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.
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.
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.
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);
}
}
|
La suite de cette section sera développée dans une version future de ce document
|
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);
}
}
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.
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().
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.
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.
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.
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é.
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.
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.
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() |
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.
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");
}
});
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);
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é.
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());
}
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());
}
});
}
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.
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.
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);
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é |
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 |
void removeSelectionRows(int[] rows) |
Enlève de la sélection les noeuds dont les index sont fournis en paramètre |
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 |
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 |
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. |
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. |
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 |
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.
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 {
}
});
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);
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.
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);
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 fr.jmdoudoux.dej.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 :
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 |
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 :
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 fr.jmdoudoux.dej.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) |
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é |
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.
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();
...
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 fr.jmdoudoux.dej.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 :
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 fr.jmdoudoux.dej;
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 fr.jmdoudoux.dej.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 fr.jmdoudoux.dej;
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 fr.jmdoudoux.dej;
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 fr.jmdoudoux.dej;
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 fr.jmdoudoux.dej;
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 :
Développons en Java v 2.40 Copyright (C) 1999-2023 Jean-Michel DOUDOUX. |