如何为JTree单元设置透明背景?

伙计们,

我正在尝试创建一个渐变JTree控件。 除了树单元格的背景不透明外,以下代码大部分都有效。 如果有人打电话告诉我,我做得不对,我将不胜感激。

预先感谢您的帮助。

问候,
彼得

package TestPackage; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import java.awt.*; public class Test { public Test() { JFrame frame = new JFrame(); JPanel framePanel = new JPanel(); framePanel.setLayout(new BorderLayout()); frame.setContentPane(framePanel); DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Item"); DefaultMutableTreeNode childNode = new DefaultMutableTreeNode("Child"); rootNode.add(childNode); GradientTree tree = new GradientTree(rootNode); // JTree tree = new JTree(rootNode); // tree.setBackground(Color.blue); tree.setCellRenderer(new MyRenderer()); JScrollPane scroll = new JScrollPane(tree); scroll.setOpaque(false); framePanel.add(scroll, BorderLayout.CENTER); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { new Test(); } }); } @SuppressWarnings("serial") public static class GradientTree extends JTree { public GradientTree(DefaultMutableTreeNode node) { super(node); } @Override protected void paintComponent(Graphics g) { int h = getHeight(); int w = getWidth(); GradientPaint gradientPaint = new GradientPaint(0, 0, Color.LIGHT_GRAY, 0, h, Color.WHITE); Graphics2D g2D = (Graphics2D) g; g2D.setPaint(gradientPaint); g2D.fillRect(0, 0, w, h); this.setOpaque(false); super.paintComponent(g); this.setOpaque(true); } } @SuppressWarnings({"serial" }) private class MyRenderer extends DefaultTreeCellRenderer { public MyRenderer() { this.setOpaque(false); this.setForeground(Color.RED); } 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); return this; } } } 

这是一个真正的痛苦。 DefaultTreeCellRenderer将忽略opaque值并无论如何填充它的内容。 但是,你可以尝试一个标志。 我过去做过,但没有时间测试它……

试试UIManager.put("Tree.rendererFillBackground", false) 。 尝试在渲染任何内容之前执行此操作,但在应用任何外观设置之后。

更新

在创建任何树之前设置此属性非常重要

没有| 随着…

在此处输入图像描述在此处输入图像描述

 public class TestTreeRenderer { public static void main(String[] args) { new TestTreeRenderer(); } public TestTreeRenderer() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { } JFrame frame = new JFrame("Test"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new BorderLayout()); frame.add(new TreePane()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public class TreePane extends JPanel { private JTree tree; public TreePane() { // THIS IS VERY IMPORTANT // You must set this BEFORE creating ANY trees!! UIManager.put("Tree.rendererFillBackground", false); setLayout(new BorderLayout()); tree = new JTree(); tree.setBackground(Color.BLUE); System.out.println("Loading files..."); File root = new File("/etc"); DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(root.getName()); for (File file : root.listFiles()) { rootNode.add(new DefaultMutableTreeNode(file.getName())); } System.out.println("Loading model"); DefaultTreeModel model = new DefaultTreeModel(rootNode); tree.setModel(model); add(new JScrollPane(tree)); } } } 

回答

(根据@Mad的回答,对底层问题的长期分析最后):

如果您希望全局属性在手动设置到树的defaultTreeCellRenderer中有效,则该呈现器必须再次调用updateUI,fi

 UIManager.put("Tree.rendererFillBackground", false); ... TreeCellRenderer r = new DefaultTreeCellRenderer() { { updateUI(); } }; tree.setCellRenderer(r); 

如果您不想更改全局设置并且透明渲染器只有一些树实例 – 选项是

  • 从头开始实现一个TreeCellRenderer并省去所有的肮脏(如重写油漆和做一些意想不到的硬编码技巧…… doooh!)
  • 通过在updateUI中临时设置ui属性来欺骗渲染器

欺骗代码:

 TreeCellRenderer r = new DefaultTreeCellRenderer() { { updateUI(); } @Override public void updateUI() { Object old = UIManager.get("Tree.rendererFillBackground"); try { UIManager.put("Tree.rendererFillBackground", false); super.updateUI(); } finally { UIManager.put("Tree.rendererFillBackground", old); } } }; 

分析

从我的评论开始:

奇怪的是,仅仅设置CellRenderer(与让ui安装它的优点相比)使得标志无效

解决了这个难题:

DefaultTreeCellRenderer 有意从UIManager中的设置设置其fillBackground字段 – 但在实例化时失败。 原因是 – 一个太常见的错误;-) – 实际上是在super的实例化中这样做,因为在super的构造函数中调用了一个重写的方法:

 // this is implemented in DefaultTreeCellRenderer // but called in JLabel constructor public void updateUI() { .... // we are in JLabel, that is fillBackground not yet known fillBackground = DefaultLookup.getBoolean(this, ui, "Tree.rendererFillBackground", true); ... } 

然后在实例化过程中,字段值被硬编码:

 private boolean fillBackground = true; 

最终结果是(假设我们通过reflection强制访问字段,fi),无论UIManager中的设置如何,总是跟随传递。

 DefaultTreeCellRenderer renderer = new DefaultTreeRenderer(); assertTrue(renderer.fillBackground); 

有了这个不寻常的东西:为什么UIManager 的设置在让ui安装它的默认值时有效? 这里的原因是渲染器updateUI被调用两次:一次在实例化时,一次在树的updateUI中:

 public void updateUI() { setUI((TreeUI)UIManager.getUI(this)); // JW: at this point the renderer has its fillbackground hard-coded to true SwingUtilities.updateRendererOrEditorUI(getCellRenderer()); // JW: now it's updateUI has been called again, and correctly set to the // UIManager's value SwingUtilities.updateRendererOrEditorUI(getCellEditor()); } 

BTW:这个实例化混乱似乎是在jdk7中引入的……很可能(虽然没有检查)渲染器颜色的默认设置也不起作用。

如何扩展DefaultTreeCellRenderer如下:

 public class MyRenderer extends DefaultTreeCellRenderer { public Component getTreeCellRendererComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row, boolean hasFocus) { JComponent c = (JComponent) super.getTreeCellRendererComponent(tree, value, isSelected, expanded, leaf, row, hasFocus); c.setOpaque(true); return c; } 

}

设置c.setOpaque(true); 似乎解决了它。

在这些Swing专家面前我真的犹豫推进这个假设……但是,最近的JDK能否真正解决这个问题呢?

我在我的应用程序中有这样的代码,它似乎工作得很好…… JTree的背景完美闪耀…… NB Jython,但应该是可以理解的:

  def getTreeCellRendererComponent( self, tree, value, selected, expanded, leaf, row, has_focus ): super_comp = self.super__getTreeCellRendererComponent( tree, value, selected, expanded, leaf, row, has_focus ) super_comp.opaque = not selected ... 

Java版本是1.7.0_079