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.
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 :
- Object getRoot() :
retourne la racine de l'arbre
- boolean isLeaf(Object node)
: détermine si le noeud courant est une feuille (ie. n'a pas de
fils, c'est-à-dire de sous-arborescence)
- int getChildCount(Object
parent) : retourne le nombre de fils du noeud parent.
- int getIndexOfChild(Object
parent, Object child) : retourne l'indice du fils (son rang)
d'un noeud donné.
- Object getChild(Object
parent, int index) : retourne le nième fils
d'un noeud donné.
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 :
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 :
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.
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
...
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 ...