Search code examples
javajtree

Java JTree's ui refresh after removing node from parent


when I remove a node from a Jtree, sometimes other nodes's jlabels are not refreshed and the text is cut off therefore displaying three dots "..." because the jlabel has the width of the previous node that was removed and the UI didn't refreshed as requested in the code.

I believe I'm managing nodes properly and refreshing the UI properly, but I guess either I'm missing something in my code or I am completely doing it wrong. I'm hoping someone can help me.

Here is an example of my JTree:

RootNode
    ParentNodeA
        ChildNodeA1
        ChildNodeA2
    ParentNodeB
        ChildNodeB1_hithere
        ChildNodeB2_imlost
        ChildNodeB3_whoisthere
        ChildNodeB4_dude

Say I want to add a node "ChildNodeA3" to ParentNodeA. I call the function addNodeToTree() by providing the JTree object and a string array with the path I want the new node to go, ie: [RootNode, ParentNodeA].

When I want to remove a node, say "ChildNodeB2_imlost", I call the function removeNodeFromTree(tree, [RootNode, ParentNodeB, ChildNodeB2_imlost]) which I provide the jtree object and a string array with the complete path to the node that is to be removed.

When I remove the node, other nodes jlabels UI is not refreshed in their new position and still hold their original width at their last position and now doesn't wrap around the text of their new position, therefore showing the three dots.

In the code below, I'm validating the path and expanding parent nodes as necessary in order to find the node to be removed. Once the node is removed, I used the tree model to reload the parent node of the node that was removed to reload the UI, but still doesn't work.

public void removeNodeFromTree(JTree tree, String[] path)
{
    TreePath treepath = null;

    for (String section : path)
    {
        int row = (treepath == null ? 0 : tree.getRowForPath(treepath));

        // Expand the current tree path because it seems that if the current tree
        // path is collapsed, getNextMatch() returns null even though the node we are
        // looking for exists. Not sure if this is a bug in JTree.getNextPath() or if that is the intent.
        tree.fireTreeExpanded(treepath);
        treepath = tree.getNextMatch(section, row, Position.Bias.Forward);

        // Return if the path provided doesn't exist.
        if ( treepath == null )
        {
            return;
        }
    }

    // Get the target node to be removed.
    DefaultMutableTreeNode targetNode (DefaultMutableTreeNode)treepath.getLastPathComponent();
    // Get the parent of the target node.
    DefaultMutableTreeNode nParent = (DefaultMutableTreeNode)targetNode.getParent();

    targetNode.removeFromParent();

    // Get the tree model from the JTree object and refresh the parent node.
    DefaultTreeModel tModel = (DefaultTreeModel)tree.getModel();
    tMode.reload(nParent);
}


public void addNodeToTree(JTree tree, String[] path, Object userObject)
{
    TreePath treepath = null;

    for (String section : path)
    {
        int row = (treepath == null ? 0 : tree.getRowForPath(treepath));

        // Expand the current tree path because it seems that if the current tree
        // path is collapsed, getNextMatch() returns null even though the node we are
        // looking for exists. Not sure if this is a bug in JTree.getNextPath() or if that is the intent.
        tree.fireTreeExpanded(treepath);
        treepath = tree.getNextMatch(section, row, Position.Bias.Forward);

        // Return if the path provided doesn't exist.
        if ( treepath == null )
        {
            return;
        }
    }

    // Get the parent of the new node to be added.
    DefaultMutableTreeNode nParent = (DefaultMutableTreeNode)targetNode.getParent();
    nParent.add(new DefaultMutableTreeNode(userObject));

    // Get the tree model from the JTree object and refresh the parent node.
    DefaultTreeModel tModel = (DefaultTreeModel)tree.getModel();
    tMode.reload(nParent);
}

Any ideas? Thank you in advanced.


Solution

  • Ok, so after reviewing the link that trashgod provided about dynamically changing a tree, I realized that my previous way of adding and removing a node was not the correct way. I discovered, that using the getNextMatch() method, the prefix parameter is not used to find an exact match of its value. It is used to find something that relatively matches or comes close to the value of the prefix. That was a big problem for me because I have nodes that have similar names so this approach would fail, ie: if I was searching for NodeA and NodeAB comes before NodeA in the JTree, then I would get NodeAB because the prefix is "NodeA" and NodeAB has "NodeA" in it. The other problem is that I wasn't making use of the tree model to insert a node or remove a node in a JTree. It appears that the correct way to add or remove nodes is thru the tree model. This will perform the proper UI updates when adding or removing nodes in the JTree.

    Here is the new code I wrote that will add and remove nodes in a JTree the correct way:

    In this code I'm assuming that I have a user defined object which when toString() is called, it will return a string that represents that object. This user defined object is added to the node in the addNodeToTree().

    // Adds a node to a JTree providing the name of the parent node that the new node
    // belongs to.
    private void addNodeToTree(Object newObject, String category, JTree tree) {
        DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel();
        DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)treeModel.getRoot();
    
        // BreadFirst means it searches by top nodes first. It'll start with the root node,
        // then iterate thru the children of the root node and so on.
        Enumeration en = rootNode.breadthFirstEnumeration();
    
        while ( en.hasMoreElements() ) {
            DefaultMutableTreeNode categoryNode = (DefaultMutableTreeNode)en.nextElement();
            // Get the user defined object.
            Object categoryObject = categoryNode.getUserObject();
    
            // Check if node matches the category that the new node belongs to and if it does, then
            // add the new node in this category node.
            if ( categoryObject.toString().equals(category) ) {
                // Create a new node.
                DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(newObject);
                // Use the tree model to insert the new node. This will take care of the UI updates.
                treeModel.insertNodeInto(newNode, categoryNode, categoryNode.getChildCount());
                // Exit out of the loop.
                break;
            }
        }
    }
    
    // Iterates thru all the nodes in the JTree and removes the node that
    // matches the string node_name.
    private void removeNodeFromTree(String node_name, JTree tree) {
        DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel();
        DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)treeModel.getRoot();
    
        // DepthFirst means it provides the very last child nodes and work its way up to the root node.
        Enumeration en = rootNode.depthFirstEnumeration();
    
        while ( en.hasMoreElements() ) {
            // Get a reference to a node in the tree to see if it is the target node.
            DefaultMutableTreeNode targetNode = (DefaultMutableTreeNode)en.nextElement();
            // Get the virtual component object of the target node.
            Object objectTargetNode = targetNode.getUserObject();
    
            // If the target node matches the node_name string, then remove the node using
            // the tree model to perform the removal.
            if ( objectTargetNode.toString().equals(node_name) ) {
                treeModel.removeNodeFromParent(targetNode);
                // Exit out of the loop.
                break;
            }
        }
    }
    

    I hope this helps anyone who comes across this same problem.