You are on page 1of 35

Les arbres et les tableaux

Chapitres traits
Les arbres - JTree

Nous avons dcrit pratiquement l'ensemble des composants swing les plus utiliss pour concevoir des interfaces homme machine relativement sophistiqus. Il nous reste voir deux composants qui peuvent galement s'avrer trs utiles dans certaines situations. Le premier composant reprsent par la classe JTree permet de visualiser une structure hirarchique sous forme d'arbre. Naturellement, nous connaissons bien l'arbre propos par les explorateurs qui visualise le systme de fichier en sparant bien les rpertoires, les sousrpertoires et les fichiers. Il existe un grand nombre de structures en arbres dans la vie de tous les jours, et nous verrons au cours de cette tude comment les mettre en oeuvre. De mme, il existe dans swing un composant, JTable, trs labor, qui affiche une grille bidimensionnelle d'objets. L aussi, les tableaux sont trs courant dans les interfaces utilisateur. De par leur nature, les tableaux sont compliqus, mais, peut-tre plus que d'autres classes de swing, le composant JTable prend en charge la plus grosse partie de cette complexit. Vous pourrez produire des tableaux parfaitement fonctionnels avec un comportement trs riche, en crivant uniquement quelques lignes de code.

Les arbres - JTree


Tous les utilisateurs d'ordinateurs possdant un systme de fichiers hirarchiques ont dj rencontr des arbres. En tant que programmeurs, il nous faut souvent afficher des structures hirarchiques. La classe JTree prend en charge l'organisation des arbres et le traitement des requtes de l'utilisateur visant ajouter et supprimer des noeuds.

Terminologie
Avant de poursuivre, je pense qu'il est souhaitable de se mettre d'accord sur quelques lments de terminologie : 1. Un arbre est compos de noeuds. 2. Un noeud peut soit tre une feuille, soit possder des noeuds enfants. 3. Chaque noeud, l'exception du noeud de dpart (la racine), possde un seul parent. 4. Un arbre possde un seul noeud de dpart. 5. Des arbres peuvent tre assembls dans un groupe, chaque arbre possdant sa propre racine. Ce type de groupe est appel une fort.

Modle MVC
JTree est l'un des composants les plus labors. Les arborescences conviennent parfaitement la reprsentation hirarchique d'informations, comme le contenu d'un disque dur ou l'organigramme d'une entreprise. Comme la plupart des autres composants swing, le modle de donnes est distinct de la reprsentation visuelle, et le composant JTree se doit de respecter cette architecture Modle-Vue-Contrleur. Ainsi, un modle de donnes hirarchiques doit tre fourni l'arbre qui affiche alors ces donnes pour vous. Cela signifie que vous pouvez par exemple mettre jour le modle de donnes et tre certain que le composant visuel sera correctement actualis. JTree est trs puissant et complexe. En fait, il est si compliqu que les classes qui grent JTree possdent leur propre paquetage, javax.swing.tree. Nanmoins, si vous acceptez les options par dfaut presque partout, JTree s'avre trs simple utiliser.

Cration d'un arbre


Pour construire un JTree, et en respectant ce que nous venons d'voquer, vous devez spcifier le modle d'arbre dans le construsteur :
TreeModel modle = ... JTree arbre = new JTree(modle);

Il existe galement des constructeurs qui permettent de crer des arbres partir d'un ensemble d'lments :

1. JTree(Object[] noeuds) 2. JTree(Vector<?> noeuds) 3. JTree(Hashtable<?, ?> noeuds) : les valeurs sont transformes en noeuds.

Ces constructeurs ne sont pas trs utiles. Ils permettent surtout de gnrer des forts d'arbres, chaque arbre possdant un seul noeud. Le troisime constructeur semble particulirement inutile puisque les noeuds sont organiss selon l'ordre alatoire fourni par les codes de hachage des cls.

Les noeuds et les modles


Du coup, la question qui se pose, c'est comment obtenir un modle d'arbre ? Vous pouvez construire votre modle en crant une classe qui implmente l'interface TreeModel. Nous verrons plus loin dans cette tude comment procder. Le plus simple, consiste prendre le modle prdfini par dfaut dans la bibliothque swing, justement nomm DefaultTreeModel :
TreeNode racine = ... ; TreeModel modle = new DefaultTreeModel(racine); JTree arbre = new JTree(modle);

Le modle de donnes d'une arborescence est constitu de noeuds interconnects. Un noeud possde un nom, en principe un parent et un certain nombre d'enfants (ventuellement aucun). Dans swing, un noeud est reprsent par l'interface TreeNode. Les noeuds modifiables sont reprsents cette fois-ci par l'interface MutableTreeNode qui hrite en fait de TreeNode. L aussi, nous pouvons crer des classes de noeud qui implmentent ces interfaces, toutefois il existe une implmentaiton concrte de l'interface MutableTreeNode qui se nomme DefaultMutableTreeNode.
TreeNode racine = new DefaultMutableTreeNode ("Noeud racine"); TreeModel modle = new DefaultTreeModel(racine); JTree arbre = new JTree(modle);

Structure d'un noeud d'arbre mutable


Un noeud d'arbre mutable par dfaut renferme un objet, et plus prcisment un objet de l'utilisateur. L'arbre peut transformer les objets de l'utilisateur contenus dans chaque noeud. A moins que vous ne spcifiiez une mthode de transformation, l'arbre se contente d'afficher une chane rsultant de la mthode toString(). Dans l'exemple prcdent, nous nous servons de chanes comme objets de l'utilisateur. En pratique, vous remplirez probablement des arbres avec des objets d'utilisateur plus importants. Par exemple, pour afficher un arbre de rpertoire, il convient de le remplir avec des objets File. Vous pouvez spcifier le type des objets d'utilisateur dans le constructeur, mais vous pouvez galement le dfinir par la suite grce la mthode setUserObject() :
DefaultMutableTreeNode noeud = new DefaultMutableTreeNode ("Un noeud"); noeud.setUserObject("Un autre noeud");

Mise en place de la hirarchie


Ensuite, il faut tablir les relations hirarchies entre les parents et les enfants pour chaque noeud. 1. Commencez par le noeud racine, et utilisez la mthode add() pour ajouter des enfants :
DefaultMutableTreeNode DefaultMutableTreeNode DefaultMutableTreeNode DefaultMutableTreeNode racine.add(jpeg); racine.add(gif); racine.add(png); racine = new DefaultMutableTreeNode("Images"); jpeg = new DefaultMutableTreeNode ("JPEG"); gif = new DefaultMutableTreeNode("GIF"); png = new DefaultMutableTreeNode("PNG");

2. Vous devez relier tous les noeuds de cette manire. Construisez ensuite un DefaultTreeModel avec le noeud racine. Pour terminer, construisez un JTree avec le modle de l'arbre :
TreeModel modle = new DefaultTreeModel(racine); JTree arbre = new JTree(modle);

3. Plus simplement, il suffit de passer le noeud racine au constructeur JTree(). L'arbre construit alors automatiquement un modle d'arbre par dfaut :
JTree arbre = new JTree(racine);

Premire mise en application


Afin d'illustrer notre premire approche sur la gestion d'une structure arborescente, je vous propose de mettre en oeuvre une petite application qui permet de visualiser une image partir d'un rpertoire prdtermin. Le choix de l'image s'effectue partir d'un arbre situ sur la partie gauche. Cet arbre recense les images par type d'extension : JPEG, GIF et PNG.

codage correspondant
package arbres; import import import import import import import javax.swing.*; java.awt.*; java.awt.image.BufferedImage; java.io.*; javax.imageio.ImageIO; javax.swing.event.*; javax.swing.tree.DefaultMutableTreeNode;

public class Arbres extends JFrame implements TreeSelectionListener { private JTree arbre; private Vue vue = new Vue(); private String rpertoire = "C:/Photos/"; public Arbres() { super("Images"); construireArbre(); add(new JScrollPane(arbre), BorderLayout.WEST); add(vue); setSize(540, 300); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); }

public static void main(String[] args) { new Arbres(); } private void construireArbre() { File fichiers = new File(rpertoire); DefaultMutableTreeNode racine = new DefaultMutableTreeNode("Images"); DefaultMutableTreeNode jpeg = new DefaultMutableTreeNode("JPEG"); DefaultMutableTreeNode gif = new DefaultMutableTreeNode("GIF"); DefaultMutableTreeNode png = new DefaultMutableTreeNode("PNG"); racine.add(jpeg); racine.add(gif); racine.add(png); for (String nom : fichiers.list()) { if (nom.endsWith(".gif")) gif.add(new DefaultMutableTreeNode(nom)); else if (nom.endsWith(".jpeg") || nom.endsWith(".jpg")) jpeg.add(new DefaultMutableTreeNode (nom)); else if (nom.endsWith(".png")) png.add(new DefaultMutableTreeNode (nom)); } arbre = new JTree(racine); arbre.setPreferredSize(new Dimension(180, 1000)); arbre.addTreeSelectionListener (this); } private class Vue extends JComponent { private BufferedImage photo; private double ratio; @Override protected void paintComponent(Graphics g) { if (photo!=null) g.drawImage(photo, 0, 0, getWidth(), (int)(getWidth()/ratio), null); } public void setPhoto(File fichier) { try { photo = ImageIO.read(fichier); ratio = (double)photo.getWidth() / photo.getHeight(); repaint(); } catch (IOException ex) { setTitle("Impossible de lire le fichier");} } } public void valueChanged(TreeSelectionEvent e) { if (arbre.getSelectionPath()!=null) { String nom = arbre.getSelectionPath().getLastPathComponent ().toString(); vue.setPhoto(new File("C:/Photos/"+nom)); } } }

Lorsque vous excutez ce programme, seuls le noeud racine (Images) et ses enfants sont visibles (JPEG, GIF et PNG). Cliquez sur les poignes pour ouvrir les arbres de niveau infrieur. Le segment dpassant des poignes se trouve sur la droite lorsque le sous-rpertoire est cach, et il pointe vers le bas lorsque le sousrpertoire est affich. Il semble que ces segments reprsentent des poignes de porte. Il faut appuyer sur la poigne pour ouvrir le sous-rpertoire.

Changer l'apparence de l'arbre


Par dfaut l'arbre affiche des lignes entre les parents et les enfants, ce qui permet de bien vrifier rapidement l'appartenance des diffrents lments (le style par dfaut est Angled).

Il est possible d'enlever ces lignes de liaisons. Utilisez pour cela la mthode putClientProperty() de la classe JTree. Positionnez alors la proprit JTree.lineStyle None :
arbre.putClientProperty("JTree.lineStyle", "None");

A l'inverse, pour vous assurez que les lignes sont bien affiches, utilisez :
arbre.putClientProperty("JTree.lineStyle", "Angled");

Un autre style appel Horizontal permet d'afficher l'arbre avec des lignes horizontales sparant uniquement les enfants du noeud racine.
arbre.putClientProperty("JTree.lineStyle", "Horizontal");

Par dfaut, il n'existe aucune poigne pour cacher la racine d'un arbre. Si vous le dsirez, vous pouvez en ajouter une avec la mthode setShowsRootHandles() :
arbre.setShowsRootHandles(true);

Inversement, la racine peut tre entirement cache. Cela peut tre utile si vous souhaitez afficher une fort, c'est--dire un ensemble d'arbres possdant chacun leur propre racine. Vous devez cependant regrouper tous les arbres de la fort avec une seule racine commune. Vous devez alors cacher cette racine au moyen de la mthode setRootVisible() :
arbre.setRootVisible(false);

Il est possible de combiner ces deux dernires mthodes afin que tous les arbres de la fort disposent de poignes pour faciliter le dveloppement des feuilles :
arbre.setShowsRootHandles(true); arbre.setRootVisible(false);

Passons maintenant de la racine aux feuilles de l'arbre. Notez que les feuilles possdent une icne diffrente de celle des autres noeuds. Lorsque l'arbre est affich, chaque noeud est reprsent par une icne. Il existe en fait trois sortes d'icnes : 1. Les icnes de feuilles. 2. Les icnes de noeuds intermdiaires ouverts. 3. Les icnes de noeuds intermdiaires ferms.

Pour des raisons de simplicit, nous appelerons les deux dernires icnes, des icnes de rpertoire. L'afficheur de noeud doit savoir quelle icne utiliser pour chaque noeud. Par dfaut, cette dcision est prise de la faon suivante : si la mthode isLeaf() d'un noeud renvoie true, l'icne de feuille est utilise. Sinon, une icne de rpertoire est utilise.

La mthode isLeaf() de la classe DefaultMutableTreeNode renvoie true si le noeud ne possde aucun enfant. Par consquent, les noeuds possdant des enfants sont associs des icnes de rpertoire, et les noeuds sans enfant sont associs des icnes de feuille. Parfois, cette technique n'est pas toujours approprie. Supposons que nous ajoutions un noeud "Autre" dans notre exemple de visualisation d'images, qui permet de recenser les autres formats d'images, mais que le rpertoire en question ne possde pas d'lments. Il convient cependant d'viter d'affecter une icne de feuille ce noeud puisque seuls les fichiers images correspondent des feuilles. La classe JTree ne possde aucune information lui permettant de dterminer si un noeud doit tre considr comme une feuille ou comme un rpertoire. Elle le demande donc au modle de l'arbre. Si un noeud sans enfant n'est pas toujours interprt au plan conceptuel comme une feuille, vous pouvez demander au modle d'utiliser diffrents critres pour vrifier qu'un noeud est bien une feuille, en interrogeant la proprit AllowsChildren d'un noeud. Rgler cette proprit au moyen de la mthode setAllowsChildren() : 1. Pour les noeuds correspondant des feuilles qui ne devraient donc pas avoir d'enfant :
noeud.setAllowsChildren(false);

2. Pour les noeuds qui possdent des enfants :


noeud.setAllowsChildren(true);

Il est possible galement d'utiliser le constructeur de la classe DefaultMutableTreeNode qui dispose de deux paramtres, le deuxime attend une valeur boolenne spcifiant si le noeud va possder des enfants ou pas : 1. Pour les noeuds correspondant des feuilles qui ne devraient donc pas avoir d'enfant :
DefaultMutableTreeNode noeud = new DefaultMutableTreeNode("Noeud", false);

2. Pour les noeuds qui possdent des enfants :


DefaultMutableTreeNode noeud = new DefaultMutableTreeNode("Noeud", true);

Ensuite, il faut indiquer au modle qu'il doit examiner la proprit AllowsChildren d'un noeud pour savoir s'il doit tre affich avec une icne de feuille ou non. La mthode setAskAllowsChildren() de la classe DefaultTreeModel permet de dfinir ce comportement :
TreeModel modle = new DefaultTreeModel(racine); modle.setAskAllowsChildren(true);

Ici aussi, vous pouvez galement prvoir cette fonctionnalit directement partir du constructeur du modle, en proposant la valeur true sur le deuxime argument :
TreeModel modle = new DefaultTreeModel(racine, true);

A partir de ces critres de dcision, les noeuds susceptibles d'avoir des enfants sont associs des icnes de rpertoire, et les autres des icnes de feuilles. Sinon, si vous construisez un arbre en fournissant un noeud racine (sans modle), vous pouvez spcifier ce comportement galement directement dans le constructeur de la classe JTree :
JTree arbre = new JTree(racine, true);

modification du source correspondant l'apparence ci-contre


private void construireArbre() { File fichiers = new File(rpertoire); DefaultMutableTreeNode racine = new DefaultMutableTreeNode ("Images"); DefaultMutableTreeNode jpeg = new DefaultMutableTreeNode("JPEG", true); DefaultMutableTreeNode gif = new DefaultMutableTreeNode ("GIF", true); DefaultMutableTreeNode png = new DefaultMutableTreeNode ("PNG", true); DefaultMutableTreeNode autre = new DefaultMutableTreeNode("Autre", true); racine.add(jpeg); racine.add(gif); racine.add(png); racine.add(autre); for (String nom : fichiers.list()) { if (nom.endsWith(".gif")) gif.add(new DefaultMutableTreeNode(nom, false)); else if (nom.endsWith(".jpeg") || nom.endsWith(".jpg")) jpeg.add(new DefaultMutableTreeNode (nom, false)); else if (nom.endsWith(".png")) png.add(new DefaultMutableTreeNode (nom, false)); else autre.add(new DefaultMutableTreeNode(nom, false)); } arbre = new JTree(racine, true); arbre.setPreferredSize(new Dimension(180, 1000)); arbre.addTreeSelectionListener (this); }

Identifier les noeuds d'un arbre


Une fois que l'arbre est constitu, notamment de faon automatique, il peut tre intressant de retrouver certains noeuds suivant les critres dsirs. La classe DefaultMutableTreeNode dispose d'un grand nombre de mthodes qui vons nous aider pour rsoudre ce problme. Deux dmarches sont utilises : 1. La premire consiste numrer l'ensemble des noeuds, quelque soit la profondeur, partir d'un noeud prcis. 2. La deuxime nous permet de rcuprer un noeud en particulier suivant sa qualit : une feuille, un enfant, un parent, etc.

Enumration des noeuds


Il arrive parfois que vous deviez trouver un noeud dans un arbre, en partant de la racine et en passant en revue tous les enfants jusqu' ce que vous ayez trouv le noeud. La classe DefaultMutableTreeNode possde plusieurs mthodes pratiques pour parcourir les noeuds d'un arbre. Les mthodes breadthFirstEnumeration() et depthFirstEnumeration() renvoient des objets d'numration dont la mthode nextElement() parcourt tous les enfants du noeud courant, en utilisant soit une approche horizontale, soit une approche verticale. 1. L'approche horizontale est la plus simple visualiser. L'arbre est parcouru par niveaux, en commenant par la racine, suivie de tous ses enfants, puis de tous ses petits-enfants, etc.

2. Pour visualiser une approche verticale, imaginez qu'un rat soit emprisonn dans un labyrinthe en forme d'arbre. Il descend l'arbre jusqu' ce qu'il trouve une feuille, puis il remonte d'un niveau et parcourt la prochaine branche, etc.

Cette dernire approche est aussi appele une traverse postrieure en informatique, parce que la recherche commence par les enfants avant d'arriver aux parents. La mthode postOrderTraversal() est donc quivalente la mthode depthFirstTraversal(). Pour que la bibliothque soit complte, il existe aussi une mthode preOrderTraversal() qui propose galement une recherche verticale qui passe en revue les parents avant les enfants.
Voici un exemple typique d'utilisation : Enumeration recherche = racine.breadthFirstEnumeration(); while (recherche.hasMoreElements()) { DefaultMutableTreeNode noeud = (DefaultMutableTreeNode) recherche.nextElement(); ... }

Il existe une mthode pathFromAncestorEnumeration() qui trouve un chemin entre un anctre et un noeud spcifi, puis parcourt tous les noeuds se trouvant sur ce chemin. Cette mthode est assez simple, en fait elle se contente d'appeler la mthode getParent() jusqu' ce que l'anctre spcifi soit trouv, puis elle affiche ensuite en sens inverse le chemin parcouru.
Enumeration recherche = feuille.breadthFirstEnumeration(racine);

Pour terminer la mthode children() renvoit une numration des enfants (immdiats : premier niveau sans les petits enfants) d'un noeud :
Enumeration enfants = racine.children();

Recherche d'un noeud en particulier


Il existe ensuite des mthodes de la classe DefaultMutableTreeNode que nous allons recenser, qui vont nous permettre de naviguer dans l'arborescence la recherche d'un noeud en particulier : TreeNode getChildAfter(TreeNode enfant) : renvoie le noeud enfant suivant celui propos en argument de la mthode. TreeNode getChildBefore(TreeNode enfant) : renvoie le noeud enfant prcdent celui propos en argument de la mthode. int getChildCount() : renvoie le nombre de noeuds enfant. int getDepth() : renvoie la profondeur d'imbrication des noeuds allant de ce noeud jusqu' la feuille la plus loigne. TreeNode getFirstChild() : renvoie le premier noeud enfant. DefaultMutableTreeNode getFirstLeaf() : Recherche et renvoie la premire feuille prsente dans ce noeud anctre. TreeNode getLastChild() : renvoie le dernier noeud enfant. DefaultMutableTreeNode getLastLeaf() : Recherche et renvoie la dernire feuille prsente dans ce noeud anctre. int getLeafCount() : renvoie le nombre de feuilles prsentes partir de ce noeud anctre. int getLevel() : renvoie le niveau d'imbrication de ce noeud partir de la racine. DefaultMutableTreeNode getNextLeaf() : renvoie la prochaine feuille partir de celle-ci ou null si c'est la dernire. DefaultMutableTreeNode getNextNode() : renvoie le noeud suivant. DefaultMutableTreeNode getNextSibling() : renvoie le noeud frre suivant.

TreeNode getParent() : renvoie le noeud parent ou null si le la demande est faite par le noeud racine. TreeNode[] getPath() : renvoie le chemin complet allant de la racine au noeud concern. DefaultMutableTreeNode getPreviousLeaf() : renvoie la prcdente feuille partir de celle-ci ou null si c'est la premire. DefaultMutableTreeNode getPreviousNode() : renvoie le noeud prcdent. DefaultMutableTreeNode getPreviousSibling() : renvoie le noeud frre prcdent. TreeNode getRoot() : renvoie le noeud racine. TreeNode getSharedAncestor(DefaultMutableTreeNode noeud) : renvoie le plus proche noeud commun. int getSiblingCount() : renvoie le nombre de frres. Object getUserObject() : renvoie l'objet utilisateur, c'est--dire l'objet qui est plac dans le noeud. C'est trs souvent un texte, mais cela peut tre n'importe quel type d'objet. boolean isLeaf() : indique si ce noeud ne dispose pas d'enfant qui dans ce cas l se nomme une feuille. boolean isNodeAncestor(TreeNode autreNoeud) : indique si le noeud pass en argument est un noeud anctre de celui-ci. boolean isNodeChild(TreeNode noeud) : indique si le noeud pass en argument est un enfant de celui-ci. boolean isNodeDescendant(DefaultMutableTreeNode autreNoeud) : indique si le noeud pass en argument est un descendant de celui-ci. boolean isNodeRelated(DefaultMutableTreeNode noeud) : indique si le noeud pass en argument fait parti du mme arbre. boolean isNodeSibling(TreeNode autreNoeud) : indique si le noeud pass en argument est un frre de celui-ci. boolean isRoot() : s'agit-il du noeud racine ? String toString() : renvoie l'intitul du noeud sous forme de chane de caractres. void add(MutableTreeNode nouvelEnfant) : Enlve ce neud enfant de l'arborescence prcdente et le rajoute la fin des enfants de ce noeud-ci. void remove(MutableTreeNode noeud) : Enlve le noeud spcifi en argument faisant parti de la descendance du noeud en cours. void removeAllChildren() : Enlve l'ensemble des noeuds enfants faisant partis du noeud en cours. void removeFromParent() : Enlve le noeud actuel ainsi que ses descendant de l'arborescence actuelle. Le noeud actuel ne dispose alors plus de parent et devient donc noeud racine. void setAllowsChildren(boolean fils) : dtermine si le noeud courant doit possder des enfants ou pas. void setParent(MutableTreeNode nouveauParent) : propose un nouveau parent au noeud en cours. void setUserObject(Object objetUtilisateur) : propose un nouvel objet utilisateur au noeud en cours, c'est--dire l'objet qui est plac dans le noeud. C'est trs souvent un texte, mais cela peut tre n'importe quel type d'objet.

Mise en pratique de ces diffrentes recherches


Nous allons reprendre l'application prcdente sur laquelle nous allons faire quelque petites modifications. Nous allons en effet restructurer l'arborescence de l'arbre des fichiers. Le noeud racine s'appelle cette fois-ci "Fichiers". Ce noeud racine comporte deux autres noeuds : le premier intitul "Images" et le second "Autre". Cette fois-ci, la mise en place des feuilles de l'arbre, correspondant aux fichiers prsents dans le rpertoire choisi, s'effectue automatiquement suivant le nom des noeuds fils proposs partir du noeud "Images". S'il reste des fichiers, ceux-ci sont automatiquement placs dans le noeud "Autre".

codage correspondant
package arbres; import import import import import import import import javax.swing.*; java.awt.*; java.awt.image.BufferedImage; java.io.*; java.util.*; javax.imageio.ImageIO; javax.swing.event.*; javax.swing.tree.*;

public class Arbres extends JFrame implements TreeSelectionListener { private JTree arbre; private DefaultMutableTreeNode racine; private DefaultMutableTreeNode images; private DefaultMutableTreeNode autre; private Vue vue = new Vue(); private String rpertoire = "C:/Photos/"; public Arbres() { super("Images"); construireArbre(); add(new JScrollPane(arbre), BorderLayout.WEST); add(vue); setSize(540, 300); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Arbres(); } private void construireArbre() { racine = new DefaultMutableTreeNode("Fichiers", true); images = new DefaultMutableTreeNode("Images", true);

DefaultMutableTreeNode jpeg = new DefaultMutableTreeNode("JPG", true); DefaultMutableTreeNode gif = new DefaultMutableTreeNode("GIF", true); DefaultMutableTreeNode png = new DefaultMutableTreeNode("PNG", true); autre = new DefaultMutableTreeNode ("Autre", true); racine.add(images); racine.add(autre); images.add(jpeg); images.add(gif); images.add(png); arbre = new JTree(racine, true); arbre.setPreferredSize(new Dimension(180, 1000)); arbre.addTreeSelectionListener (this); ajouterFichiers(); } private void ajouterFichiers() { File fichiers = new File(rpertoire); ArrayList<String> liste = new ArrayList<String>(); for (String nom : fichiers.list()) liste.add(nom); Enumeration recherche = images.children(); while (recherche.hasMoreElements()) { DefaultMutableTreeNode noeud = (DefaultMutableTreeNode ) recherche.nextElement(); for (String nom : liste) { String extension = nom.split("\\.")[1]; if (extension.equalsIgnoreCase(noeud.toString())) noeud.add(new DefaultMutableTreeNode(nom, false)); } } for (DefaultMutableTreeNode noeud = images.getFirstLeaf(); noeud !=null; noeud = noeud.getNextLeaf()) liste.remove(noeud.toString()); for (String nom : liste) autre.add(new DefaultMutableTreeNode(nom, false)); } private class Vue extends JComponent { private BufferedImage photo; private double ratio; @Override protected void paintComponent(Graphics g) { if (photo!=null) g.drawImage(photo, 0, 0, getWidth(), (int)(getWidth()/ratio), null); } public void setPhoto(File fichier) { try { photo = ImageIO.read(fichier); ratio = (double)photo.getWidth() / photo.getHeight(); repaint(); } catch (IOException ex) { setTitle("Impossible de lire le fichier");} } } public void valueChanged(TreeSelectionEvent e) { if (arbre.getSelectionPath()!=null) { String nom = arbre.getSelectionPath().getLastPathComponent ().toString(); vue.setPhoto(new File("C:/Photos/"+nom)); } } }

Modifier des arbres et leur structure


Dans ce chapitre, nous allons apprendre modifier un arbre en "temps rel". Il est par exemple possible d'ajouter un nouveau noeud par rapport un autre noeud de rfrence. Ce nouveau noeud peut tre considr comme un fils du noeud de rfrence ou comme un frre (sibling). Le noeud de rfrence peut aussi tre supprim tout moment. Pour implmenter ce comportement, vous devrez identifier le noeud slectionn. La classe JTree possde une technique tonnante pour identifier les noeuds d'un arbre. Elle ne gre pas les noeuds de l'arbre, mais les chemins des objets, appels chemins de l'arbre. Un chemin d'arbre commence la racine et correspond une squence de noeuds enfant. Vous vous demandez peut-tre pourquoi la classe JTree a besoin du chemin complet. Ne peut-elle pas se contenter de rcuprer un TreeNode et d'appeler en boucle sa mthode getParent() ? En fait, la classe JTree ne connat pas du tout l'interface TreeNode. Cette interface n'est en effet jamais utilise par l'interface TreeModel. Elle ne sert qu' l'implmentation de DefaultTreeModel. Vous pouvez possder d'autres modles d'arbres dans lesquels les noeuds n'implmentent pas du tout l'interface TreeNode. Si vous avez recours un modle d'arbre qui gre d'autres types d'objets, ces derniers peuvent ne pas avoir de mthodes getParent() et getChild(). Ils doivent cependant possder des connexions entre eux. Cette tche revient au modle d'arbre. La classe JTree elle-mme n'a aucune ide de la nature de leurs connexions. Pour cette raison, la classe JTree doit toujours travailler avec des chemins complets.

Retrouver la slection
La classe TreePath gre une squence de rfrences d'Object (et pas de TreeNode). Un certain nombre de mthode de JTree renvoient des objets TreePath. Lorsque vous possdez un chemin d'arbre, il vous suffit en gnral de connatre le noeud final, que vous pouvez rcuprer grce la mthode getLastPathComponent(). Par exemple, pour trouver quel noeud est couramment slectionn dans un arbre, vous pouvez utiliser la mthode getSelectionPath() de la classe JTree. Vous obtiendrez en retour un objet TreePath, d'o vous dduirez le noeud slectionn :
TreePath chemin = arbre.getSelectionPath(); DefaultMutableTreeNode noeud = (DefaultMutableTreeNode ) chemin.getLastPathComponent ();

En fait, comme cette requte est trs frquente, il existe une mthode pratique qui vous fournit immdiatement le noeud slectionn :
DefaultMutableTreeNode noeud = (DefaultMutableTreeNode) arbre.getLastSelectedPathComponent();

Cette mthode n'est pas appele getSelectedNode() parce que l'arbre ne sait pas qu'il renferme des noeuds. Seul le modle d'arbre gre les chemins des objets.

Les chemins d'arbre sont l'une des deux techniques utilises par la classe JTree pour dcrire les noeuds. Il existe d'autres mthodes JTree qui acceptent ou renvoient un indice entier, une position de ligne. Une position de ligne est simplement un numro de ligne (commenant par 0) correspondant au noeud spcifi dans l'arbre affich. Seuls les noeuds visibles posdent un numro de ligne, et le numro de ligne d'un noeud change si les noeuds qui le prcdent sont cachs, affichs ou modifis. C'est pourquoi, il vaut mieux viter de travailler avec des positions de ligne. Toutes les mthodes de JTree qui se servent de lignes possdent un quivalent utilisant des chemins d'arbre.

Modifications sur un noeud


Une fois que vous avez trouv le noeud slectionn, vous pouvez le modifier. Cependant, ne vous contentez pas d'ajouter des enfants un noeud :
noeud.add(nouveauNoeud ); // non

Si vous modifiez la structure des noeuds, vous modifiez le modle, mais l'affichage associ n'est pas mis jour. Vous pouvez envoyer une notification par vousmme, mais si vous utilisez la mthode insertNodeInto() de la classe DefaultTreeModel, vous refaites le travail de la classe du modle. Par exemple, l'appel suivant ajoute un noeud et le dclare comme tant le dernier noeud du noeud slectionn, et met jour l'affichage de l'arbre :
modle.inserNodeInto(nouveauNoeud, noeud, noeud.getChildCount());

L'appel similaire removeNodeFromParent() supprime un noeud et met jour l'affichage :


modle.removeNodeFromParent(noeud);

Si vous conservez la structure des noeuds, mais que vous modiffiez un objet utilisateur, vous devrez appeler la mthode nodeChanged() :
modle.nodeChanged(noeudChang);

La classe DefaultTreeModel possde une mthode reload() qui recharge le modle entier. Cependant, vitez d'appeler cette mthode uniquement pour mettre jour votre arbre lorsque vous avez apport des modifications. Lorsqu'un arbre est gnr nouveau, tous les noeuds situs aprs les enfants de la racine sont cachs. Cela peut tre extrmement dconcertant pour vos utilisateurs, s'ils doivent ouvrir nouveau leur arbre aprs chaque modification. La classe DefaultTreeModel possde galement une mthode reload() qui permet de ne recharger que les descendants d'un noeud particulier spcifi en argument de la mthode. Pour apporter des modifications sur les noeuds d'un arbre, vous remarquez que vous tes oblig de passer systmatiquement par le modle de l'arbre. Soit, vous construisez ce modle ds le dpart, au moyen de la classe DefaultTreeModel que vous passez ensuite en argument du constructeur de l'arbre JTree. Ou bien, vous le rcuprez partir de l'arbre au moyen de la mthode getModel() de la calsse JTree.

Gestion de l'affichage et construction de chemins d'arbre


Lorsque l'affichage est mis jour cause d'une modification de la structure des noeuds, les enfants ajouts ne sont pas automatiquement affichs. En particulier, si l'un des utilisateurs ajoutait un nouvel enfant un noeud dont les enfants sont actuellement cachs, le nouvel enfant le serait aussi. Cela ne fournit l'utilisateur aucune information sur le fonctionnement de la commande qu'il vient d'effectuer. Dans ce cas, il convient d'ouvrir tous les noeuds parent pour que le noeud qui vient d'tre ajout soit visible. Vous pouvez vous servir de la mthode makeVisible() de la classe JTree dans ce but. La mthode makeVisible() attend un chemin d'arbre pointant sur le noeud qu'elle doit rendre visible. Par consquent, vous serez amen construire un chemin d'arbre partir de la racine et allant jusqu'au nouveau noeud. Pour obtenir un chemin d'arbre, il faut commencer par appeler la mthode getPathToRoot() de la classe DefaultTreeModel. Elle renvoie un tableau TreeNode[] contenant tous les noeuds situs entre un noeud et la racine. Vous pouvez alors passer ce tableau un constructeur TreePath :
TreeNode[] noeuds = modle.getPathToRoot(nouveauNoeud); TreePath chemin = new TreePath(noeuds); arbre.makeVisible(chemin);

Il est assez trange que la classe DefaultTreeModel fasse semblant d'ignorer la classe TreePath, mme si son travail est de communiquer avec un JTree. La classe JTree se sert beaucoup de chemins d'arbre, alors qu'elle n'utilise jamais de tableaux d'objets de noeuds. Mais supposons maintenant que votre arbre fasse partie d'un panneau d'affichage droulant. Aprs l'expansion des noeuds de l'arbre, le nouveau noeud risque une nouvelle fois de ne pas tre visible parce qu'il peut se trouver en dehors de la zone visible du panneau. Pour rsoudre ce problme, appelez la mthode scrollPathToVisible() au lieu d'appeler la mthode makeVisible(). Cet appel ouvre tous les noeuds du chemin et demande au panneau droulant de se positionner sur le noeud situ la fin du chemin :
arbre.scrollPathToVisible(chemin);

Edition d'un noeud


Par dfaut, les noeuds d'un arbre peuvent tre modifis. Cependant si vous validez la mthode setEditable(), l'utilisateur peut modifier un noeud en double-cliquant simplement dessus, en modifiant la chane, puis en appuyant sur la touche "Entre" :
arbre.setEditable(true);

Le systme invoque alors l'diteur de cellule par dfaut, qui est implment par la classe DefaultCellEditor. Il est possible d'installer d'autres diteurs de cellules, mais je prfre reporter notre tude sur les diteurs de cellules la section concernant les tableaux, avec lesquels les diteurs de cellules sont plus couramment utiliss.

Mise en oeuvre sur l'application prcdente


A titre d'exemple, je vous propose de reprendre l'application prcdente et de faire en sorte de pouvoir rajouter ou supprimer des noeuds dans l'arborescence des fichiers. Au dpart, seul le noeud "JPG" existe dans le rpertoire "Images". Il est possible d'intgrer un nouveau noeud seulement si c'est un fils du noeud "Images". Il existe deux possibilits pour cela, soit partir du noeud "Image" lui-mme, soit partir d'un fils dj cr, comme le noeud "JPG". Dans ce cas l, nous rajoutons un noeud frre, comme cela est visualis dans la capture ci-dessous. Les fichiers se dplacent alors en consquence suivant les extensions proposes. A tout moment, il est galement possible de supprimer un noeud particulier de l'arborescence.

codage correspondant
package arbres; import import import import import import import import import import javax.swing.*; java.awt.*; java.awt.event.*; java.awt.image.BufferedImage; java.io.*; java.util.*; java.util.ArrayList; javax.imageio.ImageIO; javax.swing.event.*; javax.swing.tree.*;

public class Arbres extends JFrame implements TreeSelectionListener { private JTree arbre; private DefaultTreeModel modle; private DefaultMutableTreeNode racine; private DefaultMutableTreeNode images; private DefaultMutableTreeNode autre; private Vue vue = new Vue(); private String rpertoire = "C:/Photos/"; private JToolBar barre = new JToolBar(); private JTextField saisie = new JTextField("Nouveau rpertoire"); public Arbres() { super("Images"); construireArbre(); barre.add(new AbstractAction("Ajouter Frre") { public void actionPerformed(ActionEvent e) { DefaultMutableTreeNode slection = (DefaultMutableTreeNode) arbre.getLastSelectedPathComponent(); if (images.isNodeChild(slection)) { System.out.println("Noeud enfant"); DefaultMutableTreeNode noeud = new DefaultMutableTreeNode (saisie.getText(), true); modle.insertNodeInto(noeud, images, 0); DefaultMutableTreeNode recherche = autre.getFirstLeaf(); while (recherche!=null) { DefaultMutableTreeNode suivant = recherche.getNextLeaf(); String extension = recherche.toString().split("\\.")[1]; if (extension.equalsIgnoreCase(noeud.toString())) modle.insertNodeInto(recherche, noeud, 0); recherche = suivant; } modle.reload(autre); } } }); barre.add(new AbstractAction("Ajouter Fils") { public void actionPerformed(ActionEvent e) { DefaultMutableTreeNode slection = (DefaultMutableTreeNode) arbre.getLastSelectedPathComponent(); if (slection.equals(images)) { DefaultMutableTreeNode noeud = new DefaultMutableTreeNode (saisie.getText(), true); modle.insertNodeInto(noeud, images, 0); DefaultMutableTreeNode recherche = autre.getFirstLeaf(); while (recherche!=null) { DefaultMutableTreeNode suivant = recherche.getNextLeaf(); String extension = recherche.toString().split("\\.")[1]; if (extension.equalsIgnoreCase(noeud.toString())) modle.insertNodeInto(recherche, noeud, 0); recherche = suivant; } modle.reload(autre); } } }); barre.add(new AbstractAction("Supprimer") { public void actionPerformed(ActionEvent e) { DefaultMutableTreeNode slection = (DefaultMutableTreeNode) arbre.getLastSelectedPathComponent(); DefaultMutableTreeNode noeud = slection.getFirstLeaf(); while (noeud!=null) { DefaultMutableTreeNode suivant = noeud.getNextLeaf(); modle.insertNodeInto(noeud, autre, 0); noeud = suivant; } modle.removeNodeFromParent(slection); } }); barre.add(saisie); add(barre, BorderLayout.NORTH); add(new JScrollPane(arbre), BorderLayout.WEST); add(vue); setSize(540, 330); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true);

} public static void main(String[] args) { new Arbres(); } private void construireArbre() { racine = new DefaultMutableTreeNode("Fichiers", true); images = new DefaultMutableTreeNode("Images", true); DefaultMutableTreeNode jpeg = new DefaultMutableTreeNode("JPG", true); autre = new DefaultMutableTreeNode ("Autre", true); racine.add(images); racine.add(autre); images.add(jpeg); modle = new DefaultTreeModel(racine, true); arbre = new JTree(modle); arbre.setPreferredSize(new Dimension(180, 1000)); arbre.addTreeSelectionListener (this); arbre.setEditable(true); ajouterFichiers(); } private void ajouterFichiers() { File fichiers = new File(rpertoire); ArrayList<String> liste = new ArrayList<String>(); for (String nom : fichiers.list()) liste.add(nom); Enumeration recherche = images.children(); while (recherche.hasMoreElements()) { DefaultMutableTreeNode noeud = (DefaultMutableTreeNode ) recherche.nextElement(); for (String nom : liste) { String extension = nom.split("\\.")[1]; if (extension.equalsIgnoreCase(noeud.toString())) noeud.add(new DefaultMutableTreeNode(nom, false)); } } for (DefaultMutableTreeNode noeud = images.getFirstLeaf(); noeud !=null; noeud = noeud.getNextLeaf()) liste.remove(noeud.toString()); for (String nom : liste) autre.add(new DefaultMutableTreeNode(nom, false)); } private class Vue extends JComponent { private BufferedImage photo; private double ratio; @Override protected void paintComponent(Graphics g) { if (photo!=null) g.drawImage(photo, 0, 0, getWidth(), (int)(getWidth()/ratio), null); } public void setPhoto(File fichier) { try { photo = ImageIO.read(fichier); ratio = (double)photo.getWidth() / photo.getHeight(); repaint(); } catch (IOException ex) { setTitle("Impossible de lire le fichier");} } } public void valueChanged(TreeSelectionEvent e) { if (arbre.getSelectionPath()!=null) { String nom = arbre.getSelectionPath().getLastPathComponent ().toString(); vue.setPhoto(new File("C:/Photos/"+nom)); } } }

Affichage des noeuds personnaliss


Dans vos applications, vous serez souvent amen modifier la manire dont un composant d'un arbre reprsente les noeuds. La modification la plus courante est naturellement la possibilit de choisir plusieurs icnes pour les noeuds et pour les feuilles. Les autres changements peuvent tre en rapport avec la police utilise ou l'affichage d'images sur chaque noeuds. Toutes ces modifications sont possibles si vous prenez la peine d'installer un nouvel afficheur de cellules d'arbre dans votre arbre. Par dfaut, la classe JTree se sert d'objets de type DefaultTreeCellRenderer pour afficher chaque noeud. La classe DefaultTreeCellRenderer tend la classe JLabel. Une tiquette contient l'icne d'un noeud et le nom de ce noeud. L'afficheur de cellules n'affiche pas les poignes permettant de savoir si un noeud est ouvert ou ferm. Ces poignes font partie de l'aspect gnral de l'arbre, et il est recommand de ne pas les changer.

Personnalisation
Vous pouvez personnaliser l'affichage de trois manires diffrentes : 1. Vous pouvez modifier les icnes, la police et la couleur de fond utilises par un objet DefaultTreeCellRenderer dj prsent dans l'arbre. Dans ce cas l, ces paramtres sont utiliss pour tous les noeuds d'un arbre. 2. Vous pouvez installer un nouvel afficheur qui tend la classe DefaultTreeCellRenderer et modifier les icnes, les polices et la couleur de fond de chaque noeud. 3. Vous pouvez installer un afficheur qui implmente l'interface TreeCellRenderer, pour afficher une nouvelle image pour chaque noeud.

Personnaliser les icnes des noeuds (mme apparence pour tous les noeuds)
Si vous dsirez modifier les icnes des rpertoires (ouvert ou ferm) ainsi que celle des feuilles tout en gardant la mme apparence sur l'ensemble de l'arbre, il suffit : 1. soit de rcuprer l'objet DefaultTreeCellRenderer de l'arbre partir de la mthode getCellRenderer() de la classe JTree, 2. soit de crer un nouvel objet DefaultTreeCellRenderer et de le proposer ensuite l'arbre au travers de la mthode setCellRenderer(). Quelque soit la solution retenue, la classe DefaultTreeCellRenderer possde, en plus de celles rcupres par hritage issue de la classe JLabel, des mthodes spcifiques la gestion d'affichage des noeuds :

Mthodes spcifiques la classe DefaultTreeCellRenderer


Color getBackgroundNonSelectionColor () void setBackgroundNonSelectionColor(Color nouvelleCouleur)

Retourne ou spcifie la couleur de fond du noeud lorsque ce dernier n'est pas slectionn. Color getBackgroundSelectionColor() void setBackgroundSelectionColor(Color nouvelleCouleur) Retourne ou spcifie la couleur de fond du noeud lorsque ce dernier est slectionn. Color getBorderSelectionColor() void setBorderSelectionColor(Color nouvelleCouleur) Retourne ou spcifie la couleur de bordure du noeud lorsque ce dernier est slectionn. Icon getClosedIcon() void setClosedIcon(Icon nouvelleIcne) Retourne ou spcifie l'icne qui reprsente un noeud ferm (rpertoire ferm). Ce noeud n'est pas une feuille et possde des enfants. Icon getDefaultClosedIcon () Retourne l'icne par dfaut reprsentant un noeud ferm. Ce noeud n'est pas une feuille. Icon getDefaultLeafIcon() Retourne l'icne par dfaut reprsentant une feuille. Icon getDefaultOpenIcon() Retourne l'icne par dfaut reprsenant un noeud ouvert (rpertoire ouvert). Ce noeud n'est pas une feuille et possde des enfants. Icon getLeafIcon() void setLeafIcon(Icon nouvelleIcne) Retourne ou spcifie l'icne qui reprsente une feuille. Icon getOpenIcon() void setOpenIcon(Icon nouvelleIcne) Retourne ou spcifie l'icne qui reprsente un noeud ouvert (rpertoire ouvert). Ce noeud n'est pas une feuille et possde des enfants. Color getTextNonSelectionColor() void setTextNonSelectionColor(Color nouvelleCouleur) Retourne ou spcifie la couleur du libell du noeud lorsque ce dernier n'est pas slectionn. Color getTextSelectionColor() void setTextSelectionColor(Color nouvelleCouleur) Retourne ou spcifie la couleur du libell du noeud lorsque ce dernier est slectionn. Component getTreeCellRendererComponent (JTree arbre, Object valeur, boolean slection, boolean ouvert, boolean feuille, int ligne, boolean focus) Mthode redfinir lorsque nous souhaitons proposer un rendu personnalis pour chaque noeud. Cette mthode est dclare dans l'interface TreeCellRenderer.

Exemple de mise en oeuvre en rcuprant l'objet DefaultTreeCellRenderer de l'arbre JTree

private void construireArbre() { racine = new DefaultMutableTreeNode("Fichiers", true); images = new DefaultMutableTreeNode("Images", true); DefaultMutableTreeNode jpeg = new DefaultMutableTreeNode("JPG", true); DefaultMutableTreeNode gif = new DefaultMutableTreeNode("GIF", true); DefaultMutableTreeNode png = new DefaultMutableTreeNode("PNG", true); autre = new DefaultMutableTreeNode ("Autre", true); racine.add(images); racine.add(autre); images.add(jpeg); images.add(gif); images.add(png); arbre = new JTree(racine, true); arbre.setPreferredSize(new Dimension(200, 1000)); arbre.addTreeSelectionListener (this); arbre.setShowsRootHandles(true); arbre.setRootVisible(false); DefaultTreeCellRenderer rendu = (DefaultTreeCellRenderer) arbre.getCellRenderer(); rendu.setLeafIcon(new ImageIcon("feuille.gif")); rendu.setClosedIcon(new ImageIcon("rpertoireFerm.gif")); rendu.setOpenIcon(new ImageIcon("rpertoireOuvert.gif")); ajouterFichiers(); }

Autre exemple en crant un nouvel objet DefaultTreeCellRenderer qui est ensuite propos l'arbre JTree

private void construireArbre() { racine = new DefaultMutableTreeNode("Fichiers", true); images = new DefaultMutableTreeNode("Images", true); DefaultMutableTreeNode jpeg = new DefaultMutableTreeNode("JPG", true); DefaultMutableTreeNode gif = new DefaultMutableTreeNode("GIF", true); DefaultMutableTreeNode png = new DefaultMutableTreeNode("PNG", true); autre = new DefaultMutableTreeNode ("Autre", true); racine.add(images); racine.add(autre); images.add(jpeg); images.add(gif); images.add(png); arbre = new JTree(racine, true); arbre.setPreferredSize(new Dimension(200, 1000)); arbre.addTreeSelectionListener (this); arbre.setShowsRootHandles(true); arbre.setRootVisible(false); DefaultTreeCellRenderer rendu = new DefaultTreeCellRenderer(); rendu.setLeafIcon(new ImageIcon("feuille.gif")); rendu.setClosedIcon(new ImageIcon("rpertoireFerm.gif")); rendu.setOpenIcon(new ImageIcon("rpertoireOuvert.gif")); arbre.setCellRenderer(rendu); ajouterFichiers(); }

Avec la premire mthode, la premire fois que l'arbre s'affiche, la dimension des icnes est celle prvue par le systme par dfaut. Nous avons donc un petit ala qui est vite rsorb ds que nous ouvrons un rpertoire quelconque. Toutefois, pour viter cet ala, il est prfrable d'utiliser la deuxime mthode. Il n'est gnralement pas souhaitable de modifier la police ou la couleur de fond d'un arbre entier, parce que cette tche revient plutt au look-and-feel choisi.

Personnaliser l'apparence de chaque noeud


Pour modifier l'apparence de certains noeuds, vous devez installer un afficheur de cellules d'arbre. Cet afficheur de cellule doit imprativement implmenter l'interface TreeCellRenderer qui possde une seule mthode getTreeCellRendererComponent(). Nous avons dj rencontr cette mthode dans la classe DefaultTreeCellRenderer, et c'est normal puisque cette dernire implmente justement cette interface. Pour personnaliser l'apparence de chaque noeud en particulier, il suffit finalement de crer un nouvel afficheur de cellule qui hrite de la classe DefaultTreeCellRenderer et nous devons redfinir la mthode getTreeCellRendererComponent() pour que cette dernire soit adapte au rendu personnalis.

Interface TreeCellRenderer
Component getTreeCellRendererComponent (JTree arbre, Object valeur, boolean slection, boolean ouvert, boolean feuille, int ligne, boolean focus) Mthode redfinir lorsque nous souhaitons proposer un rendu personnalis pour chaque noeud.

Attention : le paramtre valeur de la mthode getTreeCellRendererComponent() est l'objet noeud, et non l'objet de l'utilisateur ! Rappelez-vous que l'objet de l'utilisateur est une caractristique de DefaultMutableTreeNode, et qu'un JTree peut contenir des noeuds de n'importe quel type. Si votre arbre se sert de noeuds DefaultMutableTreeNode, vous devez traiter l'objet de l'utilisateur dans une seconde tape. Pour rcuprer l'objet utilisateur, passez par la mthode getUserObject() de la classe DefaultMutableTreeNode. Attention : DefaultTreeCellRenderer se sert d'un seul objet d'tiquette pour tous les noeuds, et il ne modifie le texte de l'tiquette que d'un seul noeud. Si, par exemple, vous souhaitez modifier la police d'un noeud particulier, vous devez lui redonner sa valeur par dfaut lorsque la mthode est appele nouveau. Autrement, tous les noeuds suivant seront affichs avec la nouvelle police.

Afficher une petite vignette sur la feuille correspondant la photo visualiser


A titre d'exemple, je vous propose de reprendre l'application prcdente et de personnaliser l'icne de chaque feuille. Il est effectivement plus judicieux de proposer une petite vignette correspondant la photo visualiser.

package arbres; import import import import import import import import javax.swing.*; java.awt.*; java.awt.image.BufferedImage; java.io.*; java.util.*; javax.imageio.ImageIO; javax.swing.event.*; javax.swing.tree.*;

public class Arbres extends JFrame implements TreeSelectionListener { private JTree arbre; private DefaultMutableTreeNode racine; private DefaultMutableTreeNode images; private DefaultMutableTreeNode autre; private Vue vue = new Vue(); private String rpertoire = "C:/Photos/"; public Arbres() { super("Images"); construireArbre(); add(new JScrollPane(arbre), BorderLayout.WEST); add(vue); setSize(540, 300); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Arbres(); } private void construireArbre() { racine = new DefaultMutableTreeNode("Fichiers", true); images = new DefaultMutableTreeNode("Images", true); DefaultMutableTreeNode jpeg = new DefaultMutableTreeNode("JPG", true); DefaultMutableTreeNode gif = new DefaultMutableTreeNode("GIF", true); DefaultMutableTreeNode png = new DefaultMutableTreeNode("PNG", true); autre = new DefaultMutableTreeNode ("Autre", true); racine.add(images); racine.add(autre); images.add(jpeg); images.add(gif); images.add(png); arbre = new JTree(racine, true); arbre.setPreferredSize(new Dimension(200, 1000)); arbre.addTreeSelectionListener (this); arbre.setShowsRootHandles(true); arbre.setRootVisible(false); arbre.setCellRenderer(new RenduArbre()); ajouterFichiers(); } private void ajouterFichiers() { File fichiers = new File(rpertoire); ArrayList<Vignette> vignettes = new ArrayList<Vignette>(); for (File fichier : fichiers.listFiles()) vignettes.add(new Vignette(fichier)); Enumeration recherche = images.children(); while (recherche.hasMoreElements()) { DefaultMutableTreeNode noeud = (DefaultMutableTreeNode ) recherche.nextElement(); for (Vignette vignette : vignettes) if (vignette.mmeExtension(noeud)) noeud.add(new DefaultMutableTreeNode(vignette, false)); } for (DefaultMutableTreeNode noeud = images.getFirstLeaf(); noeud !=null; noeud = noeud.getNextLeaf()) vignettes.remove(noeud.getUserObject()); for (Vignette vignette : vignettes) autre.add(new DefaultMutableTreeNode(vignette, false)); } public void valueChanged(TreeSelectionEvent e) { if (arbre.getSelectionPath()!=null) { DefaultMutableTreeNode noeud = (DefaultMutableTreeNode ) arbre.getLastSelectedPathComponent(); Vignette vignette = (Vignette) noeud.getUserObject(); vue.setPhoto(vignette.getFichier()); } } private class Vue extends JComponent { private Image photo; private double ratio; @Override protected void paintComponent(Graphics g) { if (photo!=null) g.drawImage(photo, 0, 0, getWidth(), (int)(getWidth()/ratio), null);

} public void setPhoto(File fichier) { photo = new ImageIcon(fichier.getPath()).getImage(); ratio = (double)photo.getWidth(null) / photo.getHeight(null); repaint(); } } private class RenduArbre extends DefaultTreeCellRenderer { public RenduArbre() { setClosedIcon(new ImageIcon("rpertoireFerm.gif")); setOpenIcon(new ImageIcon("rpertoireOuvert.gif")); setBorder(BorderFactory.createEmptyBorder(1, 0, 1, 0)); } @Override public Component getTreeCellRendererComponent (JTree arbre, Object n, boolean slection, boolean ouvert, boolean feuille, int ligne, boolean focus) { super.getTreeCellRendererComponent (arbre, n, slection, ouvert, feuille, ligne, focus); DefaultMutableTreeNode noeud = (DefaultMutableTreeNode ) n; if (feuille) { Vignette vignette = (Vignette) noeud.getUserObject(); setIcon(vignette.getIcon()); } return this; } } private class Vignette extends JLabel { private final int largeur = 50; private File fichier; private String libell; private String extension; public Vignette(File fichier) { this.fichier = fichier; String[] dcoupage = fichier.getName().split("\\."); libell = dcoupage[0]; extension = dcoupage[1]; Image image = new ImageIcon(fichier.getPath()).getImage().getScaledInstance(largeur, -1, Image.SCALE_DEFAULT); setIcon(new ImageIcon(image)); } boolean mmeExtension(DefaultMutableTreeNode noeud) { return extension.equalsIgnoreCase(noeud.toString()); } public File getFichier() { return fichier; } @Override public String toString() { return libell; } } }

Ecouter les vnements des arbres


Le plus frquemment, et c'est justement le cas dans notre application, un composant arbre est coupl un autre composant. Lorsque l'utilisateur slectionne des noeuds de l'arbre, certaines informations s'affichent dans une autre fentre. Dans notre application, lorsque l'utilisateur slectionne une feuille, la photo correspondante s'affiche sur la partie centrale de la fentre.

Une arborescence dclenche plusieurs sortent d'vnements qui sont grs par des interfaces couteurs d'vnement distincts. Nous pouvons dterminer : 1. Le moment o les noeuds ont t ouverts et referms, implment par l'interface TreeExpansionListener. 2. Celui o ils sont sur le point d'tre ouverts ou referm (suite un clic de l'utilisateur), implment par l'interface TreeWillExpandListener. 3. Et celui o les slections ont cours, implment par l'interface TreeSelectionListener.

couteur TreeExpansionListener
void treeCollapsed(TreeExpansionEvent vnement) Appel lorsque un noeud se referme. void treeExpanded(TreeExpansionEvent vnement)

Appel lorsque noeud s'ouvre.

couteur TreeWillExpandListener
void treeWillCollapse(TreeExpansionEvent vnement) Appel lorsque un noeud est sur le point de se refermer. void treeWillExpande(TreeExpansionEvent vnement) Appel lorsque noeud est sur le point de s'ouvrir.

couteur TreeSelectionListener
void valueChanged(TreeSelectionEvent vnement) Appel lorsque l'utilisateur slection ou dslectionne des noeuds de l'arbre.

Ragir la slection d'un noeud


Bien entendu, comme pour notre application, le cas le plus frquent est la prise en compte d'une slection, qui permet de ragir en consquence en lanant la visualisation de l'image souhaite. Pour obtenir ce comportement, il suffit donc d'installer un couteur de slection de l'arbre. Cet couteur doit implmenter l'interface TreeSelectionListener, une interface possdant une seule mthode :
void valueChanged(TreeSelectionEvent vnement); Cette mthode est appele lorsque l'utilisateur slectionne ou dslectionne des noeuds de l'arbre. L'couteur est ajout l'arbre de manire classique : arbre.addTreeSelectionListener (couteur);

Choisir le mode de slection


Vous pouvez autoriser l'utilisateur slectionner un seul noeud, une zone continue de noeuds ou un ensemble arbitraire et potentiellement discontinu de noeuds. La classe JTree se sert de l'interface TreeSelectionModel pour grer la slection des noeuds. Vous devrez retrouver le modle, au moyen de la mthode getSelectionModel() de la classe JTree, pour dfinir ensuite l'tat de slection, l'aide de la mthode setSelectionMode() de l'interface TreeSelectionModel, avec l'une des valeurs suivantes : 1. TreeSelectionModel.SINGLE_TREE_SELECTION, 2. TreeSelectionModel.CONTIGUOUS_TREE_SELECTION, 3. TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION.

Le mode de slection discontinu est le mode par dfaut. Pour permettre uniquement la slection d'un seul noeud :
int mode = TreeSelectionModel.SINGLE_TREE_SELECTION; arbre.getSelectionModel ().setSelectionMode(couteur);

Lorsque vous aurez dfini un mode de slection, vous n'aurez plus vous procuper du modle de slection de l'arbre.

Rcupration de la slection courante


Pour rcuprer la slection courante, il faut interroger l'arbre avec la mthode getSelectionPaths() :
TreePath[] slections = arbre.getSelectionPaths ();

Si vous limitez l'utilisateur une seule slection, vous pouvez avoir recours la mthode pratique getSelectionPath(), qui renvoie le premier chemin slectionn, ou null si aucun chemin n'a t slectionn. Vous obtiendrez en retour un objet TreePath, d'o vous en dduirez le noeud slectionn. Effectivement, lorsque vous possdez un chemin d'arbre, il vous suffit en gnral de connatre le noeud final, que vous pouvez rcuprer grce la mthode getLastPathComponent()
TreePath chemin = arbre.getSelectionPath(); DefaultMutableTreeNode noeud = (DefaultMutableTreeNode) chemin.getLastPathComponent ();

En fait, comme cette requte est trs frquente, il existe une mthode pratique qui vous fournit immdiatement le noeud slectionn :
DefaultMutableTreeNode noeud = (DefaultMutableTreeNode) arbre.getLastSelectedPathComponent();

Cette mthode n'est pas appele getSelectedNode() parce que l'arbre ne sait pas qu'il renferme des noeuds. Seul le modle d'arbre gre les chemins des objets.

Attention : la classe TreeSelectionEvent possde une mthode getPaths() qui renvoie un tableau d'objets TreePath, mais ce tableau dcrit les modifications de la slection, et non la slection courante.

Mise en oeuvre de la gestion de slection unique d'une feuille de l'arbre


... public class Arbres extends JFrame implements TreeSelectionListener { ... private void construireArbre() { ... arbre = new JTree(racine, true); arbre.setPreferredSize(new Dimension(200, 1000)); arbre.addTreeSelectionListener (this); arbre.setShowsRootHandles(true); arbre.setRootVisible(false); arbre.setCellRenderer(new RenduArbre()); arbre.getSelectionModel ().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); ajouterFichiers(); } ... public void valueChanged(TreeSelectionEvent e) { if (arbre.getSelectionPath()!=null) { DefaultMutableTreeNode noeud = (DefaultMutableTreeNode ) arbre.getLastSelectedPathComponent(); Vignette vignette = (Vignette) noeud.getUserObject(); vue.setPhoto(vignette.getFichier()); } }

... }

Les tableaux
Les tableaux prsentent les informations sous forme de lignes et de colonnes ordonnes. Ils conviennent particulirement la reprsentation de schmas financiers ou de donnes de base de donnes relationnelle. Comme les arborescences, les tableaux de Swing sont extrmement puissants et personnalisables. Si on se cantonne leurs options par dfaut, ils s'avrent eu outre trs simples utiliser. Les tableaux sont reprsents par le composant JTable qui affiche une grille bidirectionnelle d'objets. Naturellement, les tableaux sont trs courants dans les interfaces utilisateurs. De par leur nature, les tableaux sont compliqus, mais, peut-tre plus que pour d'autres classes de Swing, le composant JTable prend en charge la plus grosse partie de cette complexit. Vous pourrez ainsi produire des tableaux parfaitement fonstionnels avec un comportement riche, en crivant uniquement quelques lignes de code. Mais vous pouvez bien sr crire un code plus complet et personnaliser l'affichage et le comportement de vos applications.

Gestion de la table par modles


Le composant JTable comporte un certain nombre de modles internes qui permettent de respecter l'architecture MVC : 1. Comme pour les composants d'un arbre, un JTable n'enregistrement pas ses propres donnes, mais il les obtient partir d'un modle de tableau issu de l'interface TableModel. Tous les lements important de cette classe JTable sont construits dans le paquetage javax.swing.table comme l'est cette interface TableModel. Vous pouvez construire un tableau partir d'un tableau bidimensionnel d'objet et emballer automatiquement ce tableau dans un modle par dfaut (classe DefaultTableModel) en faisant juste appel au constructeur de JTable. 2. De mme, il est possible de prvoir un modle spcifique au nom donn par chacune des colonnes du tableau, qui se prsente gnralement sous forme gris et en haut du tableau. Pour cela, vous devez implmenter l'interface TableColumnModel. Toutefois, l aussi, il existe une classe DefaultTableColumnModel qui implmente cette interface et qui est directement intgr, sauf avis contraire, dans le composant JTable. 3. Enfin, il existe un modle prvu pous les slections des lments, issu de l'interface ListSelectionModel, dont la classe JTable possde galement un modle par dfaut : DefaultListSelectionModel.

Phase de construction
Plusiseurs constructeurs sont amnags pour rsoudre les diffrentes possibilits de cration de tableaux : 1. JTable() : Construit un tableau vierge avec le modle de table par dfaut, le modle de colonne par dfaut et le modle de slection par dfaut. 2. JTable(int nombreLignes, int nombreColonnes) : Construit un tableau en spcifiant le nombre de lignes et de colonnes avec des cellules vierges avec les modles par dfaut. 3. JTable(Object[][] cellules, Object[] nomColonnes) : Construit un tableau avec la valeur de chacune des cellules d'une part, et le nom donn chacune des colonnes d'autre part, en prenant les modles par dfaut. 4. JTable(TableModel modleTable) : Construit un tableau en proposant un nouveau modle de table, mais en gardant le modle de colonne par dafut et le modle de slection par dfaut. 5. JTable(TableModel modleTable, TableColumnModel modleColonne) : Construit un tableau en proposant un nouveau modle de table et un nouveau modle de colonne tout en gardant le modle de slection par dfaut. 6. JTable(TableModel modleTable, TableColumnModel modleColonne, ListSelectionModel modleSlection) : Construit un tableau en spcifiant les trois modles. 7. JTable(Vector cellules, Vector nomsColonne) : Construit un tableau partir d'un verteur de vecteur pour le contenu des cellules et partir d'un vecteur pour spcifier le nom des colonnes.

Mise en oeuvre d'un tableau simple


La figure ci-dessous montre un tableau typique, qui dcrit les proprits des plantes du systme solaire. Une plante est considre comme gazeuse si elle est compose principalement d'hydrogne et d'hlium.

codage correspondant
package tables; import javax.swing.*; import java.awt.*; public class Tables extends JFrame { private Object[][] cellules = { {"Mercure", 2440.0, 0, false, Color.yellow}, {"Vnus", 6052.0, 0, false, Color.yellow}, {"Terre", 6378.0, 1, false, Color.blue}, {"Mars", 3397.0, 2, false, Color.red}, {"Jupiter", 71492.0, 16, true, Color.orange}, {"Saturne", 60268.0, 18, true, Color.orange}, {"Uranus", 25559.0, 17, true, Color.blue}, {"Neptune", 24766.0, 8, true, Color.blue}, {"Pluton", 1137.0, 1, false, Color.black} }; private String[] nomColonnes = {"Plante", "Rayon", "Satellites", "Gazeuse", "Couleur"}; public Tables() { super("Plantes"); add(new JScrollPane(new JTable(cellules, nomColonnes))); pack(); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Tables(); } }

1. Comme vous pouvez le voir dans le code, les donnes de ce tableau sont enregistres dans un tableau bidimensionnel de valeurs Object. 2. Nous profitons ici de l'autoboxing. Effectivement, les deuximes troisimes et quatrimes colonnes sont automatiquement converties en objet de type Double, Integer et Boolean. 3. Sinon, le tableau se contente d'invoquer la mthode toString() de chaque objet pour l'afficher. C'est ce qui se passe pour l'affichage de la classe Color. 4. Le nom des colonnes sont fournies dans des chanes spares : String[] nomColonnes = {"Plante", "Rayon", "Satellites", "Gazeuse", "Couleur"};

Comportement minimal d'un tableau


Le tableau rsultant possde dj un comportement tonnament riche. Pour n'avoir saisie que trs peu de code, nous obtenons normment de fonctionnalits intressantes prtes l'emploi.

1. En-ttes des colonnes : Le composant JTable place automatiquement des en-ttes de colonnes sous une forme diffrente des cellules. Il apparat clairement qu'ils ne font pas partie de la zone de donnes de la table.

2. Dpassement des cellules : Lorsque la donne d'une cellule est trop longue, elle est automatiquement tronque et prsente an pointill (...). C'est le cas des cellules de couleur de la colonne de droite :

3. Le nom des colonnes toujours visible : Recadrez le tableau verticalement jusqu' ce que l'ascenceur vertical apparaisse et dplacez cet ascenceur. Les noms des colonnes restent constamment visibles.

4. Slection de ligne : Nous pouvons cliquer sur une cellule quelconque pour slectionner la ligne entire. Cette fonction est contrlable : nous pouvons choisir des cellules individuelles, des lignes ou des colonnes entires ou une combinaison des deux. Pour configurer la fonction slection du composant JTable, nous devons faire appel aux mthodes setCellSelectionEnabled(), setColumnSelectionAllowed() et setRowSelectionAllowed().

5. Edition de cellule : En double-cliquant sur une cellule, nous ouvrons une vue de modification ; elle propose alors un petit curseur de texte. Nous pouvons saisir ainsi directement dans la cellule le nouvelle valeur souhaite.

6. Dimensionnement de colonne : Lorsque nous dplaons le curseur de la souris entre deux colonnes, nous obtenons un petit curseur en forme de flches opposes. Cliquez et glissez pour modifier la largeur de la colonne. Suivant la configuration de JTable, la largeur des autres colonnes est galement succeptible de changer. La fonction de redimensionnement est contrle par la mthode setAutoResizedMode().

7. Rordonnancement des colonnes : En cliquant et glissant sur un en-tte de colonne, nous pouvons dplacer la colonne entire un autre endroit de la table. Effectivement, cliquez sur l'un des noms de colonnes et dplacez-le droite ou gauche. La colonne entire se dtache. Vous pouvez donc l'amener un nouvel emplacement. Cela modifie uniquement l'affichage des colonnes. Le modle des donnes n'est pas affect.

8. Imprimer un tableau : Depuis la version 5 de la JDK, vous envoyer tout le contenu d'un tableau directement l'imprimante au moyen de la mthode print() de la classe JTable. Une bote de dialogue d'impression s'affiche afin que vous choisissiez la bonne imprimante.

Mthodes intressantes de la classe JTable


void addColumn(TableColumn colonne) void addColumnSelectionInterval (int de, int ) void addRowSelectionInterval(int de, int ) Ajout de lignes et de colonnes. void clearSelection() Dsactive la slection. boolean editCellAt(int ligne, int colonne) boolean editCellAt(int ligne, int colonne, EventObject vnement) Gestion de l'dition par programme. int getColumnCount() String getColumnName(int colonne) boolean getColumnSelectionAllowed () int getEditingColumn () int getSelectedColumn() int getSelectedColumnCount () int[] getSelectedColumns() void setColumnSelectionInterval (int de, int ) void setEditingColumn(int colonne) Gestion des colonnes. int getRowCount() int getRowHeight() int getRowHeight(int ligne) int getRowMargin() boolean getRowSelectionAllowed() int getEditingRow() int getSelectedRow() int getSelectedRowCount() int[] getSelectedRows() void setEditingRow(int ligne) void setRowSelectionAllowed (boolean validation) void setRowSelectionInterval(int de, int ) Gestion des lignes. TableModel getModel() TableColumnModel getColumnModel() ListSelectionModel getSelectionModel () void setModel(TableModel modle) void setColumnModel(TableColumnModel modle) void setSelectionModel(ListSelectionModel modle) Gestion des trois modles par dfaut de la table. Object getValueAt(int ligne, int colonne) void setValueAt(Object valeur, int ligne, int colonne) Gestion des valeurs de cellules. boolean isCellEditable(int ligne, int colonne) boolean isCellSelected(int ligne, int colonne) boolean isColumnSelected(int colonne) boolean isEditing() boolean isRowSelected(int ligne) Quelques tests. void moveColumn(int colonne, int colonneCible) Dplace la colonne vers la nouvelle position. boolean print() boolean print(JTable.PrintMode mode) boolean print(JTable.PrintMode mode, MessageFormat en-tte, MessageFormat pied) boolean print(JTable.PrintMode mode, MessageFormat en-tte, MessageFormat pied, boolean boteImpression, PrintRequestAttributeSet attr, boolean interactive) boolean print(JTable.PrintMode mode, MessageFormat en-tte, MessageFormat pied, boolean boteImpression, PrintRequestAttributeSet attr, boolean interactive, PrintService service) Imprimer. void removeColumn(TableColumn colonne) void removeColumnSelectionInterval (int de, int ) void removeEditor() void removeRowSelectionInterval(int de, int ) Suppressions. void selectAll() Slectionner toutes les lignes. void setAutoResizeMode(int mode) Choisi le mode d'affichage automatique. Il existe 5 modes de raffichage automatique : AUTO_RESIZE_OFF, AUTO_RESIZE_NEXT_COLUMN, AUTO_RESIZE_SUBSEQUENT_COLUMNS, AUTO_RESIZE_LAST_COLUMN, AUTO_RESIZE_ALL_COLUMNS.

void setCellEditor(TableCellEditor diteur) void setDefaultEditor(Class<?> classColonne, TableCellEditor diteur) void setDefaultRenderer(Class<?> classColonne, TableCellRenderer rendu) Proposer une gestion personnalise de la table, respectivement l'diteur et le rendu de la cellule. void setCellSelectionEnabled(boolean validation) void setColumnSelectionAllowed(boolean validation) void setColumnSelectionAllowed(boolean validation) void setShowGrid(boolean grille) void setShowHorizontalLines (boolean horizontal) void setShowVerticalLines (boolean vertical) Validations. void setGridColor(Color couleur) void setSelectionBackground (Color fond) void setSelectionForeground(Color texte) Gestion des couleurs. void setIntercellSpacing(Dimension intervalle) void setPreferredScrollableViewportSize (Dimension dimension) void setRowHeight(int hauteur) void setRowHeight(int ligne, int hauteur) void setRowMargin(int marge) Rglages des diffrentes dimensions. void sorterChanged(RowSorterEvent vnement) void tableChanged(TableModelEvent vnement) void valueChanged(ListSelectionEvent vnement) void editingCanceled(ChangeEvent vnement) void editingStopped(ChangeEvent vnement) Notifications.

Utiliser le modle de table par dfaut


Il est possible de travailler avec le modle par dfaut du tableau (DefaultTableModel) afin d'introduire le contenu de chacune des cellules ultrieurement (ainsi que le nom des colonnes), c'est--dire aprs la cration de la table. Pour utiliser toutes les comptences de ce modle, faites appel la mthode getModel() du composant JTable.

Mthodes spcifiques la classe DefaultTableModel


DefaultTableModel() DefaultTableModel(int nombreLignes, int nombreColonnes). DefaultTableModel(Object[][] donnes, Object[] nomColonnes) DefaultTableModel(Object[] nomColonnes, int nombreLignes) DefaultTableModel(Vector nomColonnes, int nombreLignes) DefaultTableModel(Vector donnes, Vector nomColonnes) Ensemble de constructeurs relativement similaires la classe JTable. void addColumn(Object nomColonnes) void addColumn(Object nomColonnes, Object[] valeursDeLaColonne) void addColumn(Object nomColonnes, Vector valeursDeLaColonne) Ajoute une nouvelle colonne au modle avec ventuellement des valeurs. void addRow(Object[] valeursDeLaNouvelleLigne ) void addRow(Vector valeursDeLaNouvelleLigne ) Ajoute une nouvelle ligne avec l'ensemble des valeurs proposes en argument. int getRowCount() int getColumnCount() Retourne le nombre de lignes et de colonnes. String getColumnName(int numroColonne) Retourne le nom de la colonne suivant la position spcifie en argument. Vector getDataVector() Retourne le vecteur de vecteur de l'ensemble des valeurs pour chacune des cellules. Object getValueAt(int ligne, int colonne) . Retourne la valeur de la cellule se situant aux coordonnes du tableau spcifies en argument de la mthode. void insertRow(int ligne, Object[] donnesDeLaLigne) void insertRow(int ligne, Vector donnesDeLaLigne) Insre une nouvelle ligne dans le modle avec l'ensemble des valeurs spcifies. boolean isCellEditable(int ligne, int colonne) Contrle si la cellule spcifie est ditable ou pas. void moveRow(int dbut, int fin, int nouvellePosition) Un ensemble de lignes peuvent tre dplaces dans le modle. void removeRow(int ligne) Supprime la ligne du modle void setColumnCount (int nombreColonnes) Prvoit un certaine nombre de colonnes au modle. void setColumnIdentifiers(Object[] nouveauxNomsDeColonne) void setColumnIdentifiers(Vector nouveauxNomsDeColonne) Spcifie le nom de chaque colonne.

void setDataVector(Object[][] donnes, Object[] nomColonnes) void setDataVector(Vector donnes, Vector nomColonnes) Spcifie la valeur de chacune des cellules ainsi que le nom de chaque colonne. void setRowCount(int nombreLignes) Prvoit un certain nombre de lignes. void setValueAt(Object uneValeur, int ligne, int colonne) Propose une nouvelle valeur une cellule.

A titre d'exemple, je vous propose de raliser un tableau dynamique qui recense l'ensemble des fichiers stocks dans le rpertoire "C:\Photos\". Dans ce tableau apparat repectivement, le nom du fichier, son extension, son nombre d'octets, savoir s'il s'agit d'une image avec les dimensions de cette image.

codage correspondant
package tables; import import import import java.awt.Image; java.io.File; javax.swing.*; javax.swing.table.*;

public class Tables extends JFrame { private String[] colonnes = {"Nom fichier", "Extensions", "Octets", "Image ?", "Largeur", "Hauteur"}; private JTable table = new JTable(); public Tables() { super("Liste des fichiers"); construireTableau(); add(new JScrollPane(table)); pack(); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } private void construireTableau() { File[] fichiers = new File("C:/Photos/").listFiles(); DefaultTableModel modle = (DefaultTableModel) table.getModel(); modle.setColumnIdentifiers(colonnes); for (File fichier : fichiers) { String[] dcoupage = fichier.getName().split("\\."); String libell = dcoupage[0]; String extension = dcoupage[1].toUpperCase(); long taille = fichier.length(); Image image = new ImageIcon(fichier.getPath()).getImage(); boolean isImage = image.getWidth(null) != -1; modle.addRow(new Object[]{libell, extension, taille, isImage, image.getWidth(null), image.getHeight(null)}); } } public static void main(String[] args) { new Tables(); } }

Modles de tableaux
JTable est un composant trs puissant. Il propose gracieusement de nombreuses fonctions. Cependant, la configuration par dfaut n'est gnralement pas conseill. La stratgie suivie ne correspond pas vraiment ce que nous dsirons faire dans ces exemples. En particulier, nous souhaitons des entres en lecture seule : elle ne doivent pas tre modifiables. De mme, nous aimerions que les entres de la colonne "Image ?" soient des cases cocher et non des termes anglais. Enfin, l'idal serait de pouvoir visualiser les fichiers qui sont des images. La classe abstraite AbstractTableModel
Pour obtenir une plus grande souplesse de JTable, il vaut mieux implmenter votre propre modle de tableau au lieu de placer toutes vos donnes dans un tableau bidimensionnel pour les afficher sous forme de tableau. Il suffit donc de personnaliser vos donnes en crivant votre modle de table en implmentant l'interface TableModel. Par chance, Swing nous facilite la tche en proposant la classe abstraite AbstractTableModel faisant le gros du travail. Dans notre modle personnalis, il suffit ainsi de crer une classe qui hrite de cette classe AbstractTableModel et de redfinir les trois mthodes abstraites suivantes : 1. int getRowCount() : cette mthode renvoie le nombre de ligne de la table. 2. int getColumnCount() : cette mthode renvoie le nombre de colonne de la table. 3. Object getValueAt(int ligne, int colonne) : cette mthode renvoie la valeur de la cellule dsigne.

Il existe plusieurs manires d'implmenter la mthode getValueAt(). Vous pouvez simplement calculer la rponse, ou chercher la valeur dans une base de donnes, ou encore dans une autre source de donnes. Lorsque JTable a besoin de valeurs de donnes, il appelle la mthode getValueAt() du modle de la table. Pour connatre la taille globale de la table, il appelle les mthodes getRowCount() et getColumnCount() de ce modle de table.

Nom des colonnes


Si vous ne dfinissez pas de noms de colonnes, la mthode getColumnName() du modle AbstractTableModel choisi comme nom A, B, C, etc. Pour modifier le nom des colonnes, il suffit de surcharger la mthode getColumnName().

Gestion de l'affichage suivant le type de colonne

Par dfaut, AbstractTableModel rend toutes les cellules non modifiables, ce qui correspond ce que nous voulions. Aucune modification n'est ncessaire ce sujet. Pour proposer une visualisation personnalise suivant le type reprsenter, le tableau doit possder plus d'informations sur les types des colonnes. Pour cela, vous devez nous redfinir la mthode getColumnClass() de votre modle de tableau personnalis (issu de la classe AbstractTableModel), qui renvoie la classe qui dcrit le type de la colonne. La classe JTable choisira alors un afficheur appropri pour cette classe : 1. Icon : gnre automatiquement une image. 2. Boolean : gnre automatiquement des cellules de type case cocher. 3. Numriques : gnre automatiquement une valeur numrique de type entier, de type rel, etc. 4. Object : gnre une chane de caractres en faisant appel la mthode toString().

Pour les autres types, vous pouvez fournir vos propres afficheurs de cellule. Les afficheurs de cellules de tableau sont comparables aux afficheurs de cellules d'arbre. Ce sujet sera trait dans le chapitre suivant.

Exemple d'application de modle personnalis


A titre d'exemple, je vous propose de reprendre l'application prcdente et de crer un nouveau modle personnalis qui permettra en autre de prvoir des cases cocher et d'afficher des vignettes pour les fichiers image.

codage correspondant
package tables; import import import import import java.awt.Image; java.io.File; java.text.DecimalFormat; javax.swing.*; javax.swing.table.*;

public class Tables extends JFrame { private ModleTableau modle = new ModleTableau(); private JTable tableau = new JTable(modle); public Tables() { super("Liste des fichiers"); tableau.setRowHeight(70); add(new JScrollPane(tableau)); pack(); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Tables(); } private class ModleTableau extends AbstractTableModel { private String[] colonnes = {"Nom fichier", "Extension", "Poids", "Image ?", "Dimension", "Vue"}; private File[] fichiers = new File("C:/Photos/").listFiles(); private Object[] lignes = new Object[fichiers.length]; public ModleTableau() { for (int i=0; i<fichiers.length; i++) { File fichier = fichiers[i]; String[] dcoupage = fichier.getName().split("\\."); String libell = dcoupage[0]; String extension = dcoupage[1].toUpperCase(); long taille = fichier.length(); Image image = new ImageIcon(fichier.getPath()).getImage(); Icon icne = new ImageIcon(image.getScaledInstance(-1, 70, Image.SCALE_DEFAULT)); boolean isImage = image.getWidth(null) != -1; String dimension = isImage ? image.getWidth(null)+" x "+image.getHeight(null) : "Aucune "; lignes[i] = new Object[]{libell, extension, taille, isImage, dimension, icne}; } } public int getRowCount() { return fichiers.length; } public int getColumnCount() { return colonnes.length; } public Object getValueAt(int ligne, int colonne) { Object[] fichier = (Object[]) lignes[ligne]; return fichier[colonne];

} @Override public Class<?> getColumnClass(int colonne) { Object[] premire = (Object[]) lignes[0]; return premire[colonne].getClass(); } @Override public String getColumnName(int colonne) { return colonnes[colonne]; } } }

Rendu de cellule personnalis


La classe JTable est dj capable de matriser un certain nombre d'affichages prdfinis, comme les cases cocher et les icnes. Toutefois, il peut tre intressant d'aller plus loin dans cette dmarche et de prvoir, par exemple, un formatage particulier pour les valeurs numriques. Pour les types autre que les classes Icon et Boolean, vous devez fournir votre propre afficheur de cellules. Les afficheurs de cellules de tableau sont comparables aux afficheurs de cellules d'arbre, que nous avons dj abords. Ils doivent implmenter l'interface TableCellRenderer, qui comprend une seule mthode getTableCellRendererComponent() que vous devez donc redfinir pour adapter l'affichage votre convenance.

Interface TableCellRenderer
Component getTableCellRendererComponent(JTable table, Object valeur, boolean slectionn, boolean focus, int ligne, int colonne) Mthode redfinir lorsque nous souhaitons proposer un rendu personnalis pour chaque noeud.

Cette mthode est appele lorsque la table doit afficher une cellule. Vous devez renvoyer un composant dont la mthode paint() est invoqu pour dessiner la cellule. Une fois que vous avez crer notre afficheur de cellule en crant une classe qui implmente l'interface TableCellRenderer, vous devrez demander au tableau d'utiliser cet afficheur pour tous les objets qui correspond la classe que vous souhaitez prendre en compte. La mthode setDefaultRenderer() de la classe JTable permet d'effectuer cette association. Il suffit de fournir un objet Class et l'afficheur :
table.setDefaultRenderer(Integer.class, afficheur);

Cet afficheur est maitnenant utilis pour tous les objets du type spcifi.

Mise en oeuvre
Je vous propose de rorganiser l'application prcdente afin que le poids de chaque fichier soit exprim en octets avec la prise en compte de la sparation des milliers afin que l'affichage soit plus agrable. Par ailleurs, les fichiers qui ne sont pas desimages sont pas trs visibles dans le tableau, je propose donc de renforcer la case cocher afin d'tablir un code de couleur suivant la qualit du fichier.

codage correspondant
package tables; import import import import import java.awt.*; java.io.File; java.text.DecimalFormat; javax.swing.*; javax.swing.table.*;

public class Tables extends JFrame { private ModleTableau modle = new ModleTableau(); private JTable tableau = new JTable(modle); public Tables() { super("Liste des fichiers"); tableau.setRowHeight(70); tableau.setDefaultRenderer(Long.class, new RenduEntier()); tableau.setDefaultRenderer(Boolean.class, new RenduCaseACocher());

add(new JScrollPane(tableau)); pack(); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Tables(); } private class ModleTableau extends AbstractTableModel { private String[] colonnes = {"Nom fichier", "Extension", "Poids", "Image ?", "Dimension", "Vue"}; private File[] fichiers = new File("C:/Photos/").listFiles(); private Object[] lignes = new Object[fichiers.length]; public ModleTableau() { for (int i=0; i<fichiers.length; i++) { File fichier = fichiers[i]; String[] dcoupage = fichier.getName().split("\\."); String libell = dcoupage[0]; String extension = dcoupage[1].toUpperCase(); long taille = fichier.length(); Image image = new ImageIcon(fichier.getPath()).getImage(); Icon icne = new ImageIcon(image.getScaledInstance(-1, 70, Image.SCALE_DEFAULT)); boolean isImage = image.getWidth(null) != -1; String dimension = isImage ? image.getWidth(null)+" x "+image.getHeight(null) : "Aucune "; lignes[i] = new Object[]{libell, extension, taille, isImage, dimension, icne}; } } public int getRowCount() { return fichiers.length; } public int getColumnCount() { return colonnes.length; } public Object getValueAt(int ligne, int colonne) { Object[] fichier = (Object[]) lignes[ligne]; return fichier[colonne]; } @Override public Class<?> getColumnClass(int colonne) { Object[] premire = (Object[]) lignes[0]; return premire[colonne].getClass(); } @Override public String getColumnName(int colonne) { return colonnes[colonne]; } } private class RenduEntier extends JFormattedTextField implements TableCellRenderer { public RenduEntier() { super(new DecimalFormat("#,##0 octets")); setHorizontalAlignment(RIGHT); } public Component getTableCellRendererComponent(JTable table, Object valeur, boolean slectionn, boolean focus, int ligne, int colonne) { setValue(valeur); return this; } } private class RenduCaseACocher extends JCheckBox implements TableCellRenderer { public Component getTableCellRendererComponent(JTable table, Object valeur, boolean slectionn, boolean focus, int ligne, int colonne) { setSelected((Boolean)valeur); setBackground(isSelected() ? Color.GREEN : Color.RED); return this; } } }

Modifier une cellule de tableau


Pour qu'une cellule soit modifiable, le modle de tableau doit indiquer quelles cellules sont modifiables en dfinissant la mthode isCellEditable(). La plupart du temps, vous choisirez de rendre certaines colonnes modifiables. L'AbstractTableModel dfinit la mthode isCellEditable() de sorte qu'elle renvoie toujours false. Le DefaultTableModel surchage cette mthode pour qu'elle renvoie toujours true.
A titre d'exemple, je reprends l'application prcdente dans laquelle je supprime la visualisation des fichiers image et o je rajoute l'dition possible du nom de fichier.

codage correspondant
package tables; import import import import import java.awt.*; java.io.File; java.text.DecimalFormat; javax.swing.*; javax.swing.table.*;

public class Tables extends JFrame { private ModleTableau modle = new ModleTableau(); private JTable tableau = new JTable(modle); public Tables() { super("Liste des fichiers"); tableau.setDefaultRenderer(Long.class, new RenduEntier()); tableau.setDefaultRenderer(Boolean.class, new RenduCaseACocher()); add(new JScrollPane(tableau)); pack(); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Tables(); } private class ModleTableau extends AbstractTableModel { private String[] colonnes = {"Nom fichier", "Extension", "Poids", "Image ?", "Dimension", "Vue"}; private File[] fichiers = new File("C:/Photos/").listFiles(); private Object[] lignes = new Object[fichiers.length]; public ModleTableau() { for (int i=0; i<fichiers.length; i++) { File fichier = fichiers[i]; String[] dcoupage = fichier.getName().split("\\."); String libell = dcoupage[0]; String extension = dcoupage[1].toUpperCase(); long taille = fichier.length(); Image image = new ImageIcon(fichier.getPath()).getImage(); boolean isImage = image.getWidth(null) != -1; String dimension = isImage ? image.getWidth(null)+" x "+image.getHeight(null) : "Aucune "; lignes[i] = new Object[]{libell, extension, taille, isImage, dimension}; } } public int getRowCount() { return fichiers.length; } public int getColumnCount() { return colonnes.length; } public Object getValueAt(int ligne, int colonne) { Object[] fichier = (Object[]) lignes[ligne]; return fichier[colonne]; } public void setValueAt(Object valeur, int ligne, int colonne) { Object[] fichier(Object[]) lignes[ligne]; fichier[colonne] = valeur; } @Override public Class<?> getColumnClass(int colonne) { Object[] premire = (Object[]) lignes[0]; return premire[colonne].getClass(); } @Override public String getColumnName(int colonne) { return colonnes[colonne]; } @Override public boolean isCellEditable(int ligne, int colonne) { return colonne == 0; } } private class RenduEntier extends JLabel implements TableCellRenderer { public RenduEntier() { setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); setHorizontalAlignment(RIGHT); } public Component getTableCellRendererComponent(JTable table, Object valeur, boolean slectionn, boolean focus, int ligne, int colonne) { DecimalFormat dcimal = new DecimalFormat("#,##0 octets"); setText(dcimal.format(valeur)); return this; } } private class RenduCaseACocher extends JCheckBox implements TableCellRenderer { public Component getTableCellRendererComponent(JTable table, Object valeur, boolean slectionn, boolean focus, int ligne, int colonne) { setSelected((Boolean)valeur); setBackground(isSelected() ? Color.GREEN : Color.RED); return this; } } }

Attention, si vous dsirez que votre saisie soit prise en compte, vous devez imprativement redfinir la mthode setValueAt() de votre modle de tableau afin que la nouvelle valeur s'intgre au bon endroit dans votre enregistrement bidirectionnel, ici l'objet lignes qui est un tableau de tableau d'Object.

Editeur de cellule par dfaut


Le composant JTable possde un diteur de cellule par dfaut issu de la classe DefaultCellEditor qui implmente l'interface TableCellEditor. Un DefaultCellEditor peut tre construit avec un JTextField, un JCheckBox ou un JComboBox (ou leurs fils). JTable installe automatiquement un diteur de cases cocher pour les cellules boolennes et un diteur de champ de texte pour les cellules qui ne fournissent pas leur propre afficheur. Les champs de texte permettent l'utilisateur de modifier les chanes de caractres qui ont t cres en appliquant la mthode toString() la valeur de retour de la mthode getValueAt() du modle de tableau.

Lorsque la modification est termine, la valeur modifie est rcupre par un appel la mthode getCellEditorValue() de la classe DefaultCellEditor. Cette mthode doit renvoyer une valeur de type correct ( savoir le type renvoy par la mthode getColumnType() du modle). Pour obtenir un diteur de menu droulant (JComboBox), vous devez dfinir manuellement l'diteur de la cellule. En effet, le composant JTable n'a aucune ide des valeurs appropries pour un type particulier. J'aimerais par exemple changer l'extension d'un fichier image afin de permettre ainsi la conversion vers un autre type de compression.

Voici le code d'initialisation de ce menu droulant :


JComboBox extensions = new JComboBox(new String[]{"PNG", "GIF", "JPG"});

Pour crer ensuite un DefaultCellEditor, il suffit de fournir le menu droulant au construteur :


TableCellEditor diteur = new DefaultCellEditor(extensions);

Nous devons installer ensuite l'diteur. Contrairement aux afficheurs de cellules du poids du fichier, cet diteur ne dpend pas du type de l'objet : nous ne voulons pas forcment l'utiliser pour tous les objets de type String. Au contraire, nous devrons l'installer dans une colonne particulire. La classe JTable enregistre des informations sur les colonnes du tableau dans des objets de type TableColumn. Un objet TableColumnModel gre les colonnes. Si vous ne voulez pas insrer ou supprimer de colonnes dynamiquement, vous ne vous servirez pas beaucoup du modle de colonnes du tableau. Cependant, pour obtenir un objet TableColumn particulier, vous devez passer par le modle de colonnes et lui demander l'objet de colonne correspondant :
TableColumnModel modleColonne = tableau.getColumnModel(); TableColumn colonne = modleColonne.getColumn(4);

Pour terminer, vous pouvez installer l'diteur de cellules :


tableau.setCellEditor(diteur);

Si vos cellules sont plus hautes que les cellules par dfaut, vous devrez galement dfinir la hauteur de la ligne :
tableau.setRowHeight(hauteur);

Par dfaut, toutes les lignes d'un tableau ont la mme hauteur. Vous pouvez toutefois dfinir les hauteurs de lignes individuelles en appelant :
tableau.setRowHeight(ligne, hauteur);

La hauteur de ligne relle est gale la hauteur de ligne qui a t dfinie par ces mthodes, rduite de la marge de la ligne. La marge de ligne par dfaut est de 1, mais vous pouvez galement la modifier par l'appel :
tableau.setRowMargin(marge);

Pour afficher une icne dans l'en-tte, dfinissez la valeur de l'en-tte :


colonne.setHeaderValue(new ImageIcon("extension.gif"));

L'en-tte de tableau n'est toutefois pas suffisant pour choisir un afficheur appropri pour la valeur de l'en-tte. Vous devez installer l'afficheur manuellement. Par exemple, pour afficher une icne d'image dans un en-tte de colonne, appelez :
colonne.setHeaderRenderer(tableau.getDefaultRenderer(ImageIcon.class))

Mise en oeuvre de modification de contenu de cellule


Nous allons prendre encore une fois l'application prcdente en proposant toutefois pas mal de modifications. Cette fois-ci, seuls les fichiers images seront visibles dans le tableau, et il sera surtout possible de retoucher les images dj stockes sur le disque dur en proposant de changer le nom du fichier, le format de l'image (type de compression) ainsi que les nouvelles dimensions en respectant le ratio de l'image, c'est--dire le rapport entre la largeur et la hauteur.

codage correspondant
package tables; import import import import import import import import import java.awt.*; java.awt.event.ActionEvent; java.awt.geom.*; java.awt.image.*; java.io.*; java.text.DecimalFormat; javax.imageio.ImageIO; javax.swing.*; javax.swing.table.*;

public class Tables extends JFrame { private ModleTableau modle = new ModleTableau(); private JTable tableau = new JTable(modle); private JComboBox extensions = new JComboBox(new String[]{"PNG", "GIF", "JPG"}); private JToolBar valider = new JToolBar(); public Tables() { super("Liste des fichiers"); TableColumnModel modleColonne = tableau.getColumnModel(); modleColonne.getColumn(1).setCellEditor(new DefaultCellEditor(extensions)); tableau.setDefaultRenderer(Long.class, new RenduEntier()); tableau.setRowHeight(52); add(new JScrollPane(tableau)); valider.add(new AbstractAction("Valider changement de la ligne slectionne") { public void actionPerformed(ActionEvent e) { modle.changerFichier(tableau.getSelectedRow()); } }); add(valider, BorderLayout.NORTH); pack(); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Tables(); } private class ModleTableau extends AbstractTableModel { private String[] colonnes = {"Nom fichier", "Extension", "Poids", "Largeur", "Hauteur", "Image"}; private File[] fichiers; private Object[] lignes; public ModleTableau() { constituer(); } private void constituer() { fichiers = new File("C:/Photos/").listFiles(new FileFilter() { public boolean accept(File fichier) { String nom = fichier.getName(); String[] dcoupage = fichier.getName().split("\\."); String extension = dcoupage[1].toUpperCase(); return extension.equals("GIF") || extension.equals("PNG") || extension.equals("JPG"); } }); lignes = new Object[fichiers.length]; for (int i=0; i<fichiers.length; i++) { File fichier = fichiers[i]; String[] dcoupage = fichier.getName().split("\\."); String extension = dcoupage[1].toUpperCase(); long taille = fichier.length(); BufferedImage image = null; Icon icne = null; try { image = ImageIO.read(fichier); icne = new ImageIcon(image.getScaledInstance(-1, 50, Image.SCALE_DEFAULT)); } catch (IOException ex) { } lignes[i] = new Object[]{dcoupage[0], extension, taille, image.getWidth(), image.getHeight(), icne, image}; } } @Override

public int getRowCount() { return fichiers.length; } @Override public int getColumnCount() { return colonnes.length; } @Override public Object getValueAt(int ligne, int colonne) { Object[] image = (Object[]) lignes[ligne]; return image[colonne]; } @Override public void setValueAt(Object valeur, int ligne, int colonne) { Object[] image = (Object[]) lignes[ligne]; if (colonne==3) { double largeur = (Integer) image[3]; double hauteur = (Integer) image[4]; double ratio = (double)largeur / hauteur; double rcupration = (Integer) valeur; image[4] = (int)(rcupration / ratio); tableau.repaint(); } image[colonne] = valeur; } @Override public Class<?> getColumnClass(int colonne) { Object[] premire = (Object[]) lignes[0]; return premire[colonne].getClass(); } @Override public String getColumnName(int colonne) { return colonnes[colonne]; } @Override public boolean isCellEditable(int ligne, int colonne) { return colonne==0 || colonne==1 || colonne==3 ; } public void changerFichier(int ligne) { Object[] infos = (Object[]) lignes[ligne]; BufferedImage image = (BufferedImage) infos[6]; String nom = (String) infos[0]; String extension = (String) infos[1]; int largeur = (Integer) infos[3]; int hauteur = (Integer) infos[4]; double ratio = (double)largeur / image.getWidth(); BufferedImage traitement = new BufferedImage(largeur, hauteur, image.getType()); AffineTransform retailler = AffineTransform.getScaleInstance(ratio, ratio); int interpolation = AffineTransformOp.TYPE_BICUBIC; AffineTransformOp retaillerImage = new AffineTransformOp(retailler, interpolation); retaillerImage.filter(image, traitement); try { ImageIO.write(traitement, extension, new File("C:/Photos/" + nom + '.' + extension)); constituer(); tableau.revalidate(); } catch (IOException ex) {} } } private class RenduEntier extends DefaultTableCellRenderer { public RenduEntier() { setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); setHorizontalAlignment(RIGHT); } @Override public Component getTableCellRendererComponent(JTable table, Object valeur, boolean slectionn, boolean focus, int ligne, int colonne) { DecimalFormat dcimal = new DecimalFormat("#,##0 octets"); setText(dcimal.format(valeur)); if (slectionn) setBackground(tableau.getSelectionBackground()); else setBackground(Color.WHITE); return this; } } }

Editeur de cellules de tableau personnalis


Nous pouvons aller encore plus loin dans cette dmarche. Nous pouvons, par exemple, faire en sorte que notre nom de fichier image soit diter automatiquement au travers d'un slecteur de fichier.

L'diteur de nom de fichier ne doit pas tre un diteur de cellule standard, mais une implmentation personnalise. Pour crer un diteur de cellules personnalis, vous devez implmenter l'interface TableCellEditor. Cette interface est assez pnible utiliser. Heureusement, une classe AbstractCellEditor est fournie pour grer les dtails de l'vnement.

Interface TableCellEditor
Pour matriser la gestion de votre diteur personnalis, vous devez redfinir un certain nombre de mthodes issues de cette interface TableCellEditor : 1. getTableCellEditorComponent() : La mthode getTableCellEditorComponent() de l'interface TableCellEditor a besoin d'un composant pour afficher une cellule. C'est exactement comme pour la mthode getTableCellRendererComponent() de l'interface TableCellRenderer, sauf qu'il n'y a cette fois aucun paramtre focus. Comme la cellule doit tre modifie, elle est cense possder le focus. Dans le cas d'un menu droulant, le composant de l'diteur remplace temporairement l'afficheur.

2. isCellEditable() : La classe JTable appelle votre diteur avec un vnement (comme un clic de souris) pour dterminer si cet vnement est acceptable pour initialiser le processus de modification. Vous pouvez vous occuper de la gestion vnementielle au travers de la mthode isCellEditable() de l'interface TableCellEditor. Nous pouvons par exemple accepter tous les vnements.
@Override public boolean isCellEditable(EventObject vnement) { return true; }

Cependant, si cette mthode renvoie false, le tableau n'insrera pas le composant de l'diteur.

3. shouldSelectCell() : Une fois que le composant de l'diteur est install, la mthode shouldSelectCell() est appele, avec le mme vnement. Le processus de modification doit commencer dans cette mthode, par exemple en affichant la fentre de slection de fichier.
@Override public boolean shouldSelectCell(EventObject vnement) { slecteur.showDialog(parent, libellBouton) return true; }

4. cancelCellEditing() et stopCellEditing() : Si l'utilisateur doit annuler la modification en cours, le tableau appelle la mthode cancelCellEditing(). Si l'utilisateur a cliqu sur une autre cellule du tableau, il appelle la mthode stopCellEditing(). Lorsque votre mthode stopCellEditing() est appele, le tableau peut essayer d'utiliser la valeur en cours de modification. C'est pourquoi, il ne faut renvoyer true que lorsque la valeur courante est valide. Pour notre slecteur de fichier, il peut tre judicieux de vrifier que seules les valeurs correctes soient renvoyes l'diteur. Vous devrez aussi appeler les mthodes de superclasse pour dclencher des vnements, faute de quoi la modification ne fonctionnera pas correctement.
@Override public void stopCellEditing() { ... super.stopCellEditing(); }

5. getCellEditorValue() : Enfin, vous devez dedfinir la mthode getCellEditorValue() afin qu'elle dlivre la valeur fournie par l'utilisateur au cours de la procdure de modification :
@Override public Object getCellEditorValue() { return slecteur.getSelectedFile().getName(); }

Pour rsumer
Pour rsumer, votre diteur personnalis doit : 1. Etendre la classe AbstractCellEditor et implmenter l'interface TableCellEditor.

2. Dfinir la mthode getTableCellEditorComponent() pour fournir un composant qui va reprsent votre valeur. Il peut s'agir d'un composant dummy (si vous afficher une bote de dialogue) ou un composant pour la modification sur place, comme un menu droulant ou un champ de texte. 3. Dfinir les mthodes shouldSelectCell(), stopCellEditing() et cancelCellEditing() pour grer le dbut, la ralisation et l'annulation de la procdure de modification. stopCellEditing() et cancelCellEditing() doivent appeler les mthodes de la superclasse pour s'assurer que les couteurs sont avertis 4. Dfinir la mthode getCellEditorValue() pour renvoyer la valeur qui rsulte de la procdure d'dition. 5. Enfin, vous devez indiquer le moment o l'utilisateur a termin la modification en appelant explicitement les mthodes stopCellEditing() et/ou cancelCellEditing().

Prise en compte de la modification


Lorsque la modification est termine, la classe JTable appelle la mthode setValueAt() du modle de tableau. Comme nous l'avons dj voqu au dbut de ce chapitre, vous devez imprativement surcharger cette mthode pour enregistrer la nouvelle valeur.
void setValue(Object valeur, int ligne, int colonne); Le paramtre valeur correspond l'objet renvoy par l'diteur de cellules.

1. Si vous avez implment l'diteur de cellule, vous connaissez le type d'objet renvoy par la mthode getCellEditorValue(). 2. Dans le cas de DefaultCellEditor, il existe trois possibilits pour cette valeur. Il s'agit d'un Boolean si la cellule concerne est une case cocher et d'une chane pour un champ de texte. Si la valeur provient d'un menu droulant, il s'agit alors de l'objet slectionn par l'uitlisateur.

Si l'objet valeur n'est pas du type appropri, vous devrez le convertir. Cela se produit le plus souvent lorsqu'un nombre est modifi dans un champ de texte. Dans notre exemple, nous plaons des chanes de caractres formates suivant le motif propos par la classe DecimalFormat pour la colonne Poids.

codage correspondant
package tables; import import import import import import import import import import import java.awt.*; java.awt.event.ActionEvent; java.awt.geom.*; java.awt.image.*; java.io.*; java.text.DecimalFormat; java.util.EventObject; javax.imageio.ImageIO; javax.swing.*; javax.swing.filechooser.FileNameExtensionFilter; javax.swing.table.*;

public class Tables extends JFrame { private ModleTableau modle = new ModleTableau(); private JTable tableau = new JTable(modle); private JComboBox extensions = new JComboBox(new String[]{"PNG", "GIF", "JPG"}); private JToolBar valider = new JToolBar(); public Tables() { super("Liste des fichiers"); TableColumnModel modleColonne = tableau.getColumnModel(); modleColonne.getColumn(1).setCellEditor(new DefaultCellEditor(extensions)); modleColonne.getColumn(0).setCellEditor(new EditeurCellule()); tableau.setRowHeight(52); add(new JScrollPane(tableau)); valider.add(new AbstractAction("Valider changement de la ligne slectionne") { public void actionPerformed(ActionEvent e) { modle.changerFichier(tableau.getSelectedRow()); } }); add(valider, BorderLayout.NORTH); pack(); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Tables(); } private class ModleTableau extends AbstractTableModel { private String[] colonnes = {"Nom fichier", "Extension", "Poids", "Largeur", "Hauteur", "Image"}; private File[] fichiers; private Object[] lignes; public ModleTableau() { constituer(); } private void constituer() { fichiers = new File("C:/Photos/").listFiles(new FileFilter() { public boolean accept(File fichier) { String nom = fichier.getName(); String[] dcoupage = fichier.getName().split("\\."); String extension = dcoupage[1].toUpperCase(); return extension.equals("GIF") || extension.equals("PNG") || extension.equals("JPG"); } }); lignes = new Object[fichiers.length]; for (int i=0; i<fichiers.length; i++) { File fichier = fichiers[i]; String[] dcoupage = fichier.getName().split("\\."); String extension = dcoupage[1].toUpperCase(); long taille = fichier.length(); DecimalFormat dcimal = new DecimalFormat("#,##0 octets"); BufferedImage image = null; Icon icne = null; try { image = ImageIO.read(fichier); icne = new ImageIcon(image.getScaledInstance(-1, 50, Image.SCALE_DEFAULT)); } catch (IOException ex) { } lignes[i] = new Object[]{dcoupage[0], extension, dcimal.format(taille), image.getWidth(), image.getHeight(), icne, image}; } } @Override public int getRowCount() { return fichiers.length; } @Override

public int getColumnCount() { return colonnes.length; } @Override public Object getValueAt(int ligne, int colonne) { Object[] image = (Object[]) lignes[ligne]; return image[colonne]; } @Override public void setValueAt(Object valeur, int ligne, int colonne) { Object[] image = (Object[]) lignes[ligne]; if (colonne==3) { double largeur = (Integer) image[3]; double hauteur = (Integer) image[4]; double ratio = (double)largeur / hauteur; double rcupration = (Integer) valeur; image[4] = (int)(rcupration / ratio); tableau.repaint(); } image[colonne] = valeur; } @Override public Class<?> getColumnClass(int colonne) { Object[] premire = (Object[]) lignes[0]; return premire[colonne].getClass(); } @Override public String getColumnName(int colonne) { return colonnes[colonne]; } @Override public boolean isCellEditable(int ligne, int colonne) { return colonne==0 || colonne==1 || colonne==3 ; } public void changerFichier(int ligne) { Object[] infos = (Object[]) lignes[ligne]; BufferedImage image = (BufferedImage) infos[6]; String nom = (String) infos[0]; String extension = (String) infos[1]; int largeur = (Integer) infos[3]; int hauteur = (Integer) infos[4]; double ratio = (double)largeur / image.getWidth(); BufferedImage traitement = new BufferedImage(largeur, hauteur, image.getType()); AffineTransform retailler = AffineTransform.getScaleInstance(ratio, ratio); int interpolation = AffineTransformOp.TYPE_BICUBIC; AffineTransformOp retaillerImage = new AffineTransformOp(retailler, interpolation); retaillerImage.filter(image, traitement); try { ImageIO.write(traitement, extension, new File("C:/Photos/" + nom + '.' + extension)); constituer(); tableau.revalidate(); } catch (IOException ex) {} } } private class EditeurCellule extends AbstractCellEditor implements TableCellEditor { private JFileChooser slecteur = new JFileChooser("C:/Photos"); private JLabel nomFichier = new JLabel(); private boolean valide; public EditeurCellule() { slecteur.addChoosableFileFilter(new FileNameExtensionFilter("Images GIF", "gif")); slecteur.addChoosableFileFilter(new FileNameExtensionFilter("Images PNG", "png")); slecteur.addChoosableFileFilter(new FileNameExtensionFilter("Images JPEG", "jpg", "jpeg")); } @Override public Object getCellEditorValue() { if (valide) { String[] dcoupage = slecteur.getSelectedFile().getName().split("\\."); nomFichier.setText(dcoupage[0]); } return nomFichier.getText(); } @Override public boolean shouldSelectCell(EventObject anEvent) { valide = slecteur.showDialog(null, "Nom du fichier")==JFileChooser.APPROVE_OPTION; stopCellEditing(); return true; } @Override public Component getTableCellEditorComponent(JTable table, Object valeur, boolean slectionn, int ligne, int colonne) { nomFichier.setText((String) valeur); return nomFichier; } @Override public boolean stopCellEditing() { nomFichier.repaint(); return super.stopCellEditing(); } } }

Travailler avec les lignes et les colonnes


Dans ce chapitre, nous allons apprendre manipuler les lignes et les colonnes d'un tableau. Swing n'est pas du tout symtrique pour la gestion d'un tableau, c'est--dire que les oprations supportes par les lignes d'une part et par les colonnes d'autre part ne sont pas les mmes. Le composant JTable a t optimis pour afficher des lignes

d'informations de mme structure, comme le rsultat d'une requte d'une base de donnes, et non pour une grille bidimensionnelle arbitraire d'objets. Nous reviendrons sur cette asymtrie au cours de ce chapitre.

Modifier la taille des colonnes


La classe TableColumn permet de contrler la taille des colonnes. Vous pouvez ainsi choisir : 1. void setPreferredSize(int largeur) : la largeur prfre de la colonne. 2. void setMinWidth(int largeur) : la largeur minimale de la colonne. 3. void setMaxWidth(int largeur) : la largeur maximale de la colonne.

Cette information est utilise par le tableau pour la mise en forme des colonnes. Utilisez la mthode setResizable(boolean) pour permettre ou non l'utilisateur de modifier la largeur d'une colonne. Vous pouvez aussi modifier la largeur d'une colonne avec la mthode setWidth(int). Lorsque la taille d'une colonne est modifie, le comportement par dfaut est de conserver la largeur totale du tableau. Naturellement, dans ce cas, les changements de largeur de la colonne modifie doivent tre reports sur les autres colonnes. Avec le comportement par dfaut, ces changements seront ingalement reports sur la colonne droite de la colonne modifie. Cela permet l'utilisateur d'ajuster toutes les colonnes de gauche droite. Vous pouvez choisir un autre comportement en utilisant la mthode setAutoResizeMode(int) : 1. JTable.AUTO_RESIZE_OFF : Ne modifie pas les colonnes, change la taille du tableau. 2. JTable.RESIZE_NEXT_COLUMN : Modifie uniquement la taille de la colonne suivante. 3. JTable.RESIZE_SUBSEQUENT_COLUMN : Modifie identiquement toutes les colonnes restantes. C'est le comportement par dfaut. 4. JTable.AUTO_RESIZE_LAST_COLUMN : Modifie uniquement la taille de la dernire colonne. 5. JTable.AUTO_RESIZE_ALL_COLUMN : Modifie toutes les colonnes du tableau. Ce choix est viter parce qu'il devient trs difficile d'ajuster la largeur de plusieurs colonnes.

Slectionner des lignes, des colonnes et des cellules


En fonction du mode de slection, l'utilisateur peut slectionner des lignes, des colonnes ou des cellules isoles du tableau. Par dfaut, la slection de lignes est autorise. Une ligne entire est slectionne lorsque l'utilisateur clique sur une cellule. Appelez la mthode setRowSelectionAllowed(false) de la classe JTable pour supprimer cette slection par lignes.

Lorsque la slection par lignes est permise, vous pouvez choisir parmi plusieurs modes de slection. Vous devrez donc rcuprer le modle de slection et utiliser sa mthode setSelectionMode(int) :
table.getSelectionMode().setSelectionMode(mode)

Ici mode peut prendre l'une des trois valeurs suivantes : 1. ListSelectionModel.SINGLE_SELECTION : une seule ligne. 2. ListSelectionModel.SINGLE_INTERVAL_SELECTION : un ensemble continu de lignes. 3. ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : n'importe quelles lignes.

La slection des colonnes est dsactive par dfaut. Elle peut tre active avec l'appel de la mthode setColumnSelectionAllowed(true) de la classe JTable.

L'activation des deux types de slection (lignes et colonnes) quivaut activer la slection de cellules. L'utilisateur slectionne alors des plages de cellules. Vous pouvez galement activer ce rglage par l'appel de la mthode setCellSelectionEnabled(true) de la classe JTable.

Vous pouvez identifier les lignes et les colonnes slectionnes en appelant les mthodes getSelectedRows() et getSelectedColumns() de la classe JTable. Ces deux mthodes renvoient un tableau d'indices int[] correspondant aux lments slectionnes.

Cacher et afficher des colonnes


La mthode removeColumn() de la classe JTable supprime une colonne de l'affichage d'un tableau. Les donnes de la colonne ne sont en fait pas supprimes du modle, elles sont justes caches. La mthode removeColumn() prend un argument de type TableColumn. Si vous possdez un numro de colonne, par exemple partir d'un appel getSelectedColumns(), vous devrez encore demander au modle de l'arbre la colonne relle de l'arbre qui correspond ce numro :
JTable table = new JTable(); TableColumnModel modleColonne = table.getColumnModel(); TableColumn colonne = modleColonne.getColumn(i); table.removeColumn(colonne);

Si vous avez mmoris ce numro de colonne, il est possible de l'afficher nouveau :


table.addColumn(colonne);

Cette mthode ajoute la colonne la fin du tableau. Si vous prfrez qu'elle apparaisse un autre endroit, vous devez appeler la mthode moveColumn().

Vous pouvez galement ajouter une nouvelle colonne qui correspond un indice de colonne du modle de tableau, en ajoutant un nouvel objet TableColumn :
table.addColumn(new TableColumn(indexmodleColonne));

En fait, plusieurs colonnes du tableau peuvent tre affiches partir d'une mme colonne du modle. Cependant, il n'existe pas de mthode dans JTable pour cacher ou afficher des lignes. Si vous souhaitez cacher une ligne, vous devrez avoir recours un modle de filtre.

Ajouter et supprimer des lignes dans le modle de tableau par dfaut


La classe DefaultTableModel est une classe concrte qui implmente l'interface TableModel. Elle stocke une grille bidimensionnelle d'objets. Si vos donnes se trouvent dj sous cette forme, il n'est pas ncessaire de recopier toutes ces donnes dans un modle de tableau par dfaut. En revanche, si vous possdez peu de donnes, il peut tre pratique de les copier pour obtenir rapidement un tableau. La classe DefaultTableModel possde des mthodes permettant d'ajouter des lignes et des colonnes et de supprimer des lignes. Les mthodes addRow() et addColumn() de la classe DefaultTableModel ajoutent une nouvelle ligne ou une nouvelle colonne dans les donnes. Elles ncessitent un tableau Object[] ou un vecteur qui contient les nouvelles donnes. Avec la mthode addColumn(), vous devez aussi fournir le nom de la nouvelle colonne. Ces mthodes ajoutent les nouvelles donnes la fin de la grille. Pour insrer une nouvelle ligne au milieu des donnes existantes, utilisez la mthode insertRow(). Inversement la mthode removeRow() supprime une ligne du modle. Il n'existe aucune mthode pour insrer une colonne au milieu d'une grille. Il n'existe galement aucune mthode pour supprimer une colonne. Comme l'objet JTable s'enregistre lui-mme comme un couteur de modle de tableau, le modle avertit le tableau lorsque les donnes sont insres ou supprimes. Pour l'instant, le tableau met jour l'affichage.

Exemple de mise en oeuvre


Le programme suivant montre comment fonctionnent la slection et la modification. Un modle de tableau par dfaut contient un simple ensemble de donnes, comme une table de multiplication. Le menu Edition contient les commandes suivantes : 1. Cacher toutes les colonnes slectionnes ; 2. Afficher toutes les colonnes caches ; 3. Ajouter une ligne de donnes la fin du modle ; 4. Supprimer du modle les lignes slectionnes ;

5. Effacer les cellules slectionnes.

codage correspondant
package tables; import import import import java.awt.event.*; java.util.ArrayList; javax.swing.*; javax.swing.table.*;

public class Tables extends JFrame { private DefaultTableModel modle = new DefaultTableModel(10, 10); private JTable table = new JTable(modle); private final ArrayList<TableColumn> colonnesSupprimes = new ArrayList<TableColumn>(); private JMenuBar menu = new JMenuBar(); private JMenu slection = new JMenu("Slection"); private JMenu dition = new JMenu("Edition"); private JCheckBoxMenuItem lignes = new JCheckBoxMenuItem("Lignes", table.getRowSelectionAllowed()); private JCheckBoxMenuItem colonnes = new JCheckBoxMenuItem("Colonnes", table.getColumnSelectionAllowed ()); private JCheckBoxMenuItem cellules = new JCheckBoxMenuItem("Cellules", table.getCellSelectionEnabled()); public Tables() { super("Slections dans une table"); for (int i=0; i<modle.getRowCount(); i++) for (int j=0; j<modle.getColumnCount(); j++) modle.setValueAt((i+1)*(j+1), i, j); add(new JScrollPane(table)); setJMenuBar(menu); menu.add(slection); menu.add(dition); slection.add(lignes); slection.add(colonnes); slection.add(cellules); lignes.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { table.clearSelection(); table.setRowSelectionAllowed(lignes.isSelected()); cellules.setSelected(table.getCellSelectionEnabled()); } }); colonnes.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { table.clearSelection(); table.setColumnSelectionAllowed(colonnes.isSelected()); cellules.setSelected(table.getCellSelectionEnabled()); } }); cellules.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { table.clearSelection(); table.setCellSelectionEnabled(cellules.isSelected()); lignes.setSelected(table.getRowSelectionAllowed()); colonnes.setSelected(table.getColumnSelectionAllowed ()); } }); dition.add("Cacher les colonnes").addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int[] slectionnes = table.getSelectedColumns(); TableColumnModel modleColonne = table.getColumnModel(); for (int i=slectionnes.length-1; i>=0; i--) { TableColumn colonne = modleColonne.getColumn(slectionnes[i]); table.removeColumn(colonne); colonnesSupprimes.add(colonne); } } }); dition.add("Afficher les colonnes").addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for (TableColumn colonne : colonnesSupprimes) table.addColumn(colonne); colonnesSupprimes.clear(); } }); dition.add("Ajouter une ligne").addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Integer[] nouvellesCellules = new Integer[modle.getColumnCount()]; for (int i=0; i<nouvellesCellules.length; i++) nouvellesCellules[i] = new Integer((i+1) * modle.getRowCount()+1); modle.addRow(nouvellesCellules); } }); dition.add("Supprimer des lignes").addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int[] slectionnes = table.getSelectedRows(); for (int i=slectionnes.length-1; i>=0; i--) modle.removeRow(slectionnes[i]); } }); dition.add("Effacer les cellules").addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for (int i=0; i<table.getRowCount(); i++) for (int j=0; j<table.getColumnCount(); j++) if (table.isCellSelected(i, j)) table.setValueAt(0, i, j); } });

pack(); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Tables(); } }

You might also like