How can I refresh a JTree after adding some nodes to the underlying model?
Asked Answered
C

8

19

First of all, let me say that I dont use the DefaultTreeModel. I implement my own TreeModel, so i cant use the DefaultXXX stuff. The problem is this: Through some addStuff() methods which my model defines I add nodes to the underlying data structure. I then notify listeners by calling treeNodesChanged() inside the addStuff() function (I Know there are treeNodesInserted methods but it is the same thing. It just notifies listeners with a different method). Now, one of the listeners is a static class in my main form and this listener can tell the JTree, which is also contained in my main form, to refresh itself. How do I tell the JTree to "reload" some or all of its nodes from the model?

UPDATE: Found this question that although not exactly the same, it gives the answer I want.

UPDATE 2: My problem was not how to notify the viewer (the JTree), but rather in what way should the jtree be reloaded after the notification from the model.

First of all let me say that the only way i know to refresh a tree to reflect underlying changes, is to call the updateUI(), or reuse the setModel() method. Essentially, my problem is this:

Suppose the TreeModelListener has just been notified (through the TreeModelListener API) that a change has occured in the model. Ok, what now?

I have this problem because the JTree does not implement TreeModelListener. So the listener, in my situation, is the JTree's container, or an internal class implementing the Listener, living under the same container as Jtree.

So suppose I am a TreeModelListener implementation, living happily in a JForm with my brother JTree. Suddenly my method treeNodesInserted(TreeModelEvent evt) is called. What do I do now? If i call Jtree.updateUI() from inside me, then the model's listeners List throws ConcurrentModification Exception. Can I call something else other than updateUI?

I tried a number of things, but only updateUI refreshed the JTree. So I did it outside of the listener. From the JForm, I just call the model's method that alters the undrlying structure, and then i call updateUI. No TreeModelListener gets used.

UPDATE3: I found out that there are implicit TreeModelListeners registered. In my model's addTreeModelListener(TreeModelListener listener) implementation i put a debug system.out line:

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

and I saw this debug output just when I executed jTree.setModel(model):

listener added: javax.swing.JTree.TreeModelHandler

listener added: javax.swing.plaf.basic.BasicTreeUI.Handler

The ConcurrentModificationException is caused because a call to jtree.updateUI() re registers the listener (only the plaf, not both) so it is thrown when i call updateUI inside a listener notification loop. The only way now to refresh the tree is do it outside of TreeModelListener. Any comments or ideas for a better solution? Am I missing something?

Catchfly answered 16/12, 2009 at 10:50 Comment(5)
If you found your answer, you can close the questionNisus
And it would be nice to post the solution you used.Rota
Since my change originates from a method in a JForm (which contains the model and the Jtree), I didnt use the TreeModelListener at all. I just call the model's addStuff() which inserts nodes in the model's structure and then I manually call updateUI() on the tree. If i implemented updateUI() in a TreeModelListener implementation in the JForm, then I got concurrent modification exception on the listener's list. I didnt investigate much and I am sure it is not an elegant solution, but I am currently prototyping and dont care for the internal implementations at the moment.Catchfly
@Sven, I voted for closing it. Can I do it immediately, without voting?Catchfly
@Sven, why don't you explain to us how you expect just him to close the question.Obstetrician
F
24

I faced the same "problem": calling treeNodesInserted() did not cause my JTree to update its contents.

But the problem was in other place: I used wrong constructor for TreeModelEvent. I thought that I can create TreeModelEvent for treeNodesInserted() like that:

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

This doesn't work.

As stated in TreeModelEvent docs, this constructor is only needed for treeStructureChanged(). But for treeNodesInserted(), treeNodesRemoved(), treeNodesChanged() we should use another constructor:

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

This code works, and JTree updates its contents properly.


UPD: Actually, docs are unclear about using these TreeModelEvents, and especially with JTree, so, I want to tell about some questions that came to me when I tried to figure out how to deal with all this stuff.

Firstly, as Paralife noted it his comment, cases when nodes are inserted/changed/removed, or when tree structure is changed, aren't orthogonal. So,

Question #1: when should we use treeNodesInserted()/Changed()/Removed(), and when treeStructureChanged()?

Answer: treeNodesInserted()/Changed()/Removed() can be used if only all the affected nodes have the same parent. Otherwise you may make several calls to these methods, or just call treeStructureChanged() once (and pass the root node of affected nodes to it). So, treeStructureChanged() is a kind of universal way, while treeNodesInserted()/Changed()/Removed() are more specific.

Question #2: As far as treeStructureChanged() is a universal way, why do I need to deal with these treeNodesInserted()/Changed()/Removed()? Just call to treeStructureChanged() seems to be easier.

Answer: If you use JTree to display contents of your tree, then the following thing might be a surprize for you (as it was for me) : when you call treeStructureChanged(), then JTree doesn't keep expand state of sub-nodes! Consider the example, here's contents of our JTree now:

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

Then you make some changes to C (say, rename it to C2), and you call treeStructureChanged() for that:

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

Then, nodes E and F will be collapsed! And your JTree will look like that:

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

To avoid that, you should use treeNodesChanged(), like that:

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

Then, expanding state will be kept.


I hope this post will be useful for somebody.

Fairground answered 18/1, 2013 at 21:16 Comment(5)
Ok, 4 years after the question I cant verify this. In fact, I havent touched java stuff since then.But ,anyway, I read the docs you mention and indeed, the docs differentiate the constructor based on if the structure has changed or nodes have been inserted/removed. But these two cases are not orthogonal. Doesnt the tree structure changes when a node gets inserted/removed? The description of the constructor I use says: "Used to create an event when the node structure has changed in some way". Anyway I will mark it as correct. Thanks.Catchfly
@Paralife, yes, these cases aren't orthogonal. Actually, docs are unclear about some things, so, I updated my answer (added explanation of things that were unclear personally for me), check it if you want. Thanks for marking my answer, by the way =)Fairground
Hi Dmirty!! I found your answer very useful..Big thanks..i wanted to know that what code/line should i write inside treeNodesChanged() to actually update the UI..How do i use the TreeModelEvent object that i got to update that particular UI part of the tree?Bloodthirsty
@ayush, if you use JTree to display your tree then you should just give your TreeModel instance to the JTree, and then JTree will add its listener to your TreeModel, and will be updated just when you call myTreeModel.treeNodesChanged(...). If it isn't updated, it means that you give wrong arguments to treeNodesChanged(). Make sure you give old (unchanged) objects as last argument.Fairground
If, after reading this through and following all the steps you still experience weird behavior of your JTree, check carefully your model's methods implementing the TreeModel interface. In my case, isLeaf() returned the wrong value in some case causing the JTree to go blank after inserting a node. This happened bacause BasicTreeUI responsible for handling treeNodesInserted()/Changed()/Removed() relies on user's TreeModel implementation of getChild(), getChildCount(), isLeaf() etc.Paramorphism
R
18

I've always found the TreeModel a bit confusing. I agree with the above statement that the model should notify the view when a change is made so the view can repaint itself. However, this does not seem to be the case when using the DefaultTreeModel. I find you need to invoke the reload() method after updating the model. Something like:

DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot();
root.add(new DefaultMutableTreeNode("another_child"));
model.reload(root);
Rota answered 16/12, 2009 at 16:57 Comment(2)
DefaultTreeXXXX API is not an option. I implement my own tree model.Catchfly
actually, I'm pretty you wouldn't use the structuryChanged hammer for a simple insert ;-) Which is supported in DefaultTreeModel api - simply use that instead of changing the underlying dataStrucure (aka: node wiring) under the model's feetHogle
O
7

Yesterday I struggeled around to fix the same issue. The requirement was to insert and remove nodes on the fly, without collapsing the the expanded tree nodes. I browsed the web and found a bunch of possible solutions, until I stumbled over this thread. Then I applied the anwser from 'Dmitry Frank' with the TreeModelEvent. I was a bit confused, why it is such a big issue to just insert or remove a simple node and let the rest of the JTree untouched! Finally the plain vanilla examples from java2s helped me to find the probably simplest solution at all. (Neither a call like: nodeStructureChanged, nodeChanged, nodesWereRemoved, nodesWereInserted, etc. nor a TreeModelEvent like suggested by 'Dmitry Frank' was required.)


Here's my 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());

Keep it simple ;)

Opah answered 15/2, 2014 at 11:11 Comment(2)
Not sure why this doesn't have more upvotes... I just spent 30 minutes searching for this answer and am now confused how this is the only one for (what appears to me as) such a basic use-case for JTree.Benempt
@VadimHagedorn it doesn't have more upvotes because it depends on DefaultTreeModel and the original questioner explicitly states they cannot use it, and are using a custom model.Roush
T
5

I also found implementing TreeModel a bit confusing when the tree consist of more than just Folders(root) and Files(child), so I've used the DefaultTreeModel. This works for me. The method creates or refreshes the JTree. Hope this helps.

    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));
        }
}
Tiphani answered 3/2, 2010 at 3:9 Comment(0)
C
3

Your TreeModel is supposed to fire TreeModelEvents when it changes, and the JTree observes your model though a TreeModelListener to refresh itself when your model changes.

So if you implement the TreeModelListener support correctly, you do not need to observe the model and inform the JTree, as it already does so itself. From an MVC perspecive, the JTree is your View/Controller, and the TreeModel is your Model (or rather: model adapter), which is observable.

You could try force the JTree to update its visual state by calling repaint(), but I would recommend not doing so as it's not guaranteed to work. When you're unsure of how to do a fine-granular notification to a TreeModelListener, use TreeModelListener.treeStructureChanged(..) to notify a 'whole model' update (warning: can cause selections and node expansion states to be lost).

Chouest answered 16/12, 2009 at 11:6 Comment(3)
My problem was not how to notify the viewer (the JTree), but rather in what way should the jtree reload itself after the notification from the model.Catchfly
And also if there is possible for the jtree to reload only a portion.Catchfly
One does not need to implement TreeModelListener, since JTree has its own listener. One should call treeNodesInserted() with correct TreeModelEvent object. See my answer for details.Fairground
C
2

FINAL UPDATE: Found the problem and solved it: The following steps solve the problem,(but see the accepted answer for a better solution and a deep explanation of the problem):

  1. The implicit listeners registered are enough to do the job. No need to implement my own listener.
  2. When adding nodes and calling treeNodesInserted() it doesn't work (JTree not updated). But It works with calling treeStructureChanged().

Apparently the implicit listeners are internally refreshing the tree the way i want, but only in their treeStructureChanged() method implementation. It would be good for JTree to provide this "algorithm" as a function in order to be able to be called manually.

Catchfly answered 17/12, 2009 at 10:34 Comment(4)
All of Swing is based on the idea that a model tells its view to refresh itself, and the view figures it out. You're looking for an API that breaks this.Bronder
I though that the principle was not "the model tells the view to refresh" but rather "the model tells the view it has changed" and the view should be free to act however it wants.. Maybe the view doesnt want to refresh. Maybe It wants to continue getting notified but i think it is a bit restrictive to have the model implicitly tell the view what to do after it gets notified. In the particular situation it would not break anything and it would be best if the jtree just provided a refreshPath() method, and I could just implement my listener and have it call the method when ever I wanted.Catchfly
The way it should work according to the MVC approach is that the model is monitored for changes by an Observer or Listener and the changes are passed on to the view. Usually this is done by a controller class if it acts as mediator.Kopple
treeNodesInserted() does work (JTree is updated), you just need to use proper TreeModelEvent. Check my answer below for details.Fairground
H
1

It seems to be possible to reinitialize the whole tree by setting the model to null: e.g.

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

Tree update works for me

kr Achim

Haleakala answered 5/7, 2017 at 20:54 Comment(0)
K
0

For example: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
Keffer answered 20/8, 2016 at 5:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.