Professional Documents
Culture Documents
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.
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.
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.
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);
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);
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.
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);
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);
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);
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();
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.
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)); } } }
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.
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());
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.
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);
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.
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)); } } }
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 :
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.
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.
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.
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; } } }
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)
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.
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.
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.
... }
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.
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.
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"};
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.
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.
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.
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.
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]; } } }
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; } } }
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.
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.
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);
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);
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))
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; } } }
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().
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(); } } }
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.
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.
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.
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.
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(); } }