JTree et repr
Le
jp

Bonjour,
J'ai une question à propos des JTree. Une JList est la représentation
graphique d'une List, mais un JTree ça se sauvegarde dans quelle
structure de données?
Je pose la question car, dans ce groupe, on m'avait conseillé, il y a
quelque temps, de séparer les données en mémoire de leur représentation
graphique. C'est ce que je fais car c'est une bonne façon de programmer.
Mais pour sauver un JTree en mémoire, pour avoir une structure de données
sur la quelle on peut faire des calculs, ça se passe comment? Comment
faire pour avoir une structure de données que je pourrais ensuite sauver
sur disque et restaurer dans la mémoire à volonté?
Merci d'avance.
J'ai une question à propos des JTree. Une JList est la représentation
graphique d'une List, mais un JTree ça se sauvegarde dans quelle
structure de données?
Je pose la question car, dans ce groupe, on m'avait conseillé, il y a
quelque temps, de séparer les données en mémoire de leur représentation
graphique. C'est ce que je fais car c'est une bonne façon de programmer.
Mais pour sauver un JTree en mémoire, pour avoir une structure de données
sur la quelle on peut faire des calculs, ça se passe comment? Comment
faire pour avoir une structure de données que je pourrais ensuite sauver
sur disque et restaurer dans la mémoire à volonté?
Merci d'avance.
jp
Un arbre, manifestement ;) .
En général on essaie de raisonner sur les données qu'on veut
représenter, puis sur leur représentation... mais ce n'est pas toujours
facile au début :) .
Le mieux serait d'indiquer ce que tu veux stocker/représenter comme
information. Notamment est-ce qu'il s'agit d'une imbrication
potentiellement infinie ou non.
Par exemple est-ce que tu veux représenter quelque chose comme ça :
Roman
Chapitre
Paragraphe
Ou encore quelque chose comme ça :
Note
Sous-note
Sous-sous-note
...
Un arbre n'est qu'une racine avec des nœuds fils, qui eux-même peuvent
avoir des nœuds fils, ... Suivant ce que tu veux représenter, il n'y a
pas forcément de classe java pour ça. Les deux exemples ci-dessus sont
des arbres, simplement représentés par des classes ayant des listes de
nœuds fils (par exemple un chapitre possède une liste de paragraphes).
Donc si tu nous dis ce que tu veux stocker dans ton arbre, ça aidera à
créer les classes de données (ça c'est facile) et à définir une classe
héritant de JTreeModel qui permet de parcourir cet arbre. Est-ce que tu
t'en es sorti avec les modèles pour les listes ?
Oui, c'est bien ça. J'ai des objets Chapters qui contiennent des String.
Une pour le corps du chapitres et deux autres pour les notes et synopsis.
Je n'ai que ce type d'objets, quelque soit le niveau d'imbrication.
Chaque chapitre ou sous chapitre à la même structure.
Oui mais ça a l'air plus compliqué que les List.
Je pense m'en être sorti avec ma JList. Je me suis débrouillé en
reprenant un exemple fourni par Oracle que j'ai modifié pour l'adapter à
mon besoin. Il ne me manquait plus qu'à l'adapter à mes objets Chapter,
car pour l'instant il ne traite que des objets String. En me promenant
dans la doc, j'ai découvert un autre exemple avec un JTree. C'est bien
plus ergonomique et esthétique avec un arbre. Du coup je suis en train de
voir si je choisis cette solution et surtout si je pense pouvoir utiliser
l'arbre dans les traitements sur les String de mes Chapter. Ça risque
d'être peut-être un peu trop compliqué pour moi, alors qu'avec une List
ça ne m'aurait pas posé de trop gros problèmes. Car c'est vrai que c'est
plus compliqué de parcourir un arbre qu'une liste...
Plus haut tu parles de la classe JTreeModel. J'ai cherché et je ne l'ai
pas vue dans la doc. J'ai même regardé dans celle de JavaEE et elle n'y
est pas non plus...
Voici ma classe Chapter:
package model;
import java.io.*;
import java.util.*;
public class Chapter implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private boolean is_edited;
private String title, body, notes, synopsis;
private Date key_date; //Clé unique basée sur la date de création
pour identifier un chapitre donné.
public Chapter() {
//Inits
this.title = new String( "Titre du chapitre" );
this.notes = new String( "Texte des notes" );
this.synopsis = new String( "Texte du synopsis" );
this.body = new String( "Texte principal" );
this.key_date = new Date();
this.is_edited = false;
}
public Date getDate() {
return key_date;
}
public void setDate( Date d ) {
this.key_date = d;
}
//Etc...
public String toString() {
return this.title;
}
}
Je posterai mon objet LocationSensitiveDemo trouvé dans la doc fournie
par Oracle comme exemple, après l'avoir modifié pour qu'il colle à ce que
je veux en faire. Mais ça va me prendre du temps car cet exemple est plus
compliqué que celui des JList.
À bientôt et merci!
jp
Dans ce cas les nœuds de ton arbre sont directement tes chapitres, et
la classe Chapter va contenir des sous-chapitres. Ça devrait ressembler
à ça (je ne reprends pas tout ce qui se trouve dans ta classe, juste
pour simplifier) :
public class Chapitre
{
private String titre ;
private String notes ;
private List<Chapitre> sousChapitres ;
}
Et voilà, c'est un arbre ! Ou en tout cas un nœud de l'arbre.
Il n'y a pas besoin d'une classe représentant l'arbre, par contre tu
auras sans doute une classe contenant la liste de chapitres de plus
haut niveau, l'objet de cette classe sera la racine de ton arbre (le
nœud de plus haut niveau).
La seule chose plus compliquée c'est le parcours (tu as l'habitude de
fonctions récursives ?), mais ce n'est rien de très difficile avec
quelques exemples.
La question principale est "est-ce que les données sont vraiment
récursives (chapitres ayant des sous-chapitres, ayant des
sous-sous-chapitres, ...), la profondeur dépendant des choix de
l'utilisateur ?". Ou bien tu choisi juste qu'il y a deux niveaux
(chapitres et sous-chapitres) et que ce sera figé ?
Pardon, il s'agit de la classe TreeModel, mentionnée dans la doc de
JTree.
Cette classe ne possède pas beaucoup de méthodes, ça n'a pas l'air très
difficile à implémenter (à première vue...).
Par exemple getChild prend en paramètre un nœud parent et l'indice de
son enfant, ça doit pouvoir s'écrire comme ça (en supposant que la
classe qui regroupe les chapitres s'appelle DonneesAppli) :
public Object getChild (Object parent, int index)
{
if (parent instanceof DonneesAppli)
return ((DonneesAppli)parent).getChapitres().get (index) ;
else if (parent instanceof Chapitre)
return ((Chapitre)parent).getSousChapitres().get (index) ;
else
throw new RuntimeException ("Type inconnu : " + parent.getClass()) ;
}
Cette classe que tu crées et qui hérite de TreeModel posséderait
un attribut de type DonneesAppli, pour pouvoir implémenter
la méthode getRoot(), qui permettra au JTree de parcourir ton arbre.
Ma méthode plus haut est un peu simplifiée : si tu as comme enfants
d'un nœud-chapitre des sous-chapitres mais aussi des chaînes, il faut
en tenir compte dans tes calculs d'indices et dans d'autres méthodes.
Si ce n'est pas clair, essaie de l'implémenter avec seulement les
chapitres et sous-chapitres et reviens pour intégrer les chaînes à
tout ça.
Si tu ne sais pas encore comment tu vas faire la sauvegarde, tu peux
zapper la sérialisation : tu verras plus tard comment tu décides de
sauvegarder tes données.
Et le fait d'utiliser une date pour identifier les chapitres me paraît
dangereux si tu penses conserver ça à terme. Tu n'es pas à l'abri d'un
conflit (clics de création résolus très rapidement ou création
automatique par du code).
Heuuu, c'est quoi ce new String(String)?
String est immutable, tu peux (et même tu dois) utiliser la chaine
passée en argument à la place (this.title = "blabla";), il n'y a aucun
risque d'altération. Ce que tu as écrit est juste une perte de temps
machine et d'espace mémoire. C'est pas beau/bien :(
sam.
Je comprends, mais ça va me donner du mal pour synchroniser ces données
avec le JTree. Mais bon je vois quand-même un peu le travail qui me reste
à faire. Bien que la solution avec la JList soit quasiment finalisée, je
vais quand-même essayer avec le JTree car c'est bien mieux en terme
d'ergonomie.
Je pense que oui. Je vais être obligé d'utiliser la récursivité pour
parcourir mon arbre. De toutes façons, je ne vois pas comment faire
autrement et, de plus, ça ne me pose pas particulièrement de problème.
Ok. Merci.
Je vais voir ça. Je pense avoir compris, mais il faudra que je vois ça
une fois que j'en serai là.
Je ne pense pas car l'objet Date fournit un long qui est un nombre de
millisecondes. Pour planter le logiciel, il faudrait créer 2 Chapter dans
la même milliseconde. Ce sera impossible car le logiciel ne permettra pas
de générer des Date lui-même. Les champs Date ne seront créés que par
l'utilisateur à la main, donc impossible à faire planter le programme.
D'autres champs Date seront créés par le logiciel dans le cas de la copie
d'un chapitre en faisant un drag&drop en maintenant la touche Ctrl
appuyée. Donc, dans ce cas aussi ce n'est pas possible.
Ok. Merci du conseil. A+
jp
"C'est bon, ça ne devrait pas se produire !" ;) .
Les cas impossibles arrivent beaucoup trop souvent....
Ici la date est effectivement stockée en millisecondes. Le code est
fragile parce qu'un jour le problème va apparaître et tu vas oublier
que cette limitation existe ; et comme tu ne fais pas de vérification
que l'id est unique ça va déclencher n'importe quoi, perdre des
données, ... Dans le meilleur des cas ça plantera rapidement avec une
erreur, mais ce n'est pas du tout sûr, selon ce que tu bases sur ces
ids. Par exemple si tu indexes les chapitre sur leur id et qu'ils sont
plusieurs avec le même id, tu vas ignorer un chapitre dans l'index (et
les traitements qui se basent dessus).
Donc si un jour tu crées des chapitres par un bout de programme, tu
risques des ennuis. Par exemple une fonctionnalité qui créerait un
certain nombre de chapitres, en se basant sur des données quelque part.
Mais pire, ça peut très bien être le cas avec un simple utilisateur :
l'appli prend un peu trop de temps pour faire quelque chose, il ne s'en
aperçoit pas, il clique sur "Créer un chapitre", ça ne marche pas parce
que l'interface est figée, il reclique, le traitement en cours
se termine, les deux clics vont être traités très rapidement par
l'appli, en retard, l'un à la suite de l'autre : deux chapitres créés
peut-être dans la même milliseconde. Est-ce que tu vois ?
Une manière plus fiable de créer des ids serait de détecter le plus
grand id associé à un chapitre et de gérer un compteur via une méthode
synchronisée par exemple. Ce n'est pas très difficile.
Ce n'est sans doute pas très urgent, mais note de te repencher sur la
question pas trop tard, sinon tu vas oublier et ton appli risque de
casser un peu aléatoirement.
Fais des tests avec des outils automatisé genre Sikuli, et tu va voir
que dans ce cas ton outil créera 200 ou 300 chapitres à la seconde.
La classe java UUID fournit en principe un ID unique à chaque appel.
https://docs.oracle.com/javase/7/docs/api/java/util/UUID.html
sam.
Et si je fais ça?
package model;
import java.io.*;
import java.util.*;
public class Chapter implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private boolean is_edited;
private String title, body, notes, synopsis;
private Date key_date; //Clé unique basée sur la date de création
pour identifier un chapitre donné.
private List<Chapter> list;
public Chapter() {
//Inits
this.title = "Titre du chapitre";
this.notes = "Texte des notes";
this.synopsis = "Texte du synopsis";
this.body = "Texte principal";
this.key_date = newDate();
this.is_edited = false;
this.list = new ArrayList( null );
}
public Date newDate() {
Date d = new Date();
try {
//Thread.sleep(100); // suspendu pendant 100
millisecondes
wait(100);
} catch(InterruptedException e) {
System.out.println(e.getMessage());
}
return d;
}
public Date getDate() {
return key_date;
}
Etc ...
}
Au départ j'avais synchronisé la méthode newDate() mais je pense que ce
n'est pas la peine.
A+
Merci pour l'info. Mais mon programme ne pourra pas générer 300 chapitres
à la seconde car ils sont créés par l'utilisateur à l'aide d'un menu "new
chapter". La seconde manière d'en créer un est en faisant un drag&drop en
maintenant la touche Ctrl appuyée. Là aussi, pas moyen de créer plusieurs
chapitres dans la même milliseconde...
La remarque de ylur est bonne, c'est pourquoi j'ai rajouté quelques
lignes de code pour prévoir l'imprévisible. :)
A+