TP9 : Le MVC appliqué au JTree de Swing

Un explorateur minimal de systèmes de fichiers

Nous avons lors des séances précédentes étudié l'utilisation du design pattern Modèle Vue Contrôleur sur plusieurs exemples : un thermomètre (en cours), un gestionnaire RVB (en TP) et l'utilisation du composant JList (en cours). Dans ce TP, nous allons de nouveau utiliser ce même principe pour étudier le composant JTree en réalisant un petit explorateur de systèmes de fichiers. En effet, comme la majorité des systèmes de fichiers "classiques" sont en fait des arborescences, le JTree est particulièrement adapté à leur visualisation.

Comme pour la JList que nous avions étudié en cours, nous allons suivre la même approche avec le JTree. Dans un premier temps, nous allons nous intéresser au modèle sous-jacent, l'interface TreeModel, puis nous intéresserons à la vue, la classe JList, avant de nous concentrer sur la spécialisation de l'affichage des éléments de l'arborescence, avec le principe de délégation de l'interface TreeCellRenderer. Comme vous le constatez, la démarche est strictement identique à ce qui vous a été présenté en cours avec le composant JList !

1. Le Modèle : l'interface TreeModel

Comme pour la JList, il existe une interface TreeModel et une implémentation par défaut DefaultTreeModel définissant ce qui est attendu d'un modèle arborescent.

TreeModel
TreeModelMethods

Si l'on met de côté pour l'instant les méthodes liées à la gestion des événements et des abonnés (add/removeTreeModelListener et valueForPathChanged), il ne reste que les méthodes liées à l'inspection d'une arborescence :
Comme vous le constatez, le mapping entre ce modèle arborescent et un système de fichier est direct. En effet, si vous examinez la classe java.io.File, vous constaterez qu'il y a deux cas : soit c'est un répertoire (un noeud), soit c'est un fichier (une feuille).

Pour réaliser votre modèle, définissez une classe FileSystemModel qui implémente l'interface TreeModel. Le constructeur de cette classe prendra un objet de type File en paramètre, qui représentera la racine de l'arborescence explorée.

Nous allons commencer par implémenter les méthodes qui ont été détaillé ci-dessus. Pour cela, il suffit d'explorer un peu les méthodes de la classe File. Nous allons aussi introduire une classe très simple qui étend la classe File et se contente de surcharger la méthode toString() pour retourner le nom du fichier (cela sera utile pour l'affichage d'un noeud dans la vue !). Vous veillerez donc à retourner ce type d'objet dans la méthode getChild.

Il nous reste maintenant à implémenter les méthodes liées à la gestion des abonnés. De nouveau, il suffit de déclarer une structure de données permettant de gérer une liste d'abonnés et d'implémenter les méthodes addTreeModelListener et removeTreeModelListener.

Finalement, il ne nous reste plus que la méthode valueForPathChanged(TreePath path, Object newValue) a implémenter. Pour mieux comprendre à quoi correspond cette méthode, regardons un peu ce que nous indique la Javadoc :

valuePath

Cette méthode est invoquée lorsque l'utilisateur a modifié la valeur du noeud identifié par le chemin path. Dans notre cas, cela correspond à un renommage de fichier. Comme il est précisé dans la Javadoc, il est nécessaire de notifier les abonnés si il y a effectivement un changement de la valeur du noeud (ce n'est pas toujours le cas : notamment lorsqu'un noeud de même valeur est déjà présent !). Pour effectuer cette notification, ajoutez une méthode fireTreeNodesChanged(TreePath parentPath, int[] indices, Object[] children), qui a pour charge de créer un événement de type TreeModelEvent et de le distribuer aux différents abonnés de type TreeModelListener.

2. La vue, la classe JList

Maintenant que nous avons défini notre modèle, en respectant le contrat d'un TreeModel, il ne nous reste plus qu'à écrire la partie visible du projet : l'interface utilisateur, la vue JTree. Dans un premier temps, nous nous contenterons d'afficher une fenêtre contenant deux zones : la première présentant l'arborescence de fichiers en utilisant un JTree et la deuxième une simple zone de texte affichant diverses informations sur le fichier actuellement sélectionné.

Voici un exemple de ce que vous devrez obtenir :

jtreefs

Pour pouvoir répondre aux interactions de l'utilisateur, il faudra définir un contrôleur qui implémentera l'interface TreeSelectionListener (qui ne contient que la méthode valueChanged(TreeSelectionEvent)) et affichera les informations concernant le fichier sélectionné par l'utilisateur.

3. Amélioration de l'affichage des noeuds de l'arbre

De la même manière qu'il est possible de spécialiser un affichage particulier d'un item au sein d'une JList (cf. exemple du cours), il est possible de changer les icônes représentant les noeuds ou les feuilles au sein d'un JTree. Le principe est strictement identique : il faut implémenter l'interface TreeRenderer, ou écrire une classe qui spécialise la classe fournie par défaut, à savoir DefaultTreeCellRenderer.

treecellinterface
treecellmethods

Une première modification simple consiste à juste utiliser les méthodes setX(...) de la classe DefaultTreeCellRenderer. Une deuxième modification demandant un peu plus de travail consisterait à retourner une icône spécifique en fonction de la nature du fichier (en se basant ici sur son extension). Ainsi, il y aurait une icône particulière pour les répertoires, une autre icône pour les fichiers textes, des icônes différentes pour les différents types d'images ...

cellrenderer

Pour pouvoir charger facilement une image, le problème de sa localisation par rapport au programme qui s'exécute se pose. Il est possible de donner le chemin absolu (ie. depuis la racine), mais cela empêche de diffuser ensuite son programme, l'utilisateur ayant a modifier le chemin vers les images. La deuxième solution, qui est préférable, consiste à spécifier des chemins relatifs vers les images. Pour cela, il faut procéder en deux étapes. Dans un premier temps, il faut copier vos images dans un répertoire (img par exemple) dans le même répertoire que la classe qui va les utiliser (misc.jtreefilesystem par exemple). Vous pourrez ensuite déterminer l'URL de cette ressource en utilisant l'instruction suivante :

getClass().getResource("images/directory.png") (retourne l'URL représentant cette ressource).

Remarque : Utilisez des images au format JPEG pour vos icônes car le format PNG semble ne pas être supporté (en tout cas en 1.4) ! :(

4. Amélioration des fonctionnalités de notre explorateur

Maintenant que nous disposons d'un "joli" affichage au niveau du JTree, il pourrait être intéressant de rajouter des fonctionnalités à notre petit explorateur. Par exemple la prévisualisation des fichiers, en ne se préoccupant que des fichiers textuels et graphiques. Vous pourrez ensuite si vous trouvez des librairies permettant de gérer d'autres types de fichiers, ajouter des prévisualisateurs pour les autres types de fichiers ...