Les arbres et leur structure

Ce contenu est obsolète. Il peut contenir des informations intéressantes mais soyez prudent avec celles-ci.

Autant les objets vus dans le chapitre précédent étaient simples, autant celui que nous allons voir est assez compliqué. Cela ne l'empêche pas d'être 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 !

La composition des arbres

Tout d'abord, pour ceux qui ne verraient pas de quoi je parle, la figure suivante vous montre ce qu'on appelle un arbre (JTree).

Exemple d'arbre

La chose bien pratique avec cet objet c'est que, même s'il ne ressemble pas à un chêne ou à un autre arbre, il est composé de la même façon ! 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. Vous aurez donc :

  • une racine : le 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 « Nœud 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 « Nœud n° 1-3-5-7 » sont des feuilles.

Voici le code que j'ai utilisé :

 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
//CTRL + SHIFT + O pour générer les imports nécessaires

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 les 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);
    }
    //Nous créons, 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() :

 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);
  }
  //Nous créons, 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));
}

Cela devrait vous donner la figure suivante.

Autre exemple de JTree

En ayant manipulé ces deux objets, vous devez vous rendre compte que vous construisez une véritable hiérarchie avant de créer et d'afficher votre arbre ! Ce type d'objet est tout indiqué pour lister des fichiers ou des répertoires. D'ailleurs, nous avons vu comment faire lorsque nous avons abordé les flux. C'est avec un arbre que nous allons afficher notre arborescence de fichiers :

 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
//CTRL + SHIFT + O pour générer les imports nécessaires
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 l'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);                 
    }
    //Nous créons, 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{
      File[] list = file.listFiles();
      if(list == null)
        return new DefaultMutableTreeNode(file.getName());

      for(File nom : list){
        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. La figure suivante montre ce que ça me donne, après quelques secondes…

Arborescence de fichiers

Pas mal, mais du coup, le dossier « Racine » ne correspond à rien ! 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 suffit donc de rajouter l'instruction setRootVisible(false); à la fin de la méthode listRoot() de l'objet JTree, juste avant d'ajouter notre arbre au ContentPane.

Bon : vous arrivez à créer et afficher un arbre. Maintenant, voyons comment interagir avec !

Des arbres qui vous parlent

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

Voici un code utilisant une implémentation de cette interface :

 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
//CTRL + SHIFT + O pour générer les imports nécessaires

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 l'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);         
    }
    //Nous créons, 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{
      File[] list = file.listFiles();
      if(list == null)
        return new DefaultMutableTreeNode(file.getName());

      for(File nom : list){
        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();
  }   
}

Cela donne la figure suivante.

Arborescence qui réagit

Votre arbre est maintenant réactif ! Lorsque vous sélectionnez un dossier ou un fichier, le nom de ce dernier s'affiche. Cela se fait grâce à la méthode getLastSelectedPathComponent() : elle 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é.

Nous avons réussi à afficher le nom du dernier nœud cliqué, mais nous n'allons pas nous arrêter là… Il peut être intéressant de connaître le chemin d'accès du nœud dans l'arbre ! Surtout dans notre cas, puisque nous listons le contenu de notre disque.

Nous pouvons donc obtenir des informations supplémentaires sur une feuille ou une branche en recourant à un objet File, par exemple. L'objet TreeEvent passé en paramètre de la méthode de l'interface vous apporte de précieux renseignements, dont la méthode getPath() qui vous retourne un objet TreePath. Ce dernier contient les objets correspondant aux nœuds du chemin d'accès à un point de l'arbre. Ne vous inquiétez pas, vous n'avez pas à changer beaucoup de choses pour obtenir ce résultat.

En fait, je n'ai modifié que la classe anonyme qui gère l'événement déclenché sur l'arbre. Voici la nouvelle version de cette classe anonyme :

 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 l'objet TreePath
    for(Object name : treePath.getPath()){
      //Si l'objet a un nom, on l'ajoute au chemin
      if(name.toString() != null)
        str += name.toString();
    }
    return str;
  }
});

La figure suivante vous montre ce que j'ai pu obtenir.

Affichage du chemin complet des nœuds

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

Essayez de le faire vous-mêmes dans un premier temps, sachant que j'ai obtenu quelque chose comme la figure suivante.

Afficher des informations sur les fichiers

Voir la correction

J'espère que vous n'avez pas eu trop 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 d'apprendre à personnaliser un peu l'affichage de notre arbre.

Décorez vos arbres

Vous avez la possibilité de changer les icônes des répertoires et des fichiers, tout comme celles d'ouverture et 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 qu'il lui fait utiliser ce modèle en utilisant la méthode setCellRenderer(DefaultTreeCellRenderer cellRenderer).

La figure suivante vous montre un exemple de trois rendus distincts.

Icônes personnalisées

Et voici le code qui m'a permis d'arriver à ce résultat :

  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
//CTRL + SHIFT + O pour générer les imports nécessaires

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 l'arbre
    initRenderer();
    listRoot();

    this.setVisible(true);
  }

  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/ferme.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);
    }
    //Nous créons, 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{
      File[] list = file.listFiles();
      if(list == null)
        return new DefaultMutableTreeNode(file.getName());

      for(File nom : list){
        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 et indiquez à l'arbre le modèle à utiliser !

Il existe une autre façon de changer l'affichage (le design) de votre application. Chaque système d'exploitation possède son propre « design », mais vous avez pu constater que vos applications Java ne ressemblent pas du tout à ce que votre OS (Operating System, ou système d'exploitation) vous propose 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) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
try {
  //On force à utiliser le « look and feel » du système
  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) {}

Cela me donne la figure suivante.

Design de l'OS forcé

Bien sûr, vous pouvez 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'affichage et d'instancier un objet Fenetre en lui spécifiant quel modèle utiliser :

 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
//CTRL + SHIFT + O pour générer les imports nécessaires
public class Fenetre extends JFrame {

  private JTree arbre, arbre2, arbre3;
  private DefaultMutableTreeNode racine;

  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);

    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);
  }

  //…

  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());      
  }   
}

La figure suivante représente plusieurs fenêtres ainsi obtenues.

Différents « look and feel »

Modifier le contenu de nos arbres

C'est maintenant que les choses se compliquent ! Il va falloir faire la lumière sur certaines choses… Vous commencez à connaître les arbres : cependant, je vous ai caché quelques éléments afin de ne pas surcharger le début de ce chapitre.

Votre JTree est en fait composé de plusieurs objets. 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 nœuds de votre arbre ;
  • TreeNode : nœuds et structure de votre arbre ;
  • TreeSelectionModel : modèle de sélection de tous vos nœuds ;
  • TreePath : objet qui vous permet de connaître le chemin d'un nœud dans l'arbre. La voilà, notre classe concrète ;
  • TreeCellRenderer : interface permettant de modifier l'apparence d'un nœud ;
  • TreeCellEditor : éditeur utilisé lorsqu'un nœud est éditable.

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

Il faudra commencer par le rendre éditable, via la méthode setEnabled(Boolean bok) de notre JTree. Attention, vous serez peut-être amenés à sauvegarder le nouveau nom de votre nœud. Il faudra donc déclencher un traitement lors de la modification d'un nœud. Pour faire cela, nous allons utiliser l'objet TreeModel et l'écouter afin de déterminer ce qui se passe avec 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 cet objet :

  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
//CTRL + SHIFT + O pour générer les imports nécessaires
public class Fenetre extends JFrame {

  private JTree arbre;
  private DefaultMutableTreeNode racine;
  //Notre objet modèle
  private DefaultTreeModel model;
  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);
    }

    //Nous créons 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é !");
      }
    });

    //Nous créons, avec notre hiérarchie, un arbre
    arbre = new JTree();
    //Nous passons 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{
      File[] list = file.listFiles();
      if(list == null)
        return new DefaultMutableTreeNode(file.getName());

      for(File nom : list){
        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 nœud, vous devez double-cliquer dessus avec un intervalle d'environ une demi-seconde entre chaque clic… Si vous double-cliquez trop vite, vous déplierez le nœud !

Ce code a donné chez moi la figure suivante.

Changement de la valeur d'un nœud

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

Vous voyez que, mis à part le fait que plusieurs objets sont mis en jeu, ce n'est pas si compliqué que ça…

Maintenant, je vous propose d’examiner la manière d'ajouter des nœuds à notre arbre. Pour ce faire, nous allons utiliser un bouton qui va nous demander de spécifier le nom du nouveau nœud, via un JOptionPane.

Voici un code d'exemple :

  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
//CTRL + SHIFT + O pour générer les imports nécessaires
public class Fenetre extends JFrame {
  private JTree arbre;
  private DefaultMutableTreeNode racine;
  private DefaultTreeModel model;
  private JButton bouton = new JButton("Ajouter");

  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 l'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 sélection !");
        }
      }
    });
    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);         
    }
    //Nous créons, 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{
      File[] list = file.listFiles();
      if(list == null)
        return new DefaultMutableTreeNode(file.getName());

      for(File nom : list){
        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 look and feel différents 
    //Cette instruction permet de récupérer tous les look and feel 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 !

La figure suivante nous montre différentes étapes de création de nœuds.

Création d'un nœud

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

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 nœud sélectionné avec la ligne 1. Ensuite, nous créons un nouveau nœud avec la ligne 2 et l'ajoutons dans le nœud parent avec la ligne 3. Cependant, nous devons spécifier à notre modèle qu'il contient un nouveau nœud et donc qu'il a changé, au moyen des instructions des lignes 4 et 5.

Pour supprimer un nœud, il suffirait d'appeler model.removeNodeFromParent(node).

Voilà : je pense que vous en savez assez pour utiliser les arbres dans vos futures applications !


  • Les arbres constituent une combinaison d'objets DefaultMutableTreeNode et d'objets JTree.
  • Vous pouvez masquer le répertoire « racine » en invoquant la méthode setRootVisible(Boolean ok).
  • Afin d'intercepter les événements sur tel ou tel composant, vous devez 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é à l'aide d'un DefaultTreeCellRenderer. Définissez et affectez cet objet à votre arbre pour en personnaliser l'affichage.
  • Vous pouvez aussi changer le « look and feel » et utiliser celui de votre OS.