突出显示 JTree 节点在后台有效,但在视觉上无效

Highlighting a JTree node works under the hood, but not visually

在我的应用程序中,我在左侧显示了一个 JTree,如果用户双击叶子,相应的数据就会在右侧加载。在加载此数据时,我 (i) 保存现有文档(此处不相关),(ii) 更新树以说明可能发生的任何更改,(iii) 确保正确的节点是 selected 更新后在树中(即用户双击的节点)和 (iv) 加载 selected 节点。 应用程序逻辑工作正常,即加载了正确的文件因此我们知道select在树中编辑了正确的节点,但在视觉上没有节点在上述步骤之后完全 selected。

我知道 this question but the problem there seems to have been that the tree was not in focus. I have already tried the different remedies suggested in that post but have not been able to solve my problem. (There is also this related forum thread, although the site seems to be down right now. Furthermore, this question 表面上看起来很相似,但那里的问题源于 OP 构建专有渲染器。)

请看下面我的代码;我试图将其缩减为 SSCCE,但我仍然卡住了。 我目前最好的猜测是问题与这样一个事实有关,即每次调用 updateTree 时都会创建一个全新的 TreeModel 并将其加载到树中,并且这不知何故使得无法视觉上 select 正确的节点。 如果确实如此,那么一个潜在的解决方案是更改 TreeModel 而不是从头开始重新创建它,但是 (i) 这不太方便我和 (ii) 我相信这本身就是一个有趣的问题。

public class JTree_Problem_SSCCE
extends JFrame
{
    private final JTree tree;

    public JTree_Problem_SSCCE()
    {
        super("XYZ");

        // Tree to select data
        DefaultTreeModel treeModel = getTreeModel();

        this.tree = new JTree(treeModel);
        // I don't allow the user to select more than one node at a time but I can reproduce the problem with or without this
        //TreeSelectionModel tsm = new DefaultTreeSelectionModel();
        //tsm.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        //tree.setSelectionModel(tsm);
        tree.addMouseListener(new TreeMouseAdapter());
        expandAllTreeNodes();
        getContentPane().add(tree,BorderLayout.WEST);

        setLocation(25,25);
        setSize(1700,1000);
        setVisible(true);
    }

    private DefaultTreeModel getTreeModel()
    {
        DefaultMutableTreeNode n1 = new DefaultMutableTreeNode("Root");
        DefaultMutableTreeNode n2 = new DefaultMutableTreeNode("Child 1");
        DefaultMutableTreeNode n3 = new DefaultMutableTreeNode("Child 2");
        n1.add(n2);
        n1.add(n3);
        return new DefaultTreeModel(n1);
    }

    private void updateTree(DefaultMutableTreeNode treeNode)
    {
        DefaultTreeModel dtm = getTreeModel();
        tree.setModel(dtm);
        expandAllTreeNodes();

        // No idea why the below doesn't work visually (the application logic works just fine but no tree node is actually selected)
        System.err.println(tree.getExpandsSelectedPaths()); // always returns true (I've seen this to be the problem in other questions)
        System.err.println(new TreePath(((DefaultTreeModel)tree.getModel()).getPathToRoot(treeNode)));
        if (treeNode != null)
        {
            tree.setSelectionPath(new TreePath(((DefaultTreeModel)tree.getModel()).getPathToRoot(treeNode)));
        }

        // As recommended in the answers here (
        // I have tried the below but to no avail
//        tree.requestFocus(); // I have also tried requestFocusInWindow
//        SwingUtilities.invokeLater(new Runnable() {
//            @Override
//            public void run()
//            {
//                tree.setSelectionPath(new TreePath(((DefaultTreeModel)tree.getModel()).getPathToRoot(treeNode)));
//            }
//        });
    }

    private void expandAllTreeNodes()
    {
        // Expand all the nodes in the tree
        // See 
        for (int i = 0; i < tree.getRowCount(); i++)
        {
            tree.expandRow(i);
        }
    }

    class TreeMouseAdapter
    extends MouseAdapter
    {
        @Override
        public void mouseClicked(MouseEvent e)
        {
            if (e.getClickCount() == 2 &&
                    ((DefaultMutableTreeNode)tree.getLastSelectedPathComponent()).isLeaf())
            {
                // [Before opening the new file, save the old one]
                // After saving the old file, make sure the tree is up to date
                updateTree((DefaultMutableTreeNode)tree.getLastSelectedPathComponent());
                // [Now we open the new file]
            }
        }
    }

}

问题与在每次调用 updateTree 时创建一个全新的 TreeModel 有关。问题是 treeNode 变量引用旧树中的一个节点。因此,没有从新树的根到旧树中节点的路径(即,从 treeNode 开始多次调用 getParent() 将导致旧树的根,而不是旧树的根新的一个)。我看到两个可能的选项来解决您的问题。

选项 1:搜索新树节点

你可以写一个像下面这样的函数来搜索从新根开始的树节点与旧 treeNode 的路径。

private static DefaultMutableTreeNode searchTree(DefaultMutableTreeNode root, Object[] path) {
    if (!root.getUserObject().equals(path[0])) {
        // completely different root
        // potentially problematic
        return null;
    }

    DefaultMutableTreeNode node = root;
    for (int i = 1; i < path.length; ++i) {
        Object searchItem = path[i];
        Enumeration<TreeNode> children = node.children();
        boolean found = false;
        while (children.hasMoreElements()) {
            DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
            if (searchItem.equals(child.getUserObject())) {
                found = true;
                node = child;
                break;
            }
        }

        if (!found) {
            // path does not exist any more
            // potentially problematic
            return null;
        }
    }

    return node;
}

然后在设置树选择路径之前将以下内容添加到 updateTree 方法中。

treeNode = searchTree((DefaultMutableTreeNode) tree.getModel().getRoot(), treeNode.getUserObjectPath());

选项 2:修改现有树

而不是每次修改现有的树模型时都创建一个新的树模型。对于每个目录,首先在目录中创建一组文件,并在树中为目录创建一组树节点。删除不在目录中的文件的所有树节点。然后,为目录中不在树中的所有文件创建节点。此选项可能比选项 1 更复杂,但它不会在您的代码中产生重复树节点的潜在问题。