|
Le signe © renvoie à la correction |
Objectif. Lobjectif de cet exercice est la réalisation dune application pour éditer un texte organisé sous forme darborescence : la racine représente le document tout entier, les fils de la racine les diverses parties dans lesquelles le document est divisé, chacune divisée à son tour en sous-parties, elles-mêmes divisées à leur tour, etc. Chaque nud de larbre porte un titre et, facultativement, un contenu.
Limage ci-dessous montre laspect de lapplication que nous voulons réaliser. Elle doit permettre dinsérer (boutons Créer fils et Créer frère) et supprimer (bouton Supprimer) un nud à nimporte quel endroit de larbre, ainsi que de vider entièrement ce dernier (bouton Arbre vide), de lenregistrer dans un fichier et de le recharger ultérieurement (boutons Enregistrer et Recharger).
Lorsquun nud est sélectionné dans le volet de gauche, la zone de texte de droite montre son contenu, et il est possible alors de modifier ce dernier. Deux boutons (Accepter et Rejeter) permettent dintégrer le texte modifié dans larbre ou, au contraire, de revenir à létat du contenu avant modification.
Édition dun texte organisé de manière arborescente
(texte pris sur le site Comment ça
marche [linformatique ?])
Cet exercice illustre lutilisation de plusieurs éléments de Swing :
Nous allons réaliser quatre programmes successifs, chacun représentant une étape dun cheminement vers le programme demandé.
Au premier niveau de notre application tous les composants de linterface sont mis en place mais la structure de données c.-à-d. larbre avec les morceaux de texte nexiste pas et les boutons nont pas deffet. Voici ce quon obtient quand on lance lapplication :
Version 1, après quelques clics...
Larbre qui apparaît à gauche est larbre par défaut, « offert » par la classe JTree quand on ne lui donne pas un autre arbre à afficher. La zone de texte, inutilisée pour le moment, nous servira pour y afficher des messages qui révèlent la détection de pressions sur les boutons et de sélections dans larbre.
Le travail consiste à écrire et tester la classe EditeurArboricole.
Indications. Cest une sous-classe de JPanel et elle implémente linterface ActionListener (elle sera lauditeur des événements produits par les boutons).
Variables dinstance de cette classe (par exemple, protected) :
Il est conseillé de centraliser la définition des textes des boutons en en faisant une collection de constantes de classe :
protected static final String CREER_FILS = "Créer fils"; protected static final String CREER_FRERE = "Créer frère"; protected static final String SUPPRIMER = "Supprimer"; etc.
Le constructeur de cette classe fait essentiellement deux choses : initialiser larbre et placer les composants de linterface graphique. Linitialisation de larbre ressemble à ceci :
... arbre = new JTree(); arbre.setEditable(true); arbre.setShowsRootHandles(true); arbre.getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION); arbre.addTreeSelectionListener(new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent evt) { nouvelleCible(arbre.getSelectionPath()); } }); ...
Explication : une fois larbre créé, lappel de setEditable rend les modifications permises, lappel de setShowsRootHandles précise que la racine a une « poignée de développement » comme les autres nuds, enfin lappel de setSelectionMode indique que la sélection de plusieurs nuds nest pas permise.
On associe ensuite à larbre un objet TreeSelectionListener anonyme, dans lequel la méthode valueChanged spécifie ce quil faut faire chaque fois que le noeud sélectionné a changé. Ici on appelle une méthode nouvelleCible (quil faut écrire) avec, pour argument, le chemin joignant la racine au nud qui vient dêtre sélectionné. Dans cette version du programme, la méthode nouvelleCible se limite à ajouter à la zone de texte une ligne montrant ce chemin sous forme de suite de nuds.
La mise en place de linterface graphique se fait comme dhabitude à laide de tout un micmac de panneaux et layout managers :
Afin de permettre à larbre et aux contenus textuels de grandir sans déborder on prendra soin de ne pas poser ces composants directement sur les panneaux qui les supportent, mais de les ajouter à des objets JScrollPane, eux-mêmes posés sur les panneaux en question.
Il est conseillé de rationaliser la création des huit boutons, qui sont identiques et ont le même auditeur dévénements « action » (à savoir, lobjet EditeurArboricole lui-même) en créant une méthode auxiliaire nouveauBouton qui prend pour argument le texte du bouton.
Puisque la classe EditeurArboricole est censée implémenter linterface ActionListener il faudra écrire aussi la méthode actionPerformed qui, pour commencer, se limitera à ajouter un texte indicatif dans la zone de texte.
Méthode principale. La méthode principale de cette classe est la méthode main « générique » souvent utilisée, qui restera la même dans les phases suivantes de cet exercice. Il sagit de
Dans cette partie nous nous proposons détudier le comportement dun arbre (classe JTree) pas, ou très peu, « personnalisé ».
En particulier, nous allons constater quun tel arbre, muni dun modèle par défaut (classe DefaultTreeModel) pilotant des noeuds par défaut (classe DefaultMutableTreeNode) permet déjà de réaliser un éditeurs darbres très convenable, du moins si on se contente de nuds simples, portant chacun une seule information (par exemple, une étiquette).
Ainsi, à partir de lapplication précédente, avec un modèle et des nuds par défaut, il suffira dajouter le code qui détecte les actions sur les quatre premiers boutons pour obtenir une application permettant de faire ceci :
(Dans lexemple précédent, la zone de texte est utilisée - comme au niveau précédent - pour afficher une trace des sélections faites.)
Voici ce quil faut faire à la version 1 pour obtenir la version 2 :
1. Introduire deux nouvelles variables dinstance :
N.B. Lappellation modèle fait référence au design pattern appelé « Modèle-Vue-Controleur ». La vue se charge de la présentation graphique, le contrôleur détecte et transmet les actions de lutilisateur sur la vue, le modèle représente les données que la vue montre (ou du moins linterface entre les données et la vue).
2. Initialiser ces variables nouvelles, par exemple au début du constructeur. Dans cet exercice nous voulons nous en tenir aux versions par défaut des objets en jeu ; par conséquent, nous nous limiterons à remplacer
arbre = new JTree();
par
racine = new DefaultMutableTreeNode("Racine"); modele = new DefaultTreeModel(racine); arbre = new JTree(modele);
3. Modifier actionPerformed pour quelle réagisse aux quatre premiers boutons. Par cela, des comparaisons successives de la chaîne commande aux constantes CREER_FILS, CREER_FRERE, SUPPRIMER etc. doit permettre dappeler la méthode adéquate parmi creerFils(String etiquette), creerFrere(String etiquette), supprimerNoeud() et supprimerTousLesNoeuds().
4. Enfin, il va falloir écrire les quatre méthodes mentionnées ci-dessus. A titre dindication, voici la plus compliquée des quatre, on vous laisse chercher les autres :
1 private void creerFrere(String etiquette) { 2 if (brancheCible != null) { MutableTreeNode noeud = 3 (MutableTreeNode) brancheCible.getLastPathComponent(); MutableTreeNode parent = (MutableTreeNode) noeud.getParent(); 4 if (parent != null) { 5 MutableTreeNode neuf = new DefaultMutableTreeNode(etiquette); 6 modele.insertNodeInto(neuf, parent, parent.getIndex(noeud) + 1); 7 arbre.scrollPathToVisible(brancheCible.pathByAddingChild(neuf)); return; } } 8 Toolkit.getDefaultToolkit().beep(); }
Quelques explications (en se référant aux numéros ci-dessus) :
Écrivez les quatre méthodes creerFils(String etiquette), creerFrere(String etiquette), supprimerNoeud() et supprimerTousLesNoeuds() et faites-vous plaisir en constatant à quel point le programme obtenu est satisfaisant.
Avec les nuds par défaut fournis par la bibliothèque (DefaultMutableTreeNode) on peut faire beaucoup de choses, mais dans les applications réelles il faut souvent des nuds spécialisés, plus étroitement dépendants de ce que lapplication manipule.
Pour illustrer cela, nous allons introduire des nuds à peine plus complexes que les nuds par défaut. Ce seront les instances de la classe NoeudTextuel, et chacune portera deux informations
Puisquil sagit de mettre en place un arbre, deux autres informations sont nécessaires dans chaque nud :
Nous ferons en sorte que la sélection dun nud produise laffichage de son contenu dans la zone de texte, mais pour le moment cela ne sera pas dun intérêt palpitant :
La classe NoeudTextuel possède
La partie intéressante de la question est celle-ci : afin que le modèle puisse opérer sur des nuds NoeudTextuel il faudra que ceux-ci soient une sorte de TreeNode et même, vu quon doit les modifier, de MutableTreeNode, une sous-interface de TreeNode. Il faudra donc que notre classe commence par
public class NoeudTextuel implements MutableTreeNode { ...
ce qui nous obligera à définir les treize méthodes imposées par MutableTreeNode (dont sept héritées de TreeNode). Vous en trouverez les spécifications dans la documentation en ligne de lAPI.
La plupart de ces méthodes sont faciles à comprendre et toutes sont très courtes (la plupart font une ligne et aucune ne dépasse deux lignes). On notera que beaucoup dopérations sur les fils dun nud se ramènent immédiatement à des opérations sur les vecteurs.
Deux seules remarques :
Le principal changement à faire à la version 2 de la classe EditeurArboricole pour obtenir la version 3 est évident : remplacer toutes les occurrences de
new DefaultMutableTreeNode( un titre )
par
new NoeudTextuel( un titre )
(il y en a trois : une pour initialiser la racine, une pour créer des fils, une pour créer des frères).
Cette modification serait la seule, si on navait pas dit que la sélection dun noeud doit provoquer laffichage de son contenu. Il faudra donc aussi ajouter quelques lignes à la méthode nouvelleCible.
Il ne manque plus que deux fonctionnalités à notre programme : lédition des contenus des nuds (boutons Accepter et Rejeter) et la sauvegarde de larbre dans un fichier et son rechargement ultérieur.
1. Pour commencer il faut ajouter les lignes manquantes dans la méthode actionPerformed afin que tous les boutons soient reconnus. Cela introduit trois nouvelles méthodes, quil va falloir écrire :
2. Édition des contenus. En plus de transmettre des commandes, les boutons Accepter et Rejeter joueront le rôle dindicateurs : ces deux boutons seront inopérants (estompés) si et seulement si le texte montré dans la zone de texte na pas été modifié depuis son affichage et est donc identique à la version rangée dans larbre.
Lors de leur création, ces deux boutons seront donc désactivés (« activé » se dit ici enabled). Ensuite, il faudra détecter les modifications du texte affiché ; il suffira pour cela dassocier un auditeur keyListener au composant zoneTexte, qui se chargera dactiver les deux boutons lorsque lutilisateur fera une frappe au clavier (attention, il faut quun texte soit effectivement affiché, les frappes au clavier quand la zone de texte est vide ne doivent pas avoir deffet).
Il faudra écrire la méthode accepterOuRejeterTexte qui, avec largument true constitue la réponse à une pression du bouton Accepter et avec largument false à celle du bouton Rejeter. Cela consiste à faire jouer les méthodes getText, setText (classe JTextArea), getContenu et setContenu (classe NoeudTextuel).
Enfin, ne pas oublier d empêcher quon puisse changer de sélection tant que le nud couramment sélectionné nest pas « sauvé » (c.-à-d. accepté ou rejeté).
3. Enregistrement et rechargement. Pour sauvegarder la totalité de larbre nous allons utiliser le mécanisme de la sérialisation des objets, extrêmement puissant et simple demploi.
Des classes et méthodes existent en Java pour enregistrer le ou les objets en question, avec assez dinformation sur leurs classes pour assurer, lors des rechargements ultérieurs, la cohérence des classes utilisées lors de la sauvegarde avec celles connues lors de la restauration.
Il faut comprendre que, les objets étant toujours désignés par référence, enregistrer un objet qui a des objets membres revient à enregistrer un graphe, souvent cyclique. Cet enregistrement est loin dêtre trivial !
Voici la séquence dopérations qui constituent la méthode enregistrerArbre (mutatis mutandis, une séquence analogue constitue la méthode rechargerArbre, on vous la laisse chercher) :
Un certain nombre des opérations précédentes peuvent lancer des exceptions. Il faut les attraper, et afficher une boîte de dialogue pour informer lutilisateur (dailleurs, une boîte informative peut être affichée aussi en cas de réussite) :
Attention. Lopération décriture dun objet dans un ObjectOutputStream sappelle sérialisation de cet objet. Pour que cette opération puisse avoir lieu il faut que lobjet en question, et tous ses objets membres, et les objets membres de leurs objets membres, etc., appartiennent à des classes déclarées implémenter linterface Seriazable. Ici, la seule classe concernée est NoeudTextuel, dont il faudra donc écrire la première ligne sous la forme
public class NoeudTextuel implements MutableTreeNode, Serializable { ...
Linterface Serializable est vide, ainsi la déclaration « implements Serializable » nengage à rien. Cest juste une marque signifiant une « permission dêtre sérialisé »