【问题标题】:Very weird Java2D setClip() effect - bug?非常奇怪的 Java2D setClip() 效果 - 错误?
【发布时间】:2015-08-27 16:43:26
【问题描述】:

我想创建一个具有自定义形状和透明度的对话框,想想指向某个组件的信息气泡。

为此,我将JPanel 添加到JDialog 并覆盖面板的paintComponent(Graphics) 方法。面板本身包含常规的JLabelsJButtons。 工作正常,但只要我在面板绘制代码中使用Graphics2D.setClip(Shape),组件就会被背景透支。如果我不设置剪辑(设置为全新的Graphics2D 对象,不少于),一切正常。 这让我很困惑,我不知道我能做些什么来解决它。

P.S.:我不能在 JDialog 上使用 setShape(Shape),因为那里没有抗锯齿功能。 P.P.S.:实际用例是绘制一个大的背景图像,必须完全按照信息气泡的形状进行切割。

以下 SSCCE 演示了当您将鼠标多次悬停在右上角的“x”上时出现的问题。

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import java.awt.Polygon;
import java.awt.Shape;


public class Java2DTransparencySSCE extends JDialog {

    JPanel panel;
    private JButton close;
    private JLabel headline;
    private JLabel mainText;

    public Java2DTransparencySSCE() {
        setLayout(new BorderLayout());
        setFocusable(false);
        setFocusableWindowState(false);
        setUndecorated(true);
        setBackground(new Color(0, 0, 0, 0));

        panel = new JPanel(new GridBagLayout()) {

            @Override
            public void paintComponent(final Graphics g) {
                super.paintComponent(g);
                Graphics2D gImg = (Graphics2D) g.create();

                // if the line below is removed, everything works..
                Shape oldClip= gImg.getClip();

                // both the shape and a standard rectangular clip break everything
                // gImg.setClip(50, 50, 50, 50);
                Polygon shape = new Polygon(new int[] {0, 100, 100, 50, 0}, new int[] {200, 200, 100, 50, 100}, 5);
                gImg.setClip(shape);
                gImg.setColor(new Color(255, 0, 0, 50));
                gImg.fill(shape);
                gImg.setClip(oldClip);

                gImg.dispose();
            }

        };
        panel.setOpaque(false);
        add(panel, BorderLayout.CENTER);

        headline = new JLabel("This is a title");
        mainText = new JLabel("<html><div style=\"line-height: 150%;width:100px \">This is some sort of text which is rather long and thus pretty boring to actually read. Thanks for reading anyway!</div></html>");
        close = new JButton("X");
        close.setBorderPainted(false);
        close.setContentAreaFilled(false);
        close.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                System.exit(0);
            }
        });

        layoutPanel();
    }

    private void layoutPanel() {
        GridBagConstraints constraints = new GridBagConstraints();
        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.insets = new Insets(10, 10, 10, 10);
        constraints.fill = GridBagConstraints.BOTH;
        constraints.weightx = 1;
        panel.add(headline, constraints);

        constraints.gridx += 1;
        constraints.weightx = 0;
        constraints.fill = GridBagConstraints.NONE;
        panel.add(close, constraints);

        constraints.gridx = 0;
        constraints.gridy += 1;
        constraints.gridwidth = 2;
        constraints.weightx = 1;
        constraints.weighty = 1;
        constraints.fill = GridBagConstraints.BOTH;
        panel.add(mainText, constraints);

        pack();
    }

    public static void main(String[] args) {
        JFrame parent = new JFrame();
        JPanel pane = new JPanel(new BorderLayout());
        pane.add(new JTextArea("This is a text."));
        parent.setContentPane(pane);
        parent.setSize(400, 400);
        parent.setLocation(400, 300);
        parent.setDefaultCloseOperation(JDialog.EXIT_ON_CLOSE);
        parent.setVisible(true);

        JDialog dialog = new Java2DTransparencySSCE();
        dialog.setLocation(500, 400);
        dialog.setAlwaysOnTop(true);
        dialog.setVisible(true);
    }

}

编辑:为了规避 Windows 上的这个特定错误,我添加了以下内容,在我的用例中不会导致问题或性能下降(可能因您而异):

JDialog dialog = new Java2DTransparencySSCE() {

        @Override
        public void paint(Graphics g) {
            g.setClip(null);
            super.paint(g);
        }
};

【问题讨论】:

  • 当你把 gImg.setClip(null);在 gImg.dispose() 之前; ?我猜 gImg.setClip() 也会影响 g,所以其他按钮会被剪掉。
  • 不改变任何东西。将其归零或将其设置回旧剪辑都没有帮助。
  • 在 Mac OS X 10.9、Java 8 上看起来不错,
  • @trashgod 好奇!没想到值得一提的是这发生在 Windows 7、Java8 上。
  • 在 Linux、Java(TM) SE 运行时环境(内部版本 1.8.0_05-b13)上看起来也不错。不过,我也可以确认 Windows 上的损坏行为。

标签: java swing java-2d


【解决方案1】:

您应该在自定义绘图之前存储原始剪辑并在之后恢复。

Shape oldCLip=g2d.getClip();
...
your code
...
g2d.setClip(oldClip);

【讨论】:

  • 在一个新创建的 Graphics 对象上我马上扔掉?我还编辑了我的代码以包含它,因为它不能解决任何问题。我在写 SSCE 时忘记放回去了。
  • 您不能直接转换为 Graphics2D g 而不是创建一个新的吗?还在调用 super.paintComponent(g); 之前存储/设置新剪辑;并调用 setOpaque(false) 避免背景绘制
【解决方案2】:

在 Mac OS X 10.9、Java 8 上,我没有发现异常。我看到与下面的变体相同的外观,其中既不创建也不处置派生图形上下文。 API 建议“程序员应该在使用完 Graphics 对象后调用 dispose,前提是它是直接从组件或另一个 Graphics 对象创建的。”我不确定实现在内部有何不同,但这可能是罪魁祸首。

@Override
public void paintComponent(final Graphics g) {
    super.paintComponent(g);
    Graphics2D gImg = (Graphics2D) g;
    Shape oldClip= gImg.getClip();
    Polygon shape = new Polygon(
        new int[] {  0, 100, 100, 50,   0},
        new int[] {200, 200, 100, 50, 100}, 5);
    gImg.setClip(shape);
    gImg.setColor(new Color(255, 0, 0, 50));
    gImg.fill(shape);
    gImg.setClip(oldClip);
}

【讨论】:

  • 感谢您的测试。我现在还测试了在各种 Linux 和 OS X 机器上运行的代码,按照上面的屏幕截图工作。然而,在所有测试过的 Windows 机器(7 和 8)上,多边形完全变红并覆盖了其子元素的内容。我尝试不处理图形,也没有任何区别。
  • 这表明平台或驱动程序异常。
【解决方案3】:

将鼠标悬停在JButton 上时,面板仅使用重新绘制所需的边界——按钮的边界(如果您选中oldClip,它应该是JButton 的边界)重新绘制自身。由于super.paintComponent 没有清除剪辑并且JDialog 的背景是完全透明的,因此更改剪辑边界会导致alpha 颜色成为每个先前绘制调用的合成。

我想创建一个具有自定义形状和透明度的对话框,想想指向某个组件的信息气泡。

考虑使用轻量级组件方法 - 您可以通过设置包含对话框项的 JFrame 的玻璃窗格、根据需要切换玻璃窗格的可见性和/或内容来实现。

【讨论】:

  • 这为我指明了正确的方向!这仍然是 Java 和 Windows 之间的某种错误,但是我能够通过覆盖 JDialog 的 paint(Graphics) 方法并调用 g.setClip(null); 来防止它发生。在调用 super.paint(g) 之前。现在它按预期工作。就我而言,在那里重置剪辑时不会导致任何问题或性能下降,因此它对我有用。
【解决方案4】:

您应该在所有绘制代码​​之后调用 super.paintComponent(g)(这将是您覆盖的最后一行)。

这样您的绘图将位于子组件下方。

【讨论】:

  • 这只对 JLabels 之类的东西是正确的,对于像 JPanel 这样的容器是错误的。您实际上会在自定义内容上绘制灰色背景。不用说,不起作用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多