调整JTree节点位置时Swing抛出ArrayIndexOutOfBoundsException问题排查
一、搞定ArrayIndexOutOfBoundsException异常
从你贴的异常栈能看出来,问题出在树的布局缓存(VariableHeightLayoutCache)绘制时访问了无效的节点索引。这是因为你在循环里边删边插节点,每次操作都会触发树的部分重绘,而此时模型的状态还不稳定——比如删除节点后根节点的子节点数量减少,后续插入的i值可能已经超过当前子节点总数,导致索引越界;同时树的重绘线程可能和你的模型修改操作“撞车”。
修复步骤:
批量处理节点修改,最后统一刷新模型
别每次删/插都让树立刻重绘,先把要移动的节点都收集起来并移除,再统一按顺序插入,最后一次性通知模型更新。这样能避免中间状态的冲突。改写你的代码:
public void changeNodePositionInTree() { DefaultTreeModel model = (DefaultTreeModel) tree.getModel(); DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getRoot(); List<DefaultMutableTreeNode> nodesToMove = new ArrayList<>(); // 先收集所有需要调整的节点,并从原父节点移除 for (String nodeName : someList) { DefaultMutableTreeNode node = findNodeInTreeByName(nodeName); if (node != null && node.getParent() != null) { nodesToMove.add(node); model.removeNodeFromParent(node); } } // 按顺序插入到根节点的对应位置,确保索引合法 for (int i = 0; i < nodesToMove.size(); i++) { DefaultMutableTreeNode node = nodesToMove.get(i); // 防止插入索引超过当前根节点的子节点数量 int insertIndex = Math.min(i, root.getChildCount()); model.insertNodeInto(node, root, insertIndex); } // 一次性刷新整个根节点的模型,触发树重绘 model.reload(root); }确保所有操作在EDT中执行
你已经用SwingUtilities.invokeLater把操作放到事件调度线程(EDT)里了,这点做的很对——记住所有Swing组件的模型修改都必须在EDT中完成,绝对不能在后台线程操作。避免循环中的索引错误
原来的代码里,删除节点后根节点的子节点数会减少,后续的i值可能会大于当前子节点总数,导致插入时索引越界。上面的代码用Math.min(i, root.getChildCount())保证了插入索引的合法性,避免了这个问题。
二、解决节点文本显示不全(带省略号)的问题
这个问题是因为JTree的单元格宽度不够,或者默认的渲染器不支持自动换行/调整大小。给你几个实用的解决方法:
方案1:自定义单元格渲染器,支持换行和自动调整
默认的DefaultTreeCellRenderer是个JLabel,默认不会换行。你可以自定义渲染器,开启换行并让它根据文本自动调整大小:
tree.setCellRenderer(new DefaultTreeCellRenderer() { @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); // 开启自动换行,按单词拆分 setLineWrap(true); setWrapStyleWord(true); // 根据树的宽度调整渲染器的宽度 setSize(tree.getWidth(), getPreferredSize().height); return this; } });
方案2:让JTree自动计算最佳宽度
如果你的树放在JScrollPane里,可以先计算所有节点的最大宽度,再设置树的首选宽度:
// 遍历所有节点,计算最大文本宽度 int maxNodeWidth = 0; DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot(); Enumeration<?> nodeEnum = root.breadthFirstEnumeration(); while (nodeEnum.hasMoreElements()) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) nodeEnum.nextElement(); // 获取渲染器并计算首选宽度 Component renderer = tree.getCellRenderer().getTreeCellRendererComponent(tree, node, false, false, node.isLeaf(), 0, false); maxNodeWidth = Math.max(maxNodeWidth, renderer.getPreferredSize().width); } // 设置树的首选宽度,加上缩进的预留空间 tree.setPreferredSize(new Dimension(maxNodeWidth + 50, tree.getPreferredSize().height));
方案3:确保节点展开,避免折叠截断
有时候节点折叠时文本会被截断,你可以设置树展开所有节点,或者开启展开时自动滚动:
tree.setScrollsOnExpand(true); // 展开所有节点(如果业务允许) expandAllTreeNodes(tree, new TreePath(root));
对应的expandAllTreeNodes方法实现:
private void expandAllTreeNodes(JTree tree, TreePath path) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); if (node.getChildCount() > 0) { for (Enumeration<?> e = node.children(); e.hasMoreElements();) { DefaultMutableTreeNode child = (DefaultMutableTreeNode) e.nextElement(); TreePath childPath = path.pathByAddingChild(child); expandAllTreeNodes(tree, childPath); } } tree.expandPath(path); }
内容的提问来源于stack exchange,提问作者polis

