JTree组件渲染异常及行高、图标统一优化问题求助
JTree界面优化问题
我尝试使用标准JTree实现树形展示,编写了如下测试代码,期望实现类似NetBeans IDE的美观树形界面,优化后仍存在两个问题:
- Windows7 150%缩放环境下,节点文字底部被截断1-2像素,需调整字体大小或行高;
- 已通过
else { node.setAllowsChildren(false); }解决叶子节点初始显示+号的问题,现需为所有节点设置统一的自定义图标。
测试代码
package inspector; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.util.ArrayList; import java.util.HashMap; import java.util.Set; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; /** * * @author Alex */ public class TreeTest { private static ValueMap prepareTree() { ValueMap obj = new ValueMap(); obj.set("x", new Value(1)); obj.set("y", new Value(1)); ValueArray users = new ValueArray(); obj.set("users", users); ValueMap user = new ValueMap(); user.set("login", new Value("Alex654")); user.set("password", new Value("123456")); ValueMap info = new ValueMap(); info.set("city", new Value("Moscow")); info.set("job", new Value("Developer")); info.set("firstName", new Value("Alex")); info.set("lastName", new Value("Popov")); user.set("bio", info); users.push(user); ValueMap user2 = new ValueMap(); user2.set("login", new Value("admin")); user2.set("password", new Value("test")); user2.set("bio", new Value(null)); users.push(user2); return obj; } static class Value { Value() { value = null; } Value(Object val) { this.value = val; } @Override public String toString() { if (value instanceof Integer || value instanceof Double) { return value + ""; } else { return "\"" + value.toString() + "\""; } } Object value; } static class ValueArray extends Value { ValueArray() { items = new ArrayList<Value>(); } ValueArray(Value val) { items = new ArrayList<Value>(); items.add(val); } void push(Value val) { items.add(val); } ArrayList<Value> items; } static class ValueMap extends Value { ValueMap() { items = new HashMap<String, Value>(); } Value get(String key) { return items.get(key); } void put(String key, Value val) { items.put(key, val); } void set(String key, Value val) { items.put(key, val); } HashMap<String, Value> items; } public static class ValueWrapper { ValueWrapper(String label, Value val) { this.label = label; this.val = val; } String getLabel() { return label; } Value getValue() { return val; } @Override public String toString() { String result = label; if (val instanceof ValueArray) { result += ": Array"; } else if (val instanceof ValueMap) { result += ": { Object }"; } else result += ": " + val.toString(); return result; } private String label; private Value val; } public static void processChildren(DefaultMutableTreeNode node) { Value val = ((ValueWrapper)node.getUserObject()).getValue(); if (val instanceof ValueArray) { ArrayList<Value> items = ((ValueArray)val).items; for (int i = 0; i < items.size(); i++) { DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(new ValueWrapper(i + "", items.get(i))); node.add(newNode); processChildren(newNode); } } else if (val instanceof ValueMap) { HashMap<String, Value> props = ((ValueMap)val).items; Set<String> keys = props.keySet(); for (String key: keys) { DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(new ValueWrapper(key, props.get(key))); node.add(newNode); if (!key.equals("__proto__")) { processChildren(newNode); } } } else { node.setAllowsChildren(false); } } public static void main(String[] args) { try { UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) {} final ValueMap root = prepareTree(); if (root == null) return; final JFrame frame = new JFrame("Object Inspector"); JPanel cp = new JPanel(); cp.setBorder(BorderFactory.createEmptyBorder(9, 10, 9, 10)); frame.setContentPane(cp); cp.setLayout(new BoxLayout(cp, BoxLayout.PAGE_AXIS)); DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(new ValueWrapper("obj", root)); processChildren(rootNode); DefaultTreeModel treeModel = new DefaultTreeModel(rootNode, true); final JTree tree = new JTree(treeModel); final JPanel contentpane = new JPanel(); contentpane.setBackground(Color.WHITE); contentpane.setOpaque(true); contentpane.setLayout(new BorderLayout()); contentpane.add(tree); //contentpane.setBounds(0, 0, 490, 380); final int width = 490, height = 418; cp.setPreferredSize(new Dimension(width, height)); final JScrollPane scrollpane = new JScrollPane(contentpane); scrollpane.setOpaque(false); scrollpane.getInsets(); cp.add(scrollpane); scrollpane.setBackground(Color.WHITE); scrollpane.setOpaque(true); //setSize(518, 420); frame.pack(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLocationRelativeTo(null); frame.setVisible(true); } }
解决方案
1. 解决高缩放比例下文字截断问题
有两种常用处理方式:
- 直接调整行高:给JTree设置足够容纳文字的行高,数值可根据实际字体大小调整:
tree.setRowHeight(22); - 适配缩放的字体与行高:根据系统缩放比例调整字体,再匹配行高避免截断:
Font originalFont = tree.getFont(); // 150%缩放环境下,将字体放大1.5倍 Font scaledFont = originalFont.deriveFont(originalFont.getSize() * 1.5f); tree.setFont(scaledFont); // 行高比字体高度大6像素,预留足够显示空间 tree.setRowHeight(scaledFont.getSize() + 6);
也可以通过UIManager全局设置JTree行高,对所有JTree实例生效:
UIManager.put("Tree.rowHeight", 22);
2. 设置统一自定义图标
通过自定义TreeCellRenderer替换默认图标,步骤如下:
- 创建自定义渲染器类,继承
DefaultTreeCellRenderer,为所有节点设置统一图标; - 将该渲染器绑定到目标JTree。
示例代码:
import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JTree; import javax.swing.tree.DefaultTreeCellRenderer; import java.awt.Component; class CustomTreeIconRenderer extends DefaultTreeCellRenderer { private final Icon customIcon; public CustomTreeIconRenderer(Icon icon) { this.customIcon = icon; } @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); // 为所有节点设置统一图标 setIcon(customIcon); return this; } }
在创建JTree后,设置渲染器:
// 替换为你的图标路径,比如本地图标文件 Icon customIcon = new ImageIcon("icons/custom-node-icon.png"); tree.setCellRenderer(new CustomTreeIconRenderer(customIcon));
如果没有本地图标,也可以使用系统内置图标或自定义绘制图标。
内容的提问来源于stack exchange,提问作者Alex654
相关产品推荐
相关产品推荐

