Search code examples
javaswingjtreetreenodedefaulttreemodel

Why does node selection not work properly after TreeModel reload?


I have a Swing application that uses a JTree. I want some of the nodes of the tree to be hidden, so I have implemented two DefaultTreeModels, one with every node and a filtered one with only the ones that should be displayed. The latter is set as the actual model.

At some points I must change the filtered nodes, and when I do, the items in the tree update properly, but their behavior is wrong. Nodes do not get highlighted when they are selected (even though they are indeed selected) and the user can no longer double-click to expand a node, they must click the little '+' button.

Below is a generalization of my code, two methods from my custom class that extends JTree.

updateFilter gets called when the filter needs to be updated. populateFilteredNode recursively populates the root node of my filtered model. For simplicity, filteredRoot is a class member variable (of type DefaultMutableTreeNode) and is the root of the filtered model. fullModel and filteredModel are of type DefaultTreeModel

public void updateFilter() {
    // Get current expansion state
    ArrayList<Integer> expansionState = getExpansionState();

    DefaultMutableTreeNode fullModelRoot = fullModel.getRoot();

    // Remove existing nodes in the filtered model
    while(filteredRoot.getChildCount() > 0) {
        filteredModel.removeNodeFromParent(filteredRoot.getFirstChild());
    }

    populateFilteredNode(fullModelRoot, filteredRoot);

    // Repaint tree and restore expansion state
    repaint();
    setExpansionState(expansionState);
}

private void populateFilteredNode(DefaultMutableTreeNode fullNode, DefaultMutableTreeNode filteredNode) {

    int index = 0;

    for(int n = 0; n < fullNode.getChildCount(); n++) {
        DefaultMutableTreeNode fullChildNode = fullNode.getChildAt(n);

        // Show the item and its children if one of many cases is true
        if(shouldShowItem(fullChildNode.getItem())) {
            DefaultMutableTreeNode filteredChildNode = fullChildNode.clone();

            filteredModel.insertNodeInto(filteredChildNode, filteredNode, index++);

            populateFilteredNode(fullChildNode, filteredChildNode);
        }
    }
}

If anyone has a similar experience or knows why the selected node will not appear highlighted, please let me know. Or if there is a better way to accomplish filtering. Or if more code would help provide an answer.


Solution

  • I found something that works for my case, although it's quick and dirty and I don't necessarily understand why it works. This 12-year-old post on Code Ranch somehow got me headed in the right direction. I'm just posting it here in case anyone has a similar problem and it might be of help.

    I save the selection path before making any changes to the table model, and then call this new function findNewSelectionPath after the changes were made. Below is a generalized version of the function (I use several custom classes so I did my best to make it look generically usable).

    private TreePath findNewSelectionPath(TreePath oldSelectionPath) {
        TreePath newSelectionPath = null;
    
        if(oldSelectionPath != null) {
            Object[] oldPathComponents = oldSelectionPath.getPath();
            Object[] newPathComponents = new Object[oldPathComponents.length];
    
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) filteredModel.getRoot();
    
            // Set the root
            if(oldPathComponents[0].equals(node)) {
                newPathComponents[0] = node;
            }
    
            // Set the rest of the path components
            for(int n = 1; n < oldPathComponents.length; n++) {
                for(int k = 0; k < node.getChildCount(); k++) {
                    if(oldPathComponents[n].equals(node.getChildAt(k))) {
                        newPathComponents[n] = node.getChildAt(k);
                        node = node.getChildAt(k);
                        break;
                    }
                }
            }
    
            // Make sure that the last path component exists
            if(newPathComponents[newPathComponents.length - 1] != null) {
                newSelectionPath = new TreePath(newPathComponents);
            }
        }
        return newSelectionPath;
    }