JTree update nodes without collapsing
Asked Answered
A

4

12

I have a Java SE 7 application that needs to have the JTree nodes updated. From the tutorial given by Oracle using this thread, there's no given hint on how I could update the label (displayed text of the node on the Tree) on code. Currently I am using DefaultTreeModel as the model of my JTree and DefaultMutableTreeNode as the nodes of the said Tree.

To further detail about the application I am working on, I am developing a chat facility having the contact(s) displayed with their availability status (whether Online, Offline, etc.) per account.

The question is, how can I update the displayed text of a particular node without (at most) removing it from it's parent and adding it on it's designated index. Like a DefaultMutableTreeNode.setText("<new label>")?


UPDATE : January 20, 2013

Redefined the question for clarifications.

Ascender answered 3/9, 2012 at 14:27 Comment(8)
You should only update your JTree from the UI thread - if you do that there is no need to synchronize anything.Lenlena
Would it still be not a problem if the JTree maybe updated multiple times in <60s?Ascender
If everything is executed on the same thread (which should be the case), all operations will be sequential. Worst thing that could happen is that it could be slow if it gets updated too often, but updates will run one after the other.Lenlena
True, but when I invoke DefaultTreeModel.reload() to the model of my JTree the nodes collapses, how can I update a particular node without having any of the other nodes with childs to collapse?Ascender
Maybe check this post and #10435633Lenlena
Sorry for the delay of response. The commented thread didn't solve this problem. I have updated my question for further clarifications.Ascender
Still don't understand why updating a node might be a problem - what/why/how exactly drives the update?Indies
I have faced annoying unwanted collapse of nodes when I call treeStructureChanged() instead of treeNodesChanged(). See this answer for details, especially the last part of it, with examples of changing the node. But, I didn't work with DefaultXXX stuff. Anyway, show us your code.Cowbell
P
8

May this simple and executable program help you to resolve your issue.

public class JTreeDemo  extends JPanel
    implements Runnable {

private JTree tree;
private DefaultTreeModel treeModel ;
private Random rnd = new Random();
private List<User> userList;
public JTreeDemo() {
    super( );

    //Create the nodes.
    DefaultMutableTreeNode top =
        new DefaultMutableTreeNode("Users");
    treeModel = new DefaultTreeModel(top);
    createNodes(top);

    //Create a tree that allows one selection at a time.
    tree = new JTree(treeModel);
    tree.getSelectionModel().setSelectionMode
            (TreeSelectionModel.SINGLE_TREE_SELECTION);

    //Create the scroll pane and add the tree to it. 
    JScrollPane treeView = new JScrollPane(tree);


    //Add the split pane to this panel.
    add(treeView);
}

public String getRandomStatus() {
    int nextInt = rnd.nextInt(100);
    if( nextInt%2==0) {
        return "Online";
    } else {
        return "Offline";
    }
}
@Override
public void run() {
     while(true) {
        try {   
          Thread.sleep(1000);
          int nextInt = rnd.nextInt(10);
          User user = userList.get(nextInt);
          user.setStatus(getRandomStatus());
          treeModel.nodeChanged(user);
        } catch (InterruptedException ex) {
            // handle it if necessary
        }
     }
}

private class User extends DefaultMutableTreeNode {
    public String userName;
    public String status;

    public User(String name) {
        userName = name;

    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getStatus() {
        return status;
    }

    @Override
    public String toString() {
        String color = status.equals("Online") ? "Green" : "Red";
        return "<html><b color='"+color+"'>"+
                userName +"-"+status +"</b></html>";
    }

}


private void createNodes(DefaultMutableTreeNode top) {
    userList = new ArrayList() ;
    for(int i=0;i<10;i++) {
        User u1 = new User("User " + (i+1));
        u1.setStatus("Online");
         top.add(u1);
         userList.add(u1);
    }
}

private static void createAndShowGUI() {

    JFrame frame = new JFrame("TreeDemo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    //Add content to the window.
    JTreeDemo jTreeDemo = new JTreeDemo();
    frame.add(jTreeDemo);
    frame.pack();
    frame.setVisible(true);
    // update status randomly
    Thread thread = new Thread(jTreeDemo);
    thread.start();
}

 public static void main(String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            createAndShowGUI();
        }
    });
 }
}

I've added a Thread to update Status randomly, hope you can modify base on your need.

Output :
enter image description here


Edit:
1. Based on suggestion I've removed reload(node) and added tree model reload.

Polytechnic answered 20/1, 2013 at 10:58 Comment(2)
-1 a) updating the model off the EDT b) reload instead of triggering a changed c) (minor issue) visual decoration inside the node - don't override toString for view reasons, that's the task of the rendererIndies
@Indies thanks for your points. I've written up to my knowledge that is why i mentioned that my answer help you to resolve issue. May I know what is EDT ?Polytechnic
L
10

Perhaps if you use 'nodeChanged()' instead of 'reload()' you will get the effect you desire.

There are a bunch of methods on the DefaultTreeModel class that cause various parts of the tree to be changed and redrawn. There are also other methods on DefaultTreeModel that only cause redrawing to take place.

You mentioned 'reload(node)' and commented that it causes the tree to collapse when you call it. 'reload' causes the entire sub-tree to be completely redrawn starting at that node. (But if that node isn't visible, it changes nothing.) That is called a 'structure change'.

'insertNodeInto()' and 'removeNodeFromParent()' modify the tree structure by adding or removing the node and then redrawing.

I think 'nodeChanged()' is the one you need since it just notifies the model that something changed in the node that will cause it to display differently. Perhaps the displayable text is now different than it was. Perhaps you changed the user object in the node. That's when you call 'nodeChanged()' on a node.

You should try 'nodeChanged()' in place of the 'reload()' call in your own code that was collapsing and in the example program vels4j provided. This might take care of the problem.

Note that there are also two other families of methods on the DefaultTreeModel that are used in other cases:

These methods work with the tree nodes and use the tree path to define where the change took place. They do not change the data structures underlying the tree but notify the model that something changed so it can notify the listeners that actually redraw things or otherwise respond to changes.

nodesWereInserted() nodesWereRemovde() nodesChanged() nodeStructureChanged()

There are also a set of fire...() methods that are used internally to the DefaultTreeModel and any sub-classes you may create. They merely notify any listeners that something changed. Notice that they are protected.

Latashalatashia answered 21/1, 2013 at 19:13 Comment(3)
+1 DefaultTreeModel#nodeChanged(javax.swing.tree.TreeNode) is the way to go.Dominations
Can you provide an SSCCE as provided by @vels4j for me to accept your answer.Ascender
You can take @vels4j code and change the reload() call to nodeChanged(). I don't think anything further is needed. I hope there is a way to split the credit as he/she did most of the work to give you your answer. I just added an improvement to keep it from collapsing and thinking there was a structure change.Latashalatashia
P
8

May this simple and executable program help you to resolve your issue.

public class JTreeDemo  extends JPanel
    implements Runnable {

private JTree tree;
private DefaultTreeModel treeModel ;
private Random rnd = new Random();
private List<User> userList;
public JTreeDemo() {
    super( );

    //Create the nodes.
    DefaultMutableTreeNode top =
        new DefaultMutableTreeNode("Users");
    treeModel = new DefaultTreeModel(top);
    createNodes(top);

    //Create a tree that allows one selection at a time.
    tree = new JTree(treeModel);
    tree.getSelectionModel().setSelectionMode
            (TreeSelectionModel.SINGLE_TREE_SELECTION);

    //Create the scroll pane and add the tree to it. 
    JScrollPane treeView = new JScrollPane(tree);


    //Add the split pane to this panel.
    add(treeView);
}

public String getRandomStatus() {
    int nextInt = rnd.nextInt(100);
    if( nextInt%2==0) {
        return "Online";
    } else {
        return "Offline";
    }
}
@Override
public void run() {
     while(true) {
        try {   
          Thread.sleep(1000);
          int nextInt = rnd.nextInt(10);
          User user = userList.get(nextInt);
          user.setStatus(getRandomStatus());
          treeModel.nodeChanged(user);
        } catch (InterruptedException ex) {
            // handle it if necessary
        }
     }
}

private class User extends DefaultMutableTreeNode {
    public String userName;
    public String status;

    public User(String name) {
        userName = name;

    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getStatus() {
        return status;
    }

    @Override
    public String toString() {
        String color = status.equals("Online") ? "Green" : "Red";
        return "<html><b color='"+color+"'>"+
                userName +"-"+status +"</b></html>";
    }

}


private void createNodes(DefaultMutableTreeNode top) {
    userList = new ArrayList() ;
    for(int i=0;i<10;i++) {
        User u1 = new User("User " + (i+1));
        u1.setStatus("Online");
         top.add(u1);
         userList.add(u1);
    }
}

private static void createAndShowGUI() {

    JFrame frame = new JFrame("TreeDemo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    //Add content to the window.
    JTreeDemo jTreeDemo = new JTreeDemo();
    frame.add(jTreeDemo);
    frame.pack();
    frame.setVisible(true);
    // update status randomly
    Thread thread = new Thread(jTreeDemo);
    thread.start();
}

 public static void main(String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            createAndShowGUI();
        }
    });
 }
}

I've added a Thread to update Status randomly, hope you can modify base on your need.

Output :
enter image description here


Edit:
1. Based on suggestion I've removed reload(node) and added tree model reload.

Polytechnic answered 20/1, 2013 at 10:58 Comment(2)
-1 a) updating the model off the EDT b) reload instead of triggering a changed c) (minor issue) visual decoration inside the node - don't override toString for view reasons, that's the task of the rendererIndies
@Indies thanks for your points. I've written up to my knowledge that is why i mentioned that my answer help you to resolve issue. May I know what is EDT ?Polytechnic
W
0

It's easy if nodes contains objects which are unique in the tree and have implemented method equals and hashCode (for example you show strings or object with unique ID from database). First of all you iterate over all expanded nodes and save objects from the nodes in a set. Then you perform update of the model. After update you iterate over all nodes and if they are in the set you expand the node in the tree.
If nodes are not unique - you need to save in the set the complete tree path (for example as list) and check it after update to expand the nodes.
If objects has neither equals nor hashCode (both these methods must be implemented) - this variant cannot be used.

Weakfish answered 23/1, 2013 at 18:58 Comment(1)
True, but it may take some CPU time since we are talking of iterating through the nodes everytime an update is to be made.Ascender
D
0

Just for the record (I voted for Lee Meador), DefaultTreeModel#nodeChanged(javax.swing.tree.TreeNode) is the way to go:

public class TestFrame extends JFrame {

    public TestFrame() {
        //create gui with simple jtree (and DefaultTreeModel)
        JButton changeBtn = new JButton();
        final JTree jTree = new JTree();
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        changeBtn.setText("update selected node");
        getContentPane().add(changeBtn, java.awt.BorderLayout.PAGE_END);
        DefaultMutableTreeNode treeNode1 = new DefaultMutableTreeNode("root");
        DefaultMutableTreeNode treeNode2 = new DefaultMutableTreeNode("blue");
        treeNode1.add(treeNode2);
        treeNode2 = new DefaultMutableTreeNode("violet");
        DefaultMutableTreeNode treeNode3 = new DefaultMutableTreeNode("red");
        treeNode2.add(treeNode3);
        treeNode3 = new DefaultMutableTreeNode("yellow");
        treeNode2.add(treeNode3);
        treeNode1.add(treeNode2);
        jTree.setModel(new DefaultTreeModel(treeNode1));
        getContentPane().add(jTree, BorderLayout.CENTER);
        pack();
        //add listener to button, to change selected node on button click
        changeBtn.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                DefaultMutableTreeNode dmt = (DefaultMutableTreeNode)jTree.getSelectionPath().getLastPathComponent();
                //update content/representation of selected node
                dmt.setUserObject("My update: " + new Date());
                //nodeChanged
                ((DefaultTreeModel) jTree.getModel()).nodeChanged(dmt);
            }
        });
    }

    public static void main(String args[]) {

        EventQueue.invokeLater(new Runnable() {
            public void run() {
                new TestFrame().setVisible(true);
            }
        });
    }
}
Dominations answered 27/1, 2013 at 17:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.