web-dev-qa-db-fra.com

Comment actualiser un JTree après avoir ajouté des nœuds au modèle sous-jacent?

Tout d’abord, laissez-moi vous dire que je n’utilise pas le DefaultTreeModel. J'implémente mon propre TreeModel, je ne peux donc pas utiliser le logiciel DefaultXXX. Le problème est le suivant: grâce à certaines méthodes addStuff () définies par mon modèle, j'ajoute des nœuds à la structure de données sous-jacente. Je préviens ensuite les écouteurs en appelant treeNodesChanged () à l'intérieur de la fonction addStuff () (je sais qu'il existe des méthodes treeNodesInserted mais c'est la même chose. Il notifie simplement les écouteurs avec une méthode différente). Maintenant, l'un des écouteurs est une classe statique dans mon formulaire principal et cet auditeur peut indiquer à JTree, qui est également contenu dans mon formulaire principal, de se rafraîchir. Comment dire à JTree de "recharger" tout ou partie de ses nœuds à partir du modèle?

UPDATE: Trouvé cette question que bien que pas exactement la même chose, cela donne la réponse que je veux.

MISE À JOUR 2: Mon problème n'était pas de savoir comment notifier le visualiseur (le JTree), mais plutôt de quelle manière le jtree devait être rechargé après la notification du modèle.

Tout d’abord, permettez-moi de dire que le seul moyen que je connaisse pour actualiser un arbre afin de refléter les modifications sous-jacentes est d’appeler updateUI () ou de réutiliser la méthode setModel (). Mon problème est essentiellement le suivant:

Supposons que TreeModelListener vient d’être averti (via l’API TreeModelListener) qu’une modification est survenue dans le modèle. Ok, et maintenant?

J'ai ce problème parce que le JTree n'implémente pas TreeModelListener. Ainsi, dans mon cas, l’auditeur est le conteneur de JTree, ou une classe interne implémentant l’auditeur, vivant sous le même conteneur que Jtree.

Supposons donc que je suis une implémentation TreeModelListener, vivant heureux dans un JForm avec mon frère JTree. Soudain, ma méthode treeNodesInserted (TreeModelEvent evt) est appelée. Qu'est-ce que je fais maintenant? Si j'appelle Jtree.updateUI () de l'intérieur de moi, la liste des écouteurs du modèle jette une exception ConcurrentModification. Puis-je appeler autre chose que updateUI?

J'ai essayé un certain nombre de choses, mais seul updateUI a rafraîchi le JTree. Donc je l'ai fait en dehors de l'auditeur. Depuis JForm, j'appelle simplement la méthode du modèle qui modifie la structure sous-jacente, puis j'appelle updateUI. TreeModelListener ne s'habitue pas.

UPDATE3: J'ai découvert qu'il existe des TreeModelListeners implicites enregistrés. Dans l'implémentation addTreeModelListener (écouteur TreeModelListener) de mon modèle, j'ai mis une ligne debug system.out:

System.out.println("listener added: " + listener.getClass().getCanonicalName());

et j'ai vu cette sortie de débogage juste au moment où j'ai exécuté jTree.setModel (model):

écouteur ajouté: javax.swing.JTree.TreeModelHandler

écouteur ajouté: javax.swing.plaf.basic.BasicTreeUI.Handler

La ConcurrentModificationException est due au fait qu'un appel à jtree.updateUI () enregistre de nouveau l'écouteur (uniquement le plaf, pas les deux); il est donc lancé lorsque j'appelle updateUI dans une boucle de notification d'écouteur. Le seul moyen d'actualiser l'arborescence consiste à le faire en dehors de TreeModelListener. Des commentaires ou des idées pour une meilleure solution? Est-ce que je manque quelque chose?

14
Paralife

J'ai rencontré le même "problème": appeler treeNodesInserted() n'a pas amené mon JTree à mettre à jour son contenu.

Mais le problème était ailleurs: j'ai utilisé le mauvais constructeur pour TreeModelEvent. Je pensais que je pouvais créer TreeModelEvent pour treeNodesInserted() comme ça:

//-- Wrong!!
TreePath path_to_inserted_item = /*....*/ ;
TreeModelEvent tme = new TreeModelEvent(my_source, path_to_inserted_item);

Ça ne marche pas.

Comme indiqué dans TreeModelEvent docs , ce constructeur n’est nécessaire que pour treeStructureChanged(). Mais pour treeNodesInserted(), treeNodesRemoved(), treeNodesChanged(), nous devrions utiliser un autre constructeur:

TreePath path_to_parent_of_inserted_items = /*....*/ ;
int[] indices_of_inserted_items = /*....*/ ;
Object[] inserted_items = /*....*/ ;
TreeModelEvent tme = new TreeModelEvent(
      my_source,
      path_to_parent_of_inserted_items,
      indices_of_inserted_items,
      inserted_items
   );

Ce code fonctionne et JTree met à jour son contenu correctement.


UPD: En fait, la documentation n’est pas claire sur l’utilisation de ces TreeModelEvents, et en particulier avec JTree, aussi je veux parler de certaines questions qui m’ont été posées lorsque j’ai essayé de comprendre comment gérer tout cela.

Premièrement, comme Paralife l'a noté dans son commentaire, les cas où des noeuds sont insérés/modifiés/supprimés ou lorsque la structure de l'arbre est modifiée ne sont pas orthogonaux. Alors,

Question n ° 1: quand devrions-nous utiliser treeNodesInserted()/Changed()/Removed(), et quand treeStructureChanged()?

Réponse: treeNodesInserted()/Changed()/Removed() peut être utilisé si tous les nœuds affectés ont le même parent. Sinon, vous pouvez appeler plusieurs fois ces méthodes ou simplement appeler une fois treeStructureChanged() (et lui transmettre le nœud racine des nœuds affectés). Donc, treeStructureChanged() est une sorte de méthode universelle, alors que treeNodesInserted()/Changed()/Removed() est plus spécifique.

Question n ° 2: Dans la mesure où treeStructureChanged() est une méthode universelle, pourquoi dois-je gérer ces fonctions treeNodesInserted()/Changed()/Removed()? Il suffit d'appeler à treeStructureChanged() semble être plus facile.

Réponse: Si vous utilisez JTree pour afficher le contenu de votre arborescence, alors ce qui suit pourrait vous surprendre (comme ce fut le cas pour moi): lorsque vous appelez treeStructureChanged(), alors JTree ne conserve pas son état développé de sous-nœuds! Prenons l'exemple, voici le contenu de notre JTree maintenant:

[A]
 |-[B]
 |-[C]
 |  |-[E]
 |  |  |-[G]
 |  |  |-[H]
 |  |-[F]
 |     |-[I]
 |     |-[J]
 |     |-[K]
 |-[D]

Ensuite, vous apportez des modifications à C (disons, renommez-le en C2) et vous appelez treeStructureChanged() pour cela:

  myTreeModel.treeStructureChanged(
        new TreeModelEvent(
           this,
           new Object[] { myNodeA, myNodeC } // Path to changed node
           )
        );

Ensuite, les nœuds E et F seront réduits! Et votre JTree ressemblera à ça:

[A]
 |-[B]
 |-[C2]
 |  +-[E]
 |  +-[F]
 |-[D]

Pour éviter cela, vous devriez utiliser treeNodesChanged(), comme ceci:

  myTreeModel.treeNodesChanged(
        new TreeModelEvent(
           this,
           new Object[] { myNodeA }, // Path to the _parent_ of changed item
           new int[] { 1 },          // Indexes of changed nodes
           new Object[] { myNodeC }, // Objects represents changed nodes
                                     //    (Note: old ones!!! 
                                     //     I.e. not "C2", but "C",
                                     //     in this example)
           )
        );

Ensuite, l'état en expansion sera conservé.


J'espère que ce post sera utile pour quelqu'un.

17
Dmitry Frank

J'ai toujours trouvé le TreeModel un peu déroutant. Je suis d'accord avec l'affirmation ci-dessus selon laquelle le modèle doit notifier la vue lorsqu'un changement est effectué afin que la vue puisse se repeindre. Cependant, cela ne semble pas être le cas lors de l'utilisation de DefaultTreeModel. Je trouve que vous devez invoquer la méthode reload () après la mise à jour du modèle. Quelque chose comme:

DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot();
root.add(new DefaultMutableTreeNode("another_child"));
model.reload(root);
13
camickr

J'ai également trouvé que l'implémentation de TreeModel était un peu déroutant lorsque l'arborescence comprenait plus que des dossiers (racine) et des fichiers (enfants). J'ai donc utilisé le modèle DefaultTreeModel. Cela fonctionne pour moi. La méthode crée ou actualise le JTree. J'espère que cela t'aides.

    public void constructTree() {       

        DefaultMutableTreeNode root =
                new DefaultMutableTreeNode(UbaEnv.DB_CONFIG);
        DefaultMutableTreeNode child = null;

        HashMap<String, DbConfig> dbConfigs = env.getDbConfigs();
        Iterator it = dbConfigs.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry pair = (Map.Entry) it.next();
            child = new DefaultMutableTreeNode(pair.getKey());

            child.add(new DefaultMutableTreeNode(UbaEnv.PROP));
            child.add(new DefaultMutableTreeNode(UbaEnv.SQL));
            root.add(child);
        }

        if (tree == null) {
            tree = new JTree(new DefaultTreeModel(root));
            tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
            tree.addTreeSelectionListener(new TreeListener());
        } else {
            tree.setModel(new DefaultTreeModel(root));
        }
}
5
Ariel

Hier, j'ai lutté pour résoudre le même problème.L'exigence était d'insérer et de supprimer des nœuds à la volée, sans réduire les nœuds d'arborescence développés.J'ai parcouru le Web et trouvé un tas de solutions possibles, jusqu'à ce que je trébuche sur ce fil. . Ensuite, j'ai appliqué la réponse de 'Dmitry Frank' avec le TreeModelEventname__. J'étais un peu perplexe. Pourquoi l'insertion ou la suppression d'un nœud simple et le reste de JTreeinaltéré! Sont enfin si compliqués! Enfin, les exemples plain Vanilla dans Java2s m'ont aidé à trouver la solution probablement la plus simple du tout. (Ni un appel tel que: nodeStructureChangedname__, nodeChangedname__, nodesWereRemovedname__, nodesWereInsertedname__, etc., ni un TreeModelEventcomme suggéré par 'Dmitry Frank' était requis.)


Voici ma solution:

// Remove a node
treeModel.removeNodeFromParent(myNode);

// Insert a node (Actually this is a reinsert, since I used a Map called `droppedNodes` to keep the previously removed nodes. So I don't need to reload the data items of those nodes.)
MyNode root = treeModel.getRootNode();
treeModel.insertNodeInto(droppedNodes.get(nodeName), root, root.getChildCount());

Rester simple ;)

4
My-Name-Is

Votre TreeModel est censé déclencher TreeModelEvents lorsqu'il change et le JTree observe votre modèle via un TreeModelListener pour s'actualiser lorsque votre modèle change.

Donc, si vous implémentez correctement le support TreeModelListener , vous n'avez pas besoin d'observer le modèle et d'informer le JTree, car il le fait déjà lui-même. D'une perspective MVC, le JTree est votre vue/contrôleur, et le TreeModel est votre modèle (ou plutôt: l'adaptateur de modèle), ce qui est observable.

Vous pouvez essayer de forcer JTree à mettre à jour son état visuel en appelant repaint (), mais je vous recommande de ne pas le faire, car son fonctionnement n'est pas garanti. Lorsque vous ne savez pas comment procéder pour une notification fine TreeModelListener, utilisez TreeModelListener.treeStructureChanged (..) pour notifier une mise à jour du modèle entier (avertissement: peut entraîner la perte de sélections et d'états d'expansion de nœud).

3
Peter Walser

MISE À JOUR FINALE: Trouvé le problème et l'a résolu: Les étapes suivantes résolvent le problème, (mais voir la réponse acceptée pour une meilleure solution et une explication détaillée du problème) :

  1. Les écouteurs implicites inscrits suffisent pour faire le travail. Pas besoin d'implémenter mon propre auditeur.
  2. Lorsque vous ajoutez des nœuds et appelez treeNodesInserted(), cela ne fonctionne pas (JTree non mis à jour). Mais cela fonctionne avec l'appel treeStructureChanged().

Apparemment, les écouteurs implicites actualisent l’arbre en interne de la manière que je veux, mais seulement dans l’implémentation de leur méthode treeStructureChanged(). Il serait bon que JTree fournisse cet "algorithme" en tant que fonction pour pouvoir être appelé manuellement.

2
Paralife

Il semble possible de réinitialiser l’ensemble de l’arbre en définissant le modèle sur null: P. Ex.

        TreePath path = tree.getSelectionPath();
        model.doChanges(); // do something with model
        tree.setModel(null);
        tree.setModel(model);
        tree.setSelectionPath(path);
        tree.expandPath(path);

La mise à jour des arbres fonctionne pour moi

kr Achim

0
herberlin

Par exemple: Jtree Refresh Dynamically

package package_name;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;

public final class homepage extends javax.swing.JFrame implements ActionListener
{
     DefaultTreeModel model;
     DefaultMutableTreeNode root;
     File currentDir = new File("database");

public homepage() throws InterruptedException {
        initComponents();
    //------full screen window
        this.setExtendedState(MAXIMIZED_BOTH);
    //====================Jtree Added Details..   

    //result is the variable name for jtree
        model =(DefaultTreeModel) treedbnm.getModel();
    //gets the root of the current model used only once at the starting
        root=(DefaultMutableTreeNode) model.getRoot();
    //function called   
        dbcreate_panel1();
        Display_DirectoryInfo(currentDir,root);

}//End homepage() Constructor
public void Display_DirectoryInfo(File dir,DefaultMutableTreeNode tmp_root) throws InterruptedException 
{..........   
}
public void dbcreate_panel1()
{
    //------------------Jtree Refresh Dynamically-------------------//
             root.removeFromParent();
             root.removeAllChildren();
             model.reload();
             Display_DirectoryInfo(currentDir,root);
}
}//End homepage
0
Chetan Bhagat