You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,以此更新节点的显示宽度,完美解决文本截断和留白问题。

额外优化建议

  1. 避免重复创建Font对象:可以在渲染器中提前缓存加粗和普通两种字体,减少不必要的对象创建:
private Font regularFont;
private Font boldFont;

public MyCellRenderer() {
    // ... 其他初始化代码
    regularFont = label.getFont();
    boldFont = regularFont.deriveFont(Font.BOLD);
}

// 在getTreeCellRendererComponent中使用:
label.setFont(info.checked ? boldFont : regularFont);
  1. 移除不必要的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:43:45