【问题标题】:How to set transparent background for JTree cell?如何为 JTree 单元格设置透明背景?
【发布时间】:2012-11-24 00:45:42
【问题描述】:

伙计们,

我正在尝试创建渐变 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;
        }
    }
}

【问题讨论】:

  • 不相关:在绘制过程中永远不会改变组件状态 - 在你的情况下,映射做 not 在paintComponent 中调用 setOpaque。
  • 感谢您的反馈。如果我不这样做,super.paintComponent 会绘制整个背景,并且我会丢失我应用的渐变。有更好的方法吗?问候,彼得
  • tree.setOpaque(false) 永久有什么问题?重复我自己(不能经常重复):在其绘制过程中更改组件的状态是不是一个选项。这是毫无例外的规则。
  • 你是对的。出于某种原因,我认为它在我早期的代码中不起作用。问候,彼得。

标签: java swing transparency jtree renderer


【解决方案1】:

这是一个真正的痛苦。 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));
        }
    }
}

【讨论】:

  • 感谢您的帮助。尝试调用它,但我没有看到任何变化。问候。彼得
  • @Peter Timing 就是一切。您需要在创建树之前设置此值。这也会影响您可能创建的任何新树。您也可以使树无效并重新绘制,但我没有测试它
  • 我认为可以在 Swing 中为 XxxRenderers 设置透明或透明
  • @mKorbel DefaultTreeCellRender 有标志(rendererFillBackground),它允许渲染器忽略opaque 标志,这很烦人。
  • @mKorbel 实际上DefaultTreeCellRenderer 将默认填充 BG,MadProgrammer 的解决方案允许绕过这种丑陋的行为。但是你是对的,如果 OP 不想要背景,他不应该扩展DefaultTreeCellRenderer,而是使用不透明的JLabel。这要简单得多。
【解决方案2】:

回答

(扩展@Mad 的答案,对潜在问题的冗长分析在最后):

如果您希望全局属性在手动设置为树的 defaultTreeCellRenderer 中有效,则该渲染器必须再次调用 updateUI ,f.i.

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

如果您这样做想要更改全局设置并让透明渲染器只有一些树实例 - 选项是

  • 要么从头开始实现 TreeCellRenderer,并省去所有的脏东西(比如覆盖油漆和做一些意想不到的硬编码技巧......哇!)
  • 通过在 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;

最终结果是(假设我们强制访问该字段,f.i. 通过反射),无论 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());
}

顺便说一句:这种实例化混乱似乎是在 jdk7 中引入的……很可能(虽然没有检查)渲染器颜色的默认设置也不能正常工作。

【讨论】:

  • 克娄巴特拉。非常感谢您的详细分析。但是,即使我按照您的建议更改了 updateUI() 逻辑,它也不起作用。我发现它起作用的唯一方法是在创建树之前将 Tree.rendererFillBackground 设置设置为 false。问候,彼得
  • @Peter 感谢提醒 - 听起来你在 jdk6 上?
【解决方案3】:

像这样扩展 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);好像解决了。

【讨论】:

    【解决方案4】:

    当着这些 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

    【讨论】:

      猜你喜欢
      • 2013-01-11
      • 2011-10-04
      • 1970-01-01
      • 2014-04-05
      • 1970-01-01
      • 1970-01-01
      • 2023-03-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多