Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

JTree et repr

24 réponses
Avatar
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.

10 réponses

1 2 3
Avatar
Yliur
Le 19 Feb 2018 01:28:50 GMT
jp a écrit :
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é?

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 ?
Avatar
jp
Le Mon, 19 Feb 2018 08:29:12 +0100, Yliur a écrit :
Le 19 Feb 2018 01:28:50 GMT jp a écrit :
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é?

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
...

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

Oui mais ça a l'air plus compliqué que les List.
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 ?

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!
Avatar
Yliur
Le 19 Feb 2018 16:49:50 GMT
jp a écrit :
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
...

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.

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

Oui mais ça a l'air plus compliqué que les List.

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é ?
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 ?

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...

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.
Voici ma classe Chapter:
[...]

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).
Avatar
Samuel DEVULDER
Le 19/02/2018 à 17:49, jp a écrit :
this.title = new String( "Titre du chapitre" );

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.
Avatar
jp
Le Mon, 19 Feb 2018 18:30:01 +0100, Yliur a écrit :
Le 19 Feb 2018 16:49:50 GMT jp a écrit :
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).

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.
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é ?

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.
Pardon, il s'agit de la classe TreeModel, mentionnée dans la doc de
JTree.

Ok. Merci.
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.

Je vais voir ça. Je pense avoir compris, mais il faudra que je vois ça
une fois que j'en serai là.
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).

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.
Avatar
jp
Le Mon, 19 Feb 2018 19:38:02 +0100, Samuel DEVULDER a écrit :
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.

Ok. Merci du conseil. A+
Avatar
Yliur
Le 21 Feb 2018 00:50:56 GMT
jp a écrit :
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.

"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.
Avatar
Samuel DEVULDER
Le 21/02/2018 à 01:50, jp a écrit :
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.

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.
Avatar
jp
Le Wed, 21 Feb 2018 15:16:41 +0100, Yliur a écrit :
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.

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+
Avatar
jp
Le Wed, 21 Feb 2018 23:23:28 +0100, Samuel DEVULDER a écrit :
Le 21/02/2018 à 01:50, jp a écrit :
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.

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.

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+
1 2 3