简体   繁体   English

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

[英]How to set transparent background for JTree cell?

Folks, 民间,

I am trying to create a gradient JTree control. 我正在尝试创建一个渐变JTree控件。 The following code mostly works except that the background for the tree cell is not transparent. 除了树单元格的背景不是透明的以外,以下代码大部分可用。 I would appreciate it if someone call tell me what is it that I am not doing right. 如果有人打电话告诉我我做错了什么,我将不胜感激。

Thank you in advance for your help. 预先感谢您的帮助。

Regards, 问候,
Peter 彼得


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;
        }
    }
}

This is a real pain. 这是一个真正的痛苦。 The DefaultTreeCellRenderer will ignore the opaque value and fill it's contents anyway. DefaultTreeCellRenderer将忽略opaque值,并DefaultTreeCellRenderer填充其内容。 However, there is a flag you can try. 但是,有一个标志可以尝试。 I've done it in the past, but don't have time to test it... 我过去做过,但是没有时间进行测试...

Try UIManager.put("Tree.rendererFillBackground", false) . 尝试UIManager.put("Tree.rendererFillBackground", false) Try and do this before anything is renderer, but after any look and feel settings have been applied. 在任何东西都成为渲染器之前,但在应用任何外观设置之后,请尝试执行此操作。

UPDATED 更新

It is very important to set this property BEFORE you create any trees 在创建任何树之前,设置此属性非常重要

Without | 没有| With... 用...

在此处输入图片说明在此处输入图片说明

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));
        }
    }
}

Answer 回答

(expanding on @Mad's answer, the longish analysis of the underlying problems is at the end): (扩展@Mad的答案,对基本问题的冗长分析在最后):

If you want the global property to be effective in a defaultTreeCellRenderer set manually to the tree, that renderer has to call updateUI again , fi 如果要使global属性在手动设置为树的defaultTreeCellRenderer中生效,则该渲染器必须再次调用updateUI,

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

If you do not want to change the global setting and have the transparent renderers only some tree instances - the options are 如果你不想改变全局设置,并有透明渲染只有一些树的实例-该选项

  • either implement a TreeCellRenderer from scratch and leaving out all the dirtiness (like overriding paint and doing some unexpected hard-coded tricksery ... doooh!) 要么从头开始实现TreeCellRenderer,然后消除所有脏污(例如覆盖油漆并执行一些意外的硬编码技巧...哎呀!)
  • tricks the renderer by temporarily setting the ui property in updateUI 通过在updateUI中临时设置ui属性来欺骗渲染器

Tricksing code: 技巧代码:

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);
        }
    }
};

Analysis 分析

starting from my comment: 从我的评论开始:

Weirdly, the mere act of setting CellRenderer (vs. letting the ui install its favourits) makes the flag ineffective 奇怪的是,仅设置CellRenderer的行为(与让ui安装其收藏夹相比)会使标志无效

This puzzle is resolved: 这个难题解决了:

DefaultTreeCellRenderer has the intention to set its fillBackground field from the setting in the UIManager - but fails doing so on instantiation. DefaultTreeCellRenderer 打算通过UIManager中的设置来设置其fillBackground字段-但在实例化时无法这样做。 The reason is a - all too common error ;-) - in actually doing so in super's instantiation, due to calling a overridden method in super's constructor: 原因是-太常见的错误;-)-实际上是在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);
    ...
}

then later in the instantiation process, the field value is hardcoded: 然后在实例化过程的后面,对字段值进行硬编码:

private boolean fillBackground = true;

The net result is (assuming that we force access to the field, fi via reflection), the following passes always, irrespective of the setting in the UIManager. 最终结果是(假设我们通过反射强制访问该字段),无论UIManager中的设置如何,始终执行以下操作。

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

With that the unusual thingy is: why does the setting in the UIManager has an effect when letting the ui install its default? 与此不同寻常的是:为什么让UI安装默认设置时,UIManager 的设置会生效? Here the reason is that the renderers updateUI is called twice: once on instantiation and once in the tree's updateUI: 这是因为渲染器的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: this instantiation mess seems to be introduced in jdk7 ... most probably (didn't check, though) the default settings of the renderer colors not working as well. 顺便说一句:这种实例化混乱似乎是在jdk7中引入的...很可能(虽然没有检查)渲染器颜色的默认设置也不起作用。

How about extending the DefaultTreeCellRenderer like this: 如何像这样扩展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; 
    }

} }

Setting c.setOpaque(true); 设置c.setOpaque(true); seems to solve it. 似乎解决了。

I really hesitate to advance this hypothesis in the presence of these Swing experts... but could it be that more recent JDKs have actually rectified this problem? 在这些Swing专家在场的情况下,我真的很犹豫是否提出这一假设……但是,可能是因为最近的JDK确实纠正了这个问题?

I have code like this in my app and it seems to work fine... the JTree's background shines through perfectly... NB Jython, but should be understandable: 我的应用程序中有类似这样的代码,它似乎可以正常工作... 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 version is 1.7.0_079 Java版本是1.7.0_079

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM