TreeCellRenderer中标签宽度自适应异常问题求助
解决JTree自定义渲染器状态切换时节点宽度不更新的问题
这个问题的核心原因是JTree会缓存节点的显示宽度:当你修改复选框状态导致标签字体在加粗/普通之间切换时,虽然渲染器的preferredSize已经变化,但JTree并没有主动重新计算节点宽度,依然使用之前缓存的尺寸,所以会出现文本截断或留白过多的情况。
解决方案:通知TreeModel节点已更改
当你切换复选框状态后,不要只调用repaint(),而是要通知TreeModel对应的节点发生了变化,这样JTree会自动清除该节点的尺寸缓存,重新计算渲染器的preferredSize,从而更新节点宽度。
修改你的toggleCheckBox方法如下:
private void toggleCheckBox(TreePath treePath) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent(); NodeInfo info = (NodeInfo) node.getUserObject(); info.checked = !info.checked; // 关键:通知TreeModel节点数据已更改,触发JTree重新计算节点尺寸 ((DefaultTreeModel) getModel()).nodeChanged(node); }
为什么这个方法有效?
JTree的DefaultTreeModel在调用nodeChanged()时,会触发TreeModelEvent,JTree接收到这个事件后,会标记对应的节点需要重新布局,清除之前缓存的节点宽度,然后重新调用渲染器获取最新的preferredSize,以此更新节点的显示宽度,完美解决文本截断和留白问题。
额外优化建议
- 避免重复创建Font对象:可以在渲染器中提前缓存加粗和普通两种字体,减少不必要的对象创建:
private Font regularFont; private Font boldFont; public MyCellRenderer() { // ... 其他初始化代码 regularFont = label.getFont(); boldFont = regularFont.deriveFont(Font.BOLD); } // 在getTreeCellRendererComponent中使用: label.setFont(info.checked ? boldFont : regularFont);
- 移除不必要的
invalidate()调用:渲染器中的label.invalidate()、checkbox.invalidate()和invalidate()是多余的,因为JTree在渲染时会自动处理组件的状态更新。
修改后的完整TestTree类(关键部分已更新)
import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.EmptyBorder; import javax.swing.plaf.basic.BasicTreeUI; import javax.swing.tree.*; @SuppressWarnings("serial") public class TestTree extends JTree { public int checkboxWidth; public TestTree() { super(getNodes()); setRootVisible(false); setShowsRootHandles(true); setCellRenderer(new MyCellRenderer()); addMouseListener(new TreeMouseManager()); addKeyListener(new TreeKeyManager()); } private void toggleCheckBox(TreePath treePath) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent(); NodeInfo info = (NodeInfo) node.getUserObject(); info.checked = !info.checked; // 通知TreeModel节点已更改,触发尺寸重新计算 ((DefaultTreeModel) getModel()).nodeChanged(node); } private class TreeMouseManager extends MouseAdapter { @Override public void mouseClicked(MouseEvent event) { TreePath treePath; treePath = getPathForLocation(event.getX(), event.getY()); if (treePath == null) return; if ((event.getClickCount() != 1) || (event.getButton() != MouseEvent.BUTTON1)) return; BasicTreeUI ui; int depth; int leftIndent; int rightIndent; int checkboxLeft; int checkboxRight; ui = (BasicTreeUI)getUI(); depth = treePath.getPathCount(); leftIndent = ui.getLeftChildIndent(); rightIndent = ui.getRightChildIndent(); checkboxLeft = (depth - 1) * (leftIndent + rightIndent); checkboxRight = checkboxLeft + checkboxWidth - 1; int x; x = event.getX(); if ((x < checkboxLeft) || (x > checkboxRight)) return; toggleCheckBox(treePath); } } private class TreeKeyManager extends KeyAdapter { @Override public void keyPressed(KeyEvent event) { TreePath treePath; treePath = getSelectionPath(); if (treePath == null) return; if (event.getKeyCode() == KeyEvent.VK_SPACE) toggleCheckBox(treePath); } } private class MyCellRenderer extends JPanel implements TreeCellRenderer { private JCheckBox checkbox; private JLabel label; private Font regularFont; private Font boldFont; public MyCellRenderer() { checkbox = new JCheckBox(); checkbox.setBorder(null); checkbox.setOpaque(false); label = new JLabel(); label.setBorder(new EmptyBorder(new Insets(0, 2, 0, 2))); setLayout(new GridBagLayout()); setOpaque(false); GridBagConstraints constraints; constraints = new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); add(checkbox, constraints); constraints = new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); add(label, constraints); checkboxWidth = (int)checkbox.getPreferredSize().getWidth(); // 缓存字体对象 regularFont = label.getFont(); boldFont = regularFont.deriveFont(Font.BOLD); } @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { DefaultMutableTreeNode node; NodeInfo info; node = (DefaultMutableTreeNode)value; if (!(node.getUserObject() instanceof NodeInfo)) { return this; } info = (NodeInfo)node.getUserObject(); checkbox.setSelected(info.checked); label.setText(info.name); label.setOpaque(selected); // 使用缓存的字体 label.setFont(info.checked ? boldFont : regularFont); if (selected) { label.setBackground(SystemColor.textHighlight); label.setForeground(SystemColor.textHighlightText); } else { label.setBackground(SystemColor.text); label.setForeground(SystemColor.textText); } return this; } } private static DefaultMutableTreeNode getNodes() { DefaultMutableTreeNode root; root = new DefaultMutableTreeNode("root"); DefaultMutableTreeNode first; DefaultMutableTreeNode second; DefaultMutableTreeNode third; NodeInfo info; info = new NodeInfo(); info.name = "This is the first node"; info.checked = true; first = new DefaultMutableTreeNode(info); info = new NodeInfo(); info.name = "And this is the second"; info.checked = false; second = new DefaultMutableTreeNode(info); info = new NodeInfo(); info.name = "Finally, the third"; info.checked = false; third = new DefaultMutableTreeNode(info); root.add(first); root.add(second); root.add(third); info = new NodeInfo(); info.name = "Second level node"; info.checked = true; first.add(new DefaultMutableTreeNode(info)); info = new NodeInfo(); info.name = "This is another one"; info.checked = false; first.add(new DefaultMutableTreeNode(info)); info = new NodeInfo(); info.name = "And this is the last one"; info.checked = true; first.add(new DefaultMutableTreeNode(info)); return root; } private static class NodeInfo { public String name; public boolean checked; } }
现在运行修改后的代码,你会发现切换复选框状态时,节点宽度会自动适配文本的加粗/普通状态,不会再出现截断或留白问题。
内容的提问来源于stack exchange,提问作者Robert Kock
相关产品推荐
相关产品推荐

