Aller au menu - Aller au contenu

Icône Les arbres

Mise à jour : 12/02/2010
Difficulté : Difficile Difficile Creative Commons BY-NC-SA
96 702 visites depuis 7 jours, dont 428 sur ce chapitre classé 4/786
Bon : autant les objets vus dans le chapitre précédent étaient simples, autant celui que nous allons voir est assez compliqué.
Cela n'empêche pas que ce dernier est très pratique et très utilisé.

Vous devez tous déjà avoir vu un arbre, non pas celui du monde végétal, mais celui qui permet d'explorer des dossiers. Nous allons voir comment utiliser et exploiter un tel objet, et interagir avec lui : ne vous inquiétez pas, tout partira de zéro...

Le mieux, c'est encore de rentrer dans le vif du sujet ! :)

Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

La composition des arbres

Tout d'abord, pour ceux qui ne verraient pas de quoi je parle, voici ce qu'on appelle un arbre (JTree) :

Image utilisateur


La chose bien pratique avec cet objet, c'est que, même s'il ne ressemble pas à un chêne ou à un autre arbre, celui-ci est composé de la même façon !

Euh... Qu'est-ce que tu racontes ?

En fait, lorsque vous regardez bien un arbre, celui-ci est constitué de plusieurs sous-ensembles :
  • des racines ;
  • un tronc ;
  • des branches ;
  • des feuilles.


L'objet JTree se base sur la même architecture, enfin presque. Vous aurez donc :
  • une racine : répertoire le plus haut dans la hiérarchie ; ici, seul "Racine" est considéré comme une racine ;
  • une ou plusieurs branches : un ou plusieurs sous-répertoires, "Fichier enfant N°1-2-3-4" sont des branches (ou encore "Noeud N°2-4-6") ;
  • une ou plusieurs feuilles : éléments se trouvant en bas de la hiérarchie, ici "Sous Fichier enfant N°1-2-3-4" ou encore "Noeud N°1-3-5-7" sont des feuilles.


Voici le code que j'ai utilisé :

Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;


public class Fenetre extends JFrame {

	private JTree arbre;
	
	public Fenetre(){
		this.setSize(300, 300);
		this.setLocationRelativeTo(null);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setTitle("Les arbres");
		//On invoque la méthode de construction de notre arbre
		buildTree();
		
		this.setVisible(true);
	}
	
	private void buildTree(){
		//Création d'une racine
		DefaultMutableTreeNode racine = new DefaultMutableTreeNode("Racine");
		
		//Nous allons ajouter des branches et des feuilles à notre racine
		for(int i = 1; i < 12; i++){
			DefaultMutableTreeNode rep = new DefaultMutableTreeNode("Noeud N°"+i);
			
			//S'il s'agit d'un nombre pair, on rajoute une branche
			if((i%2) == 0){
				//Et une branche en plus ! Une !
				for(int j = 1; j < 5; j++){
					DefaultMutableTreeNode rep2 = new DefaultMutableTreeNode("Fichier enfant N°" + j);
					//Cette fois, on ajoute nos feuilles
					for(int k = 1; k < 5; k++)
							rep2.add(new DefaultMutableTreeNode("Sous Fichier enfant N°" + k));
					rep.add(rep2);
				}
			}
			//On ajoute la feuille ou la branche à la racine
			racine.add(rep);
		}
		//On crée, avec notre hiérarchie, un arbre
		arbre = new JTree(racine);
		
		//Que nous plaçons sur le ContentPane de notre JFrame à l'aide d'un scroll 
		this.getContentPane().add(new JScrollPane(arbre));
	}
	
	public static void main(String[] args){
		Fenetre fen = new Fenetre();
	}
	
}


Si vous avez du mal à vous y retrouver, essayez cette version de la méthode buildTree() :

Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void buildTree(){
	//Création d'une racine
	DefaultMutableTreeNode racine = new DefaultMutableTreeNode("Racine");
		
	//Nous allons ajouter des branches et des feuilles à notre racine
	for(int i = 1; i < 6; i++){
		DefaultMutableTreeNode rep = new DefaultMutableTreeNode("Noeud N°"+i);
			
		//On rajoute 4 branches
		if(i < 4){	
			DefaultMutableTreeNode rep2 = new DefaultMutableTreeNode("Fichier enfant");
			rep.add(rep2);
		}
		//On ajoute la feuille ou la branche à la racine
		racine.add(rep);
	}
	//On crée, avec notre hiérarchie, un arbre
	arbre = new JTree(racine);
	
	//Que nous plaçons sur le ContentPane de notre JFrame à l'aide d'un scroll 
	this.getContentPane().add(new JScrollPane(arbre));
}


Ce qui devrait vous donner :

Image utilisateur


En ayant manipulé ces deux objets, vous devez vous rendre compte que vous créez une véritable hiérarchie avant de créer votre arbre et d'afficher celui-ci ! :)
Vous devez aussi vous apercevoir que ce type d'objet est tout indiqué pour lister des fichiers ou des répertoires.
D'ailleurs, nous avons vu comment faire ceci lorsque nous avons abordé les flux.
Nous allons utiliser un arbre afin d'afficher notre arborescence de fichiers :

Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import java.io.File;
import java.util.Hashtable;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;


public class Fenetre extends JFrame {

	private JTree arbre;
	private DefaultMutableTreeNode racine;
	public Fenetre(){
		this.setSize(300, 300);
		this.setLocationRelativeTo(null);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setTitle("Les arbres");
		//On invoque la méthode de construction de notre arbre
		listRoot();
		
		this.setVisible(true);
	}
	
	private void listRoot(){
		
		this.racine = new DefaultMutableTreeNode();
		 
		int count = 0;
		for(File file : File.listRoots())
		{
			DefaultMutableTreeNode lecteur = new DefaultMutableTreeNode(file.getAbsolutePath());
			try {
				for(File nom : file.listFiles()){
					DefaultMutableTreeNode node = new DefaultMutableTreeNode(nom.getName()+"\\");
					
					lecteur.add(this.listFile(nom, node));
					
				}
			} catch (NullPointerException e) {}
			
			this.racine.add(lecteur);
			
			//Si nous avons parcouru plus de 50 dossiers, on sort
			//if(count > 50) {break;}
			
		}
		//On crée, avec notre hiérarchie, un arbre
		arbre = new JTree(this.racine);
		
		//Que nous plaçons sur le ContentPane de notre JFrame à l'aide d'un scroll 
		this.getContentPane().add(new JScrollPane(arbre));
	}
	
	private DefaultMutableTreeNode listFile(File file, DefaultMutableTreeNode node){
		int count = 0;
		
		if(file.isFile())
			return new DefaultMutableTreeNode(file.getName());
		else{
			for(File nom : file.listFiles()){
				count++;
				//pas plus de 5 enfants par noeud
				if(count < 5){
					DefaultMutableTreeNode subNode;
					if(nom.isDirectory()){
						subNode = new DefaultMutableTreeNode(nom.getName()+"\\");
						node.add(this.listFile(nom, subNode));
					}else{
						subNode = new DefaultMutableTreeNode(nom.getName());
					}
					node.add(subNode);
				}
			}
			return node;
		}
	}
	
	public static void main(String[] args){
		Fenetre fen = new Fenetre();
	}
	
}


Ce type de code ne devrait plus vous faire peur ! :-°
Voici ce que ça me donne, après quelques secondes...

Image utilisateur


Pas mal, mais du coup, le dossier "Racine" ne correspond à rien ici !

Effectivement ; heureusement, il existe une méthode dans l'objet JTree qui permet de ne pas afficher la racine d'une arborescence : setRootVisible(Boolean ok); .
Il vous suffit donc de rajouter cette instruction à la fin de la méthode listRoot() de notre objet JTree, juste avant d'ajouter notre arbre au ContentPane.

Bon : vous arrivez à créer et afficher un arbre, maintenant, nous allons voir comment interagir avec celui-ci ! ;)

Des arbres qui vous parlent

Vous connaissez la musique maintenant, nous allons encore implémenter une interface ! :D
Celle-ci se nomme TreeSelectionListener.
Elle ne comporte qu'une méthode à redéfinir : valueChanged(TreeSelectionEvent event) .

Voici un code utilisant une implémentation de la dite interface :

Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import java.io.File;
import java.util.Hashtable;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;


public class Fenetre extends JFrame {

	private JTree arbre;
	private DefaultMutableTreeNode racine;
	public Fenetre(){
		this.setSize(300, 200);
		this.setLocationRelativeTo(null);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setTitle("Les arbres");
		//On invoque la méthode de construction de notre arbre
		listRoot();
		
		this.setVisible(true);
	}
	
	private void listRoot(){
		
		this.racine = new DefaultMutableTreeNode();
		 
		int count = 0;
		for(File file : File.listRoots())
		{
			DefaultMutableTreeNode lecteur = new DefaultMutableTreeNode(file.getAbsolutePath());
			try {
				for(File nom : file.listFiles()){
					DefaultMutableTreeNode node = new DefaultMutableTreeNode(nom.getName()+"\\");
					
					lecteur.add(this.listFile(nom, node));
					
				}
			} catch (NullPointerException e) {}
			
			this.racine.add(lecteur);
			
			//Si nous avons parcouru plus de 50 dossiers, on sort
			//if(count > 50) {break;}
			
		}
		//On crée, avec notre hiérarchie, un arbre
		arbre = new JTree(this.racine);
		arbre.setRootVisible(false);
		arbre.addTreeSelectionListener(new TreeSelectionListener(){

			public void valueChanged(TreeSelectionEvent event) {
				if(arbre.getLastSelectedPathComponent() != null){
					System.out.println(arbre.getLastSelectedPathComponent().toString());
				}
			}
		});
		//Que nous plaçons sur le ContentPane de notre JFrame à l'aide d'un scroll 
		this.getContentPane().add(new JScrollPane(arbre));
	}
	
	private DefaultMutableTreeNode listFile(File file, DefaultMutableTreeNode node){
		int count = 0;
		
		if(file.isFile())
			return new DefaultMutableTreeNode(file.getName());
		else{
			for(File nom : file.listFiles()){
				count++;
				//pas plus de 5 enfants par noeud
				if(count < 5){
					DefaultMutableTreeNode subNode;
					if(nom.isDirectory()){
						subNode = new DefaultMutableTreeNode(nom.getName()+"\\");
						node.add(this.listFile(nom, subNode));
					}else{
						subNode = new DefaultMutableTreeNode(nom.getName());
					}
					node.add(subNode);
				}
			}
			return node;
		}
	}
	
	public static void main(String[] args){
		Fenetre fen = new Fenetre();
	}
	
}


Ce qui me donne (ben oui, on n'a toujours pas les mêmes dossiers) :

Image utilisateur


Vous avez maintenant un arbre réactif !
Lorsque vous sélectionnez un dossier, ou un fichier, le nom de ce dernier s'affiche.
Ceci se fait grâce à la méthode getLastSelectedPathComponent() qui retourne un Object correspondant au dernier point de l'arbre qui a été cliqué. Il ne reste plus qu'à utiliser la méthode toString() afin de retourner son libellé... :magicien:

Nous avons réussi à afficher le nom du dernier noeud cliqué, mais nous n'allons pas nous arrêter là... Dans notre cas, il peut être intéressant de connaître le chemin d'accès du noeud dans l'arbre ! Surtout dans notre cas, puisque nous listons le contenu de notre disque.
Nous pouvons donc avoir des informations supplémentaires sur une feuille ou une branche en utilisant un objet File, par exemple.

Comment fait-on pour connaître le chemin de l'arborescence ?

C'est un tout petit peu plus compliqué, mais rien d'insurmontable... ;)
L'objet TreeEvent en paramètre de la méthode de l'interface vous donne de précieux renseignements, dont la méthode getPath() qui vous retourne un objet TreePath qui, lui, contient les objets correspondant aux noeuds du chemin d'accès à un point de l'arbre.

C'est un peu confus, là...

Ne vous inquiétez pas, vous n'avez pas à changer beaucoup de choses pour avoir ce résultat.
En fait, je n'ai modifié que la classe anonyme utilisée pour gérer l'événement déclenché sur l'arbre.
Voici la nouvelle version de votre classe anonyme :

Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
arbre.addTreeSelectionListener(new TreeSelectionListener(){

	public void valueChanged(TreeSelectionEvent event) {
		if(arbre.getLastSelectedPathComponent() != null){
			//La méthode getPath retourne un objet TreePath
			System.out.println(getAbsolutePath(event.getPath()));
		}
	}
			
	private String getAbsolutePath(TreePath treePath){
		String str = "";
		//On balaie le contenu de notre TreePath
		for(Object name : treePath.getPath()){
			//Si l'objet à un nom, on l'ajoute au chemin
			if(name.toString() != null)
				str += name.toString();
		}
		return str;
	}
});


Et voici ce que j'ai pu obtenir :

Image utilisateur


Vous pouvez voir que nous avons maintenant le chemin complet dans notre arbre et, vu que nous utilisons les fichiers de notre système, nous allons pouvoir en savoir plus.
Nous allons donc ajouter un "coin information" à droite de notre arbre, dans un conteneur à part.

Essayer de le faire vous-mêmes dans un premier temps, sachant que j'ai obtenu quelque chose comme ça :

Image utilisateur


Voilà mon code :
Secret (cliquez pour afficher)

Classe Panneau.java


Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import java.awt.Color;
import javax.swing.JPanel;
import javax.swing.JTextArea;

public class Panneau extends JPanel {
	private String texte = "Racine de l'arbre.";
	private JTextArea jta;
	
	public Panneau(){
		this.jta = new JTextArea(texte);
		this.setBackground(Color.white);
		this.add(jta);
	}
	public void setTexte(String texte) {
		this.jta.setText(texte);
	}	
}


Classe Fenetre.java


Code : Java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import java.awt.BorderLayout;
import java.io.File;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;


public class Fenetre extends JFrame {

	private JTree arbre;
	private DefaultMutableTreeNode racine;
	private Panneau panneau = new Panneau();
	
	public Fenetre(){
		this.setSize(500, 200);
		this.setLocationRelativeTo(null);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setTitle("Les arbres");
		//On invoque la méthode de construction de notre arbre
		listRoot();
		
		this.setVisible(true);
	}
	
	private void listRoot(){
		
		this.racine = new DefaultMutableTreeNode();
		 
		int count = 0;
		for(File file : File.listRoots())
		{
			DefaultMutableTreeNode lecteur = new DefaultMutableTreeNode(file.getAbsolutePath());
			try {
				for(File nom : file.listFiles()){
					DefaultMutableTreeNode node = new DefaultMutableTreeNode(nom.getName()+"\\");
					
					lecteur.add(this.listFile(nom, node));
					
				}
			} catch (NullPointerException e) {}
			
			this.racine.add(lecteur);
			
			//Si nous avons parcouru plus de 50 dossiers, on sort
			//if(count > 50) {break;}
			
		}
		//On crée, avec notre hiérarchie, un arbre
		arbre = new JTree(this.racine);
		arbre.setRootVisible(false);
		arbre.addTreeSelectionListener(new TreeSelectionListener(){

			public void valueChanged(TreeSelectionEvent event) {
				if(arbre.getLastSelectedPathComponent() != null){
					//La méthode getPath retourne un objet TreePath
					File file= new File(getAbsolutePath(event.getPath()));
					panneau.setTexte(getDescription(file));
				}
			}
			
			private String getAbsolutePath(TreePath treePath){
				String str = "";
				//On balaie le contenu de notre TreePath
				for(Object name : treePath.getPath()){
					//Si l'objet à un nom, on l'ajoute au chemin
					if(name.toString() != null)
						str += name.toString();
				}
				return str;
			}
			/**
			 * Retourne une description d'un objet File
			 * @param file
			 * @return
			 */
			private String getDescription(File file){ 
				String str = "Chemin d'accès sur le disque : \n\t" + file.getAbsolutePath() + "\n";
				str += (file.isFile()) ? "Je suis un fichier.\nJe fais " + file.length() + " ko\n" : "Je suis un dossier.\n";
				str += "J'ai des droits : \n";
				str += "\t en lecture : " + ((file.canRead()) ? "Oui;" : "Non;");
				str += "\n\t en écriture : " + ((file.canWrite()) ? "Oui;" : "Non;");
				return str;
			}
		});
		//On crée un séparateur de conteneur pour réviser
		JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(arbre), new JScrollPane(panneau));
		//On place le séparateur
		split.setDividerLocation(200);
		//On ajoute le tout
		this.getContentPane().add(split, BorderLayout.CENTER);
	}
	
	private DefaultMutableTreeNode listFile(File file, DefaultMutableTreeNode node){
		int count = 0;
		
		if(file.isFile())
			return new DefaultMutableTreeNode(file.getName());
		else{
			for(File nom : file.listFiles()){
				count++;
				//pas plus de 5 enfants par noeud
				if(count < 5){
					DefaultMutableTreeNode subNode;
					if(nom.isDirectory()){
						subNode = new DefaultMutableTreeNode(nom.getName()+"\\");
						node.add(this.listFile(nom, subNode));
					}else{
						subNode = new DefaultMutableTreeNode(nom.getName());
					}
					node.add(subNode);
				}
			}
			return node;
		}
	}
	
	public static void main(String[] args){
		Fenetre fen = new Fenetre();
	}
	
}



J'espère que vous n'avez pas trop eu de mal à faire ce petit exercice... ^^
Vous devriez maintenant commencer à savoir utiliser ce type d'objet, mais, avant de passer à autre chose, je vous propose de voir comment personnaliser un peu l'affichage de notre arbre !

Décorez vos arbres

Vous avez la possibilité de changer les icônes pour les répertoires, les fichiers, les icônes d'ouverture, les icônes de fermeture.
Cette opération est très simple à réaliser : il vous suffit d'utiliser un objet DefaultTreeCellRenderer (qui est une sorte de modèle), de définir les icônes pour tous ces cas et, ensuite, de spécifier à votre arbre d'utiliser ce modèle en utilisant la méthode setCellRenderer(DefaultTreeCellRenderer cellRenderer) .

Voici un exemple qui vous montre trois rendus distincts :

Image utilisateur


Et voici le code que j'ai utilisé pour arriver à ce résultat :

Code : Java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import java.io.File;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;


public class Fenetre extends JFrame {

	private JTree arbre, arbre2, arbre3;
	private DefaultMutableTreeNode racine;
	//On va créer deux modèles d'affichage
	private  DefaultTreeCellRenderer[] tCellRenderer = new  DefaultTreeCellRenderer[3];
	
	public Fenetre(){
		this.setSize(600, 350);
		this.setLocationRelativeTo(null);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setTitle("Les arbres");
		//On invoque la méthode de construction de notre arbre
		initRenderer();
		listRoot();
		
		this.setVisible(true);
	}
	/**
	 * Méthode qui permet de créer des modèles d'affichage
	 */
	private void initRenderer(){
		//Instanciation
		this.tCellRenderer[0] = new  DefaultTreeCellRenderer();
		//Initialisation des images pour les actions fermer, ouvrir et pour les feuilles
		this.tCellRenderer[0].setClosedIcon(new ImageIcon("img/fermer.jpg"));
		this.tCellRenderer[0].setOpenIcon(new ImageIcon("img/ouvert.jpg"));
		this.tCellRenderer[0].setLeafIcon(new ImageIcon("img/feuille.jpg"));
		
		this.tCellRenderer[1] = new  DefaultTreeCellRenderer();
		this.tCellRenderer[1].setClosedIcon(null);
		this.tCellRenderer[1].setOpenIcon(null);
		this.tCellRenderer[1].setLeafIcon(null);
	}
	
	
	private void listRoot(){
		
		this.racine = new DefaultMutableTreeNode();
		 
		int count = 0;
		for(File file : File.listRoots())
		{
			DefaultMutableTreeNode lecteur = new DefaultMutableTreeNode(file.getAbsolutePath());
			try {
				for(File nom : file.listFiles()){
					DefaultMutableTreeNode node = new DefaultMutableTreeNode(nom.getName()+"\\");
					
					lecteur.add(this.listFile(nom, node));
					
				}
			} catch (NullPointerException e) {}
			
			this.racine.add(lecteur);
			
			//Si nous avons parcouru plus de 50 dossiers, on sort
			//if(count > 50) {break;}
			
		}
		//On crée, avec notre hiérarchie, un arbre
		arbre = new JTree(this.racine);
		arbre.setRootVisible(false);
		//On définit le rendu pour cet arbre
		arbre.setCellRenderer(this.tCellRenderer[0]);
		
		arbre2 = new JTree(this.racine);
		arbre2.setRootVisible(false);
		arbre2.setCellRenderer(this.tCellRenderer[1]);
		
		arbre3 = new JTree(this.racine);
		arbre3.setRootVisible(false);
		
		JSplitPane split = new JSplitPane(	JSplitPane.HORIZONTAL_SPLIT, 
				new JScrollPane(arbre2), 
				new JScrollPane(arbre3));
		split.setDividerLocation(200);
		
		JSplitPane split2 = new JSplitPane(	JSplitPane.HORIZONTAL_SPLIT, 
				new JScrollPane(arbre), 
				split);
		split2.setDividerLocation(200);
		this.getContentPane().add(split2);
	}
	
	private DefaultMutableTreeNode listFile(File file, DefaultMutableTreeNode node){
		int count = 0;
		
		if(file.isFile())
			return new DefaultMutableTreeNode(file.getName());
		else{
			for(File nom : file.listFiles()){
				count++;
				//pas plus de 5 enfants par noeud
				if(count < 5){
					DefaultMutableTreeNode subNode;
					if(nom.isDirectory()){
						subNode = new DefaultMutableTreeNode(nom.getName()+"\\");
						node.add(this.listFile(nom, subNode));
					}else{
						subNode = new DefaultMutableTreeNode(nom.getName());
					}
					node.add(subNode);
				}
			}
			return node;
		}
	}
	
	public static void main(String[] args){
		Fenetre fen = new Fenetre();
	}
	
}


C'est simple, n'est-ce pas ?
Vous définissez les nouvelles images à utiliser et vous spécifiez à l'arbre quel modèle utiliser ! ;)

Il existe une autre façon de changer l'affichage (le design) de votre application.
Chaque système d'exploitation à son propre "design", mais vous avez pu constater que vos applications Java ne ressemblent pas du tout à ce que votre OS vous propose d'habitude !
C'est vrai que les couleurs ne sont pas comme d'habitude !

Les couleurs, mais aussi la façon dont sont dessinés vos composants... :-°
Mais il y a un moyen de pallier ce problème : utiliser le "look and feel" de votre OS.

J'ai rajouté ces lignes de code dans le constructeur de mon objet, avant l'instruction setVisible(true) :

Code : Java
1
2
3
4
5
6
7
8
9
try {
   //On force à utiliser le look and feel du system
   UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
   //Ici on force tous les composants de notre fenêtre (this) à se redessiner avec le look and feel du système
   SwingUtilities.updateComponentTreeUI(this);
} catch (InstantiationException e) {
} catch (ClassNotFoundException e) {
} catch (UnsupportedLookAndFeelException e) {
} catch (IllegalAccessException e) {}


Ce qui me donne avec l'exemple ci-dessus :

Image utilisateur


Vous pouvez, bien sûr, utiliser d'autres "look and feel" que ceux de votre système et de Java. Voici un code qui permet de lister ces types d'affichages et d'instancier un objet Fenetre en lui spécifiant quel modèle utiliser :

Code : Java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
import java.io.File;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.tree.DefaultMutableTreeNode;


public class Fenetre extends JFrame {

	private JTree arbre, arbre2, arbre3;
	private DefaultMutableTreeNode racine;
	
	//On passe maintenant le nom du look and feel à utiliser en paramètre du constructeur
	public Fenetre(String lookAndFeel){
		this.setSize(200, 300);
		this.setLocationRelativeTo(null);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		String title = (lookAndFeel.split("\\."))[(lookAndFeel.split("\\.").length - 1)];
		this.setTitle("Nom du 'look and feel : " + title);
		//On invoque la méthode de construction de notre arbre
	
		listRoot();
		//On force l'utilisation
		try {
			   UIManager.setLookAndFeel(lookAndFeel);
			   SwingUtilities.updateComponentTreeUI(this);
			} catch (InstantiationException e) {
			} catch (ClassNotFoundException e) {
			} catch (UnsupportedLookAndFeelException e) {
			} catch (IllegalAccessException e) {}
		this.setVisible(true);
	}
	
	private void listRoot(){
		
		this.racine = new DefaultMutableTreeNode();
		 
		int count = 0;
		for(File file : File.listRoots())
		{
			DefaultMutableTreeNode lecteur = new DefaultMutableTreeNode(file.getAbsolutePath());
			try {
				for(File nom : file.listFiles()){
					DefaultMutableTreeNode node = new DefaultMutableTreeNode(nom.getName()+"\\");
					
					lecteur.add(this.listFile(nom, node));
					
				}
			} catch (NullPointerException e) {}
			
			this.racine.add(lecteur);
			
			//Si nous avons parcouru plus de 50 dossiers, on sort
			//if(count > 50) {break;}
			
		}
		//On crée, avec notre hiérarchie, un arbre
		arbre = new JTree(this.racine);
		arbre.setRootVisible(false);
		
		this.getContentPane().add(new JScrollPane(arbre));
	}
	
	private DefaultMutableTreeNode listFile(File file, DefaultMutableTreeNode node){
		int count = 0;
		
		if(file.isFile())
			return new DefaultMutableTreeNode(file.getName());
		else{
			for(File nom : file.listFiles()){
				count++;
				//pas plus de 5 enfants par noeud
				if(count < 5){
					DefaultMutableTreeNode subNode;
					if(nom.isDirectory()){
						subNode = new DefaultMutableTreeNode(nom.getName()+"\\");
						node.add(this.listFile(nom, subNode));
					}else{
						subNode = new DefaultMutableTreeNode(nom.getName());
					}
					node.add(subNode);
				}
			}
			return node;
		}
	}
	
	public static void main(String[] args){
		//nous allons créer des fenêtres avec des looks différents 
		//Cette instruction permet de récupérer tous les looks du système
		UIManager.LookAndFeelInfo[] looks = UIManager.getInstalledLookAndFeels();
		Fenetre fen;
		//On parcourt tout le tableau en passant le nom du look à utiliser
		for(int i = 0; i < looks.length; i++)
			fen = new Fenetre(looks[i].getClassName());
		
	}
	
}


J'ai capturé les fenêtres obtenues (le nom du look se trouve dans le titre de la fenêtre) :

Image utilisateur


Voici deux captures d'écran vous montrant le rendu obtenu en appliquant le look par défaut sur le pendu :

Image utilisateur


Vous pouvez voir que les menus et les boutons ressemblent déjà plus à ce que vous pouvez avoir sous Windows... :-°
Et la boîte de dialogue, c'est flagrant :

Image utilisateur


Nous allons voir comment jouer un peu avec nos arbres... (non, pas sur nos arbres... ^^ )

Jouons avec nos arbres

C'est maintenant que les choses compliquées vont commencer ! :diable:

Par contre, il va falloir faire la lumière sur certaines choses...
Vous commencez à connaître les arbres, cependant je vous ai caché certaines choses afin de ne pas surcharger le début de ce chapitre.
Voyons : votre JTree est en fait composé de plusieurs objets. Vous vous doutez bien que pour un composant aussi complexe que celui-ci, il y a du monde au balcon...

Voici une liste des objets que vous serez susceptibles d'utiliser avec ce composant (il y a cinq interfaces et une classe concrète...) :
  • TreeModel : c'est lui qui contient les noeuds de votre arbre ;
  • TreeNode : noeuds et structure de votre arbre ;
  • TreeSelectionModel : modèle de sélection de tous vos noeuds ;
  • TreePath : objet qui vous permet de connaître le chemin d'un noeud dans l'arbre et voici notre classe concrète ;
  • TreeCellRenderer : interface vous permettant de modifier l'apparence d'un noeud ;
  • TreeCellEditor : éditeur utilisé lorsqu'un noeud est éditable.


Vous allez voir que, même si ces objets sont nombreux, leur utilisation, avec un peu de pratique, n'est pas si compliquée que ça... Nous allons commencer par quelque chose d'assez simple : modifier le libellé d'un noeud !

C'est bon, on a déjà trouvé ! Il suffit d'invoquer la méthode setEditable(Boolean bool) de notre JTree !


Tout à fait !
Cependant, vous serez peut-être amenés à sauvegarder le nouveau nom de votre noeud... Il vous faudra donc déclencher un traitement lors de la modification d'un noeud. :)
Pour faire cela, nous allons utiliser l'objet TreeModel que nous allons écouter afin de déterminer ce qui se passe sur notre arbre !

Voici un exemple de code utilisant un DefaultTreeModel (classe implémentant l'interface TreeModel) ainsi qu'une implémentation de l'interface TreeModelListener afin d'écouter le dit objet :

Code : Java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import java.awt.BorderLayout;
import java.io.File;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;


public class Fenetre extends JFrame {

	private JTree arbre;
	private DefaultMutableTreeNode racine;
	//Notre objet modèle
	private DefaultTreeModel model;
	
	//On passe maintenant le nom du look and feel à utiliser en paramètre du constructeur
	public Fenetre(){
		this.setSize(200, 300);
		this.setLocationRelativeTo(null);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setTitle("JTree");	
		listRoot();		
		this.setVisible(true);
	}
	
	private void listRoot(){
		
		this.racine = new DefaultMutableTreeNode();
		 
		int count = 0;
		for(File file : File.listRoots())
		{
			DefaultMutableTreeNode lecteur = new DefaultMutableTreeNode(file.getAbsolutePath());
			try {
				for(File nom : file.listFiles()){
					DefaultMutableTreeNode node = new DefaultMutableTreeNode(nom.getName()+"\\");
					
					lecteur.add(this.listFile(nom, node));
					
				}
			} catch (NullPointerException e) {}
			this.racine.add(lecteur);
		}
		
		//On crée notre modèle
		this.model = new DefaultTreeModel(this.racine);
		//Et nous allons écouter ce que notre modèle a à nous dire ! 
		this.model.addTreeModelListener(new TreeModelListener() {
			/**
			 * Méthode appelée lorsqu'un noeud a changé
			 */
	        public void treeNodesChanged(TreeModelEvent evt) {
	          System.out.println("Changement dans l'arbre");
	          Object[] listNoeuds = evt.getChildren();
	          int[] listIndices = evt.getChildIndices();
	          for (int i = 0; i < listNoeuds.length; i++) {
	            System.out.println("Index " + listIndices[i] + ", nouvelle valeur : "
	                + listNoeuds[i]);
	          }
	        }
	        /**
	         * Méthode appelée lorsqu'un noeud est inséré
	         */
	        public void treeNodesInserted(TreeModelEvent event) {
				System.out.println("Un noeud a été inséré !");
				
			}
	        
	        /**
	         * Méthode appelée lorsqu'un noeud est supprimé
	         */
			public void treeNodesRemoved(TreeModelEvent event) {
				System.out.println("Un noeud a été retiré !");
			}
			/**
			 * Méthode appelée lorsque la structure d'un noeud a été modifiée
			 */
			public void treeStructureChanged(TreeModelEvent event) {
				System.out.println("La structure d'un noeud a changé !");
			}
        });
		//On crée, avec notre hiérarchie, un arbre
		arbre = new JTree();
		//On passe notre modèle à notre arbre 
		//===> on pouvait aussi passer l'objet TreeModel au constructeur du JTree
		arbre.setModel(model);
		arbre.setRootVisible(false);
		//On rend notre arbre éditable
		arbre.setEditable(true);
		this.getContentPane().add(new JScrollPane(arbre), BorderLayout.CENTER);
	}
	
	private DefaultMutableTreeNode listFile(File file, DefaultMutableTreeNode node){
		int count = 0;
		
		if(file.isFile())
			return new DefaultMutableTreeNode(file.getName());
		else{
			for(File nom : file.listFiles()){
				count++;
				//pas plus de 5 enfants par noeud
				if(count < 3){
					DefaultMutableTreeNode subNode;
					if(nom.isDirectory()){
						subNode = new DefaultMutableTreeNode(nom.getName()+"\\");
						node.add(this.listFile(nom, subNode));
					}else{
						subNode = new DefaultMutableTreeNode(nom.getName());
					}
					node.add(subNode);
				}
			}
			return node;
		}
	}
	
	public static void main(String[] args){
		//nous allons créer des fenêtres avec des looks différents 
		//Cette instruction permet de récupérer tous les looks du système
		
		try {
			 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());			   
		} catch (InstantiationException e) {
		} catch (ClassNotFoundException e) {
		} catch (UnsupportedLookAndFeelException e) {
		} catch (IllegalAccessException e) {}
		Fenetre fen = new Fenetre();
		
	}	
}


Afin de pouvoir changer le nom d'un noeud, vous devez double-cliquer sur un noeud avec un espace d'environ 1/2 seconde entre chaque clic... Si vous double-cliquez trop vite, vous déplierez le noeud ! ! :)


Ce qui m'a donné chez moi :

Image utilisateur Image utilisateur

Image utilisateur

Le dossier 'toto' s'appelait 'CNAM/' : vous pouvez voir que lorsque nous changeons le nom d'un noeud, la méthode treeNodesChanged(TreeModelEvent evt) est invoquée !

Vous voyez que, mis à part le fait que plusieurs objets sont utilisés, ce n'est pas si compliqué que ça...
Maintenant, je vous propose de voir comment ajouter des noeuds à notre arbre !
Pour ce faire, nous allons utiliser un bouton qui va nous demander de spécifier le nom du nouveau noeud, ceci via un JOptionPane.

Voici un code d'exemple :

Code : Java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;


public class Fenetre extends JFrame {

	private JTree arbre;
	private DefaultMutableTreeNode racine;
	private DefaultTreeModel model;
	private JButton bouton = new JButton("Ajouter");
	
	//On passe maintenant le nom du look and feel à utiliser en paramètre du constructeur
	public Fenetre(){
		this.setSize(200, 300);
		this.setLocationRelativeTo(null);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		this.setTitle("JTree");
		//On invoque la méthode de construction de notre arbre
	
		listRoot();
		bouton.addActionListener(new ActionListener(){
			
			public void actionPerformed(ActionEvent event) {
				if(arbre.getLastSelectedPathComponent() != null){						
					JOptionPane jop = new JOptionPane();
					String nodeName = jop.showInputDialog("Saisir un nom de noeud");
					
					if(nodeName != null && !nodeName.trim().equals("")){
						
						DefaultMutableTreeNode parentNode =  (DefaultMutableTreeNode)arbre.getLastSelectedPathComponent();
						DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(nodeName);
						parentNode.add(childNode);
						model.insertNodeInto(childNode, parentNode, parentNode.getChildCount()-1);
						model.nodeChanged(parentNode);						
					}					
				}
				else{
					System.out.println("AUCUNE SELECTION ! ! !");
				}
			}
		});
		this.getContentPane().add(bouton, BorderLayout.SOUTH);
		this.setVisible(true);
	}
	
	private void listRoot(){
		
		this.racine = new DefaultMutableTreeNode();
		 
		int count = 0;
		for(File file : File.listRoots())
		{
			DefaultMutableTreeNode lecteur = new DefaultMutableTreeNode(file.getAbsolutePath());
			try {
				for(File nom : file.listFiles()){
					DefaultMutableTreeNode node = new DefaultMutableTreeNode(nom.getName()+"\\");					
					lecteur.add(this.listFile(nom, node));					
				}
			} catch (NullPointerException e) {}
			
			this.racine.add(lecteur);
			
		}
		//On crée, avec notre hiérarchie, un arbre
		arbre = new JTree();
		this.model = new DefaultTreeModel(this.racine);
		
		arbre.setModel(model);
		arbre.setRootVisible(false);
		arbre.setEditable(true);
		arbre.getModel().addTreeModelListener(new TreeModelListener() {

	        public void treeNodesChanged(TreeModelEvent evt) {
	          System.out.println("Changement dans l'arbre");
	          Object[] listNoeuds = evt.getChildren();
	          int[] listIndices = evt.getChildIndices();
	          for (int i = 0; i < listNoeuds.length; i++) {
	            System.out.println("Index " + listIndices[i] + ", noeud déclencheur : "
	                + listNoeuds[i]);
	          }
	        }	
	        public void treeNodesInserted(TreeModelEvent event) {
				System.out.println("Un noeud a été inséré !");				
			}
			public void treeNodesRemoved(TreeModelEvent event) {
				System.out.println("Un noeud a été retiré !");
			}
			public void treeStructureChanged(TreeModelEvent event) {
				System.out.println("La structure d'un noeud a changé !");
			}
        });
		
		this.getContentPane().add(new JScrollPane(arbre), BorderLayout.CENTER);
	}
	
	private DefaultMutableTreeNode listFile(File file, DefaultMutableTreeNode node){
		int count = 0;
		
		if(file.isFile())
			return new DefaultMutableTreeNode(file.getName());
		else{
			for(File nom : file.listFiles()){
				count++;
				//pas plus de 5 enfants par noeud
				if(count < 3){
					DefaultMutableTreeNode subNode;
					if(nom.isDirectory()){
						subNode = new DefaultMutableTreeNode(nom.getName()+"\\");
						node.add(this.listFile(nom, subNode));
					}else{
						subNode = new DefaultMutableTreeNode(nom.getName());
					}
					node.add(subNode);
				}
			}
			return node;
		}
	}
	
	public static void main(String[] args){
		//nous allons créer des fenêtres avec des looks différents 
		//Cette instruction permet de récupérer tous les looks du système
		
		try {
			   UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
			} catch (InstantiationException e) {
			} catch (ClassNotFoundException e) {
			} catch (UnsupportedLookAndFeelException e) {
			} catch (IllegalAccessException e) {}
		Fenetre fen = new Fenetre();
		
	}
}


Vous remarquerez que nous avons ajouté des variables d'instances afin d'y avoir accès dans toute notre classe !


Voici quelques captures d'écran :

Image utilisateurImage utilisateur

Image utilisateurImage utilisateur


Là non plus, rien d'extraordinairement compliqué, mis à part cette portion de code :

Code : Java
1
2
3
4
5
parentNode =  (DefaultMutableTreeNode)arbre.getLastSelectedPathComponent();
DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(nodeName);
DefaultMutableTreeNode parentNode.add(childNode);
model.insertNodeInto(childNode, parentNode, parentNode.getChildCount()-1);
model.nodeChanged(parentNode);


Tout d'abord, nous récupérons le dernier noeud sélectionné avec parentNode = (DefaultMutableTreeNode)arbre.getLastSelectedPathComponent(); ; ensuite, nous créons un nouveau noeud avec DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(nodeName); et nous l'ajoutons dans notre noeud parent avec l'instruction parentNode.add(childNode); , mais nous devons spécifier à notre modèle que celui-ci a un nouveau noeud et donc, qu'il a changé, ceci avec les instructions :
Code : Java
1
2
model.insertNodeInto(childNode, parentNode, parentNode.getChildCount()-1);
model.nodeChanged(parentNode);


Et voilà !

Avant de vous laisser, il me reste encore à vous montrer... un code !
Celui-ci permet de retirer dynamiquement un noeud d'un arbre ! :)
Inutile de le commenter, celui-ci est très clair :

Code : Java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;


public class Fenetre extends JFrame {

	private JTree arbre;
	private DefaultMutableTreeNode racine;
	private DefaultTreeModel model;
	private JButton bouton = new JButton("Ajouter"),  effacer = new JButton("Effacer");
	
	//On passe maintenant le nom du look and feel à utiliser en paramètre du constructeur
	public Fenetre(){
		this.setSize(200, 300);
		this.setLocationRelativeTo(null);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		this.setTitle("JTree");
		//On invoque la méthode de construction de notre arbre
	
		listRoot();
		bouton.addActionListener(new ActionListener(){
			
			public void actionPerformed(ActionEvent event) {
				if(arbre.getLastSelectedPathComponent() != null){						
					JOptionPane jop = new JOptionPane();
					String nodeName = jop.showInputDialog("Saisir un nom de noeud");
					
					if(nodeName != null && !nodeName.trim().equals("")){						
						DefaultMutableTreeNode parentNode =  (DefaultMutableTreeNode)arbre.getLastSelectedPathComponent();
						DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(nodeName);
						parentNode.add(childNode);
						model.insertNodeInto(childNode, parentNode, parentNode.getChildCount()-1);
						model.nodeChanged(parentNode);
					}					
				}
				else{
					System.out.println("AUCUNE SELECTION ! ! !");
				}
			}
		});
		
		effacer.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event) {
				if(arbre.getLastSelectedPathComponent() != null){						
					DefaultMutableTreeNode node =  (DefaultMutableTreeNode)arbre.getLastSelectedPathComponent();
					DefaultMutableTreeNode parentNode =  (DefaultMutableTreeNode)node.getParent();
					model.removeNodeFromParent(node);
					model.nodeChanged(parentNode);									
				}
				else{
					System.out.println("AUCUNE SELECTION ! ! !");
				}
			}
		});
		
		JPanel pan = new JPanel();
		pan.add(bouton);
		pan.add(effacer);
		
		this.getContentPane().add(pan, BorderLayout.SOUTH);
		this.setVisible(true);
	}
	
	private void listRoot(){
		
		this.racine = new DefaultMutableTreeNode();
		 
		int count = 0;
		for(File file : File.listRoots())
		{
			DefaultMutableTreeNode lecteur = new DefaultMutableTreeNode(file.getAbsolutePath());
			try {
				for(File nom : file.listFiles()){
					DefaultMutableTreeNode node = new DefaultMutableTreeNode(nom.getName()+"\\");					
					lecteur.add(this.listFile(nom, node));					
				}
			} catch (NullPointerException e) {}
			
			this.racine.add(lecteur);
			
		}
		//On crée, avec notre hiérarchie, un arbre
		arbre = new JTree();
		this.model = new DefaultTreeModel(this.racine);
		
		arbre.setModel(model);
		arbre.setRootVisible(false);
		arbre.setEditable(true);
		arbre.getModel().addTreeModelListener(new TreeModelListener() {

	        public void treeNodesChanged(TreeModelEvent evt) {
	        	
	        }	
	        public void treeNodesInserted(TreeModelEvent event) {
				System.out.println("Un noeud a été inséré !");				
			}
			public void treeNodesRemoved(TreeModelEvent event) {
				System.out.println("Un noeud a été retiré !");
			}
			public void treeStructureChanged(TreeModelEvent event) {
				System.out.println("La structure d'un noeud a changé !");
			}
        });
		
		this.getContentPane().add(new JScrollPane(arbre), BorderLayout.CENTER);
	}
	
	private DefaultMutableTreeNode listFile(File file, DefaultMutableTreeNode node){
		int count = 0;
		
		if(file.isFile())
			return new DefaultMutableTreeNode(file.getName());
		else{
			for(File nom : file.listFiles()){
				count++;
				//pas plus de 5 enfants par noeud
				if(count < 3){
					DefaultMutableTreeNode subNode;
					if(nom.isDirectory()){
						subNode = new DefaultMutableTreeNode(nom.getName()+"\\");
						node.add(this.listFile(nom, subNode));
					}else{
						subNode = new DefaultMutableTreeNode(nom.getName());
					}
					node.add(subNode);
				}
			}
			return node;
		}
	}
	
	public static void main(String[] args){
		//nous allons créer des fenêtres avec des looks différents 
		//Cette instruction permet de récupérer tous les looks du système
		
		try {
			   UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
			} catch (InstantiationException e) {
			} catch (ClassNotFoundException e) {
			} catch (UnsupportedLookAndFeelException e) {
			} catch (IllegalAccessException e) {}
		Fenetre fen = new Fenetre();
		
	}
}


Ce qui me donne :

Avant toute suppression

Image utilisateur


Après suppression du dossier "Installer/"

Image utilisateur


Après suppression du dossier "D/"

Image utilisateur


Avant de clore ce chapitre, voici un petit cadeau.

Secret (cliquez pour afficher)


Voici comment utiliser un menu contextuel avec vos arbres.
Je n'ai implémenté qu'un menu contextuel pour effacer un noeud, mais la démarche est la même pour d'autres actions...

Code : Java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;


public class Fenetre extends JFrame {

	private JTree arbre;
	private DefaultMutableTreeNode racine;
	private DefaultTreeModel model;
	private JButton bouton = new JButton("Ajouter"),  effacer = new JButton("Effacer");
	private JMenuItem eraseMenu ;
	
	//On passe maintenant le nom du look and feel à utiliser en paramètre du constructeur
	public Fenetre(){
		this.setSize(200, 300);
		this.setLocationRelativeTo(null);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		this.setTitle("JTree");
		//On invoque la méthode de construction de notre arbre
	
		listRoot();
		bouton.addActionListener(new ActionListener(){
			
			public void actionPerformed(ActionEvent event) {
				if(arbre.getLastSelectedPathComponent() != null){						
					JOptionPane jop = new JOptionPane();
					String nodeName = jop.showInputDialog("Saisir un nom de noeud");
					
					if(nodeName != null && !nodeName.trim().equals("")){						
						DefaultMutableTreeNode parentNode =  (DefaultMutableTreeNode)arbre.getLastSelectedPathComponent();
						DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(nodeName);
						parentNode.add(childNode);
						model.insertNodeInto(childNode, parentNode, parentNode.getChildCount()-1);
						model.nodeChanged(parentNode);
					}					
				}
				else{
					System.out.println("AUCUNE SELECTION ! ! !");
				}
			}
		});
		
		effacer.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event) {
				if(arbre.getLastSelectedPathComponent() != null){						
					DefaultMutableTreeNode node =  (DefaultMutableTreeNode)arbre.getLastSelectedPathComponent();
					DefaultMutableTreeNode parentNode =  (DefaultMutableTreeNode)node.getParent();
					model.removeNodeFromParent(node);
					model.nodeChanged(parentNode);									
				}
				else{
					System.out.println("AUCUNE SELECTION ! ! !");
				}
			}
		});
		
		JPanel pan = new JPanel();
		pan.add(bouton);
		pan.add(effacer);
		
		this.getContentPane().add(pan, BorderLayout.SOUTH);
		this.setVisible(true);
	}
	
	private void listRoot(){
		
		this.racine = new DefaultMutableTreeNode();
		 
		int count = 0;
		for(File file : File.listRoots())
		{
			DefaultMutableTreeNode lecteur = new DefaultMutableTreeNode(file.getAbsolutePath());
			try {
				for(File nom : file.listFiles()){
					DefaultMutableTreeNode node = new DefaultMutableTreeNode(nom.getName()+"\\");					
					lecteur.add(this.listFile(nom, node));					
				}
			} catch (NullPointerException e) {}
			
			this.racine.add(lecteur);
			
		}
		//On crée, avec notre hiérarchie, un arbre
		arbre = new JTree();
		this.model = new DefaultTreeModel(this.racine);
		
		arbre.setModel(model);
		arbre.setRootVisible(false);
		arbre.setEditable(true);
		arbre.getModel().addTreeModelListener(new TreeModelListener() {

	        public void treeNodesChanged(TreeModelEvent evt) {
	        	
	        }	
	        public void treeNodesInserted(TreeModelEvent event) {
				System.out.println("Un noeud a été inséré !");				
			}
			public void treeNodesRemoved(TreeModelEvent event) {
				System.out.println("Un noeud a été retiré !");
			}
			public void treeStructureChanged(TreeModelEvent event) {
				System.out.println("La structure d'un noeud a changé !");
			}
        });
		
		/**
		 * Effacer un noeud avec un menu contextuel ! !
		 */
		arbre.addMouseListener(new MouseAdapter(){
			//Lorsque nous cliquons avec la souris
			public void mousePressed(MouseEvent event) {
				//Lors d'un clic droit
				if(event.getButton() == MouseEvent.BUTTON3){
					
					System.out.println("Clic droit détecté ! !");
					
					//Si on a cliqué sur l'arbre, on peut récupérer l'indice de la ligne avec cette méthode
					//Sinon, la méthode retourne -1
					if(arbre.getRowForLocation(event.getX(), event.getY()) != -1){
						
						//On peut récupérer le chemin du noeud qui a généré l'événement
						arbre.setSelectionPath(arbre.getPathForLocation(event.getX(), event.getY()));
						System.out.println("Noeud sélectionné : " + ((DefaultMutableTreeNode)arbre.getLastSelectedPathComponent()).toString());
						
						//On peut donc en déduire le noeud et le parent
						DefaultMutableTreeNode node =  (DefaultMutableTreeNode)arbre.getLastSelectedPathComponent();
						DefaultMutableTreeNode parentNode =  (DefaultMutableTreeNode)node.getParent();
						
						//On n'a plus qu'à générer notre menu contextuel !
						JPopupMenu jpm = new JPopupMenu();
						eraseMenu = new JMenuItem("Effacer ce noeud");
						eraseMenu.addActionListener(new EraseMenuListener(parentNode, node));	
						jpm.add(eraseMenu);
						jpm.show(arbre, event.getX(), event.getY());
						//Et le tour est joué ! ! !
					}
				}
			}			
		});
		
		this.getContentPane().add(new JScrollPane(arbre), BorderLayout.CENTER);
	}
	
	private DefaultMutableTreeNode listFile(File file, DefaultMutableTreeNode node){
		int count = 0;
		
		if(file.isFile())
			return new DefaultMutableTreeNode(file.getName());
		else{
			for(File nom : file.listFiles()){
				count++;
				//pas plus de 5 enfants par noeud
				if(count < 3){
					DefaultMutableTreeNode subNode;
					if(nom.isDirectory()){
						subNode = new DefaultMutableTreeNode(nom.getName()+"\\");
						node.add(this.listFile(nom, subNode));
					}else{
						subNode = new DefaultMutableTreeNode(nom.getName());
					}
					node.add(subNode);
				}
			}
			return node;
		}
	}
	
	/**
	 * Listener personnalisé pour le menu contextuel
	 * @author CHerby
	 */
	class EraseMenuListener implements ActionListener{

		//Nous allons nous servir de ces deux objets
		DefaultMutableTreeNode  parentNode, node;
		public EraseMenuListener(DefaultMutableTreeNode parent, DefaultMutableTreeNode node){
			this.parentNode = parent;
			this.node = node;
		}
		//Ici, vous connaissez !
		public void actionPerformed(ActionEvent e) {
			model.removeNodeFromParent(this.node);
			model.nodeChanged(this.parentNode);
		}		
	}
	
	
	public static void main(String[] args){
		//nous allons créer des fenêtres avec des looks différents 
		//Cette instruction permet de récupérer tous les looks du système
		
		try {
			   UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
			} catch (InstantiationException e) {
			} catch (ClassNotFoundException e) {
			} catch (UnsupportedLookAndFeelException e) {
			} catch (IllegalAccessException e) {}
		Fenetre fen = new Fenetre();
		
	}
}


Le résultat est sympa :

Image utilisateur Image utilisateur




Voilà : je pense que vous en savez assez pour utiliser les arbres dans vos futures applications !
Je vous propose donc un petit tour sur le topo des familles.

Ce que vous devez retenir

  • Les arbres sont une combinaison entre des DefaultMutableTreeNode et des JTree.
  • Vous pouvez masquer le répertoire "racine" en invoquant la méthode setRootVisible(Boolean ok) .
  • Afin d'intercepter les événements sur un tel composant, vous devrez implémenter l'interface TreeSelectionListener.
  • Cette interface n'a qu'une méthode à redéfinir : public void valueChanged(TreeSelectionEvent event) .
  • L'affichage des différents éléments constituant un arbre peut être modifié en utilisant un DefaultTreeCellRenderer. Définissez et affectez cet objet à l'arbre pour personnaliser l'affichage de votre arbre.
  • Vous pouvez aussi changer le "look and feel" et utiliser celui de votre OS.

Q.C.M.

Quel est le nom de l'objet qui permet d'afficher une hiérarchie de DefaultMutableTreeNode ?
Quel nom porte l'interface à implémenter afin d'intercepter les événements sur un tel objet ?
Vous pouvez changer l'affichage d'un arbre en lui attribuant un nouveau :

Statistiques de réponses au QCM

Plus de peur que de mal, même si c'est un objet compliqué à utiliser.
Mais maintenant que vous êtes chaud, nous continuons avec un objet tout aussi compliqué (si ce n'est pas plus :-° ) avec : les tableaux.
Chapitre précédent Sommaire Chapitre suivant

Partager

13 commentaires pour "Les arbres"
Note moyenne : 3.57 / 4 (1025 votes)
Pseudo Commentaire
Hors ligne Artus # Posté le 28/05/2009 à 13:18:35
Avatar

Ville : Fresnes
Pays : France métropolitaine

La réponse à la question posée au dessus m'intéresse également.
Est-ce une choix ou y a t-il un intérêt à procéder de la sorte ?
 
Hors ligne gg++ # Posté le 26/04/2010 à 15:59:54
Ca gère la fougère
Avatar

Avis : Bon

C'est dommage qu'il n'explique pas comment rejouter un noeud facilement ! :(

Image utilisateur
 
Hors ligne Dominique0796 # Posté le 07/04/2011 à 15:44:33
Pas de victoires sans échecs
Avatar

Avis : Très bon

Pas mal, seulement, pour ceux qui ne connaissent pas la récursivité, c'est la galère !
Même moi qui connais un peu la récursivité, ça m'a hanté toute une nuit tellement j'avais du mal à me représenter le fonctionnement ...
Pour ceux qui ont du mal sur ce point :
http://www.siteduzero.com/tutoriel-3-3 [...] lisation.html
 
Hors ligne Dominique0796 # Posté le 07/04/2011 à 15:46:58
Pas de victoires sans échecs
Avatar

Avis : Très bon

@eTouche

Je pense que c'est pour économiser de la mémoire.
Tant qu'un objet n'est pas instancier, il doit prendre moins de mémoire que si il est instancié.
Et donc si l'objet n'a pas besoin d'être instancier dans le code qui suit, ça aura économiser un peu de mémoire ...
Ce n'est qu'une hypothèse, mais c'est plausible !
 
Hors ligne nainnain6 # Posté le 05/05/2011 à 21:49:51

bonsoir.
Pourquoi tous les fichiers n'apparaissent ils pas dans l'arborescence tel que users program files etc ?
merci d'avance

Voir tous les commentaires