Java:透明的 JDialog 被重绘为白色

Java: transparent JDialog gets repainted white

我在 Windows 上遇到了一个非常大的 Java 程序的奇怪问题。我写了一个小测试程序来重现这个问题。

在 Windows 打开 UAC 提示覆盖后,自定义的透明 JDialog 被重新粉刷成完全白色。

给定以下简单测试class:

import java.awt.Color;
import java.awt.EventQueue;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;

public class DialogTests extends JDialog {
    private static final long serialVersionUID = 1L;

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            e.printStackTrace();
        }
        EventQueue.invokeLater(() -> {
            new DialogTests().setVisible(true);
        });
    }

    public DialogTests() {
        this.setAutoRequestFocus(false);
        this.setUndecorated(true);
        this.setAlwaysOnTop(true);
        this.setFocusableWindowState(true);
        this.setBackground(new Color(0,255,255,0));

        JPanel contentPane = new JPanel();
        contentPane.setBackground(new Color(0,0,0,200));
        setContentPane(contentPane);
        setBounds(200, 200, 500, 500);

        JLabel label = new JLabel("this is just to see something!");
        label.setForeground(new Color(255,0,0,255));
        contentPane.add(label);

        JButton button1 = new JButton("test button 1");
        button1.setBackground(new Color(0,0,0,0));
        contentPane.add(button1);

        JButton button2 = new JButton("test button 2");
        button2.setBackground(new Color(0,0,0,0));
        contentPane.add(button2);
    }

}

以下一系列操作能够为我重现该问题:

  1. 启动程序。不要点击任何按钮或将鼠标移到对话框上!
  2. 强制出现 UAC 提示。例如,当您将 UAC 设置为最高安全级别时,禁用或启用网络适配器。确认提示。
  3. 点击 "test button 2"。对话框重新粉刷成白色,只有两个按钮保持可见(因为它们是为系统外观效果重新绘制的)

如果您不想或无法重现该问题,请查看以下两个屏幕截图:

之前:

之后:

我想知道此错误的解释或可能的解决方法。最好两者都:)

关于我正在使用的系统的一些细节:

非常感谢!

如非必要,请不要在颜色中使用不透明度。这打破了关于不透明度的绘画契约,并可能导致绘画伪影。有关可能导致的问题,请参阅 Background With Transparency

使用255时,组件不透明。使用 0 时,组件是透明的。所以只需使用:

component.setOpaque(true or false);

在框架或对话框上使用透明度时,您可以直接设置不透明度。尝试使用:

this.setBackground(new Color(0,255,255)); // play with this color
this.setOpacity(0.75f);  // play with this opacity.
...
//contentPane.setBackground(new Color(0,0,0,200));
contentPane.setOpaque(false);

我自己找到了解决方法,其中涉及使用 JNA library。似乎通过使用 WindowUtils.setWindowTransparent() 渲染模式以一种不再导致错误的方式得到改变。

以下增强代码应该可以正常工作:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;

import com.sun.jna.platform.WindowUtils;

public class DialogTests extends JDialog {
    private static final long serialVersionUID = 1L;

    public static void main(String[] args) {
        try {
            System.setProperty("sun.java2d.noddraw", "true");
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            e.printStackTrace();
        }
        EventQueue.invokeLater(() -> {
            new DialogTests().setVisible(true);
        });
    }

    public DialogTests() {
        this.setAutoRequestFocus(false);
        this.setUndecorated(true);
        this.setAlwaysOnTop(true);
        this.setFocusableWindowState(true);
        this.setBackground(new Color(0,255,255,0));
        WindowUtils.setWindowTransparent(this, true);

        JPanel contentPane = new JPanel();
        contentPane.setBackground(new Color(0,0,0,200));
        contentPane.setPreferredSize(new Dimension(200,200));
        setContentPane(contentPane);
        setBounds(200, 200, 500, 500);

        JLabel label = new JLabel("this is just to see something!");
        label.setForeground(new Color(255,0,0,255));
        contentPane.add(label);

        JButton button1 = new JButton("test button 1");
        button1.setBackground(new Color(0,0,0,0));
        contentPane.add(button1);

        JButton button2 = new JButton("test button 2");
        button2.setBackground(new Color(0,0,0,0));
        contentPane.add(button2);
    }

}

唯一要考虑的事情:

  • System.setProperty("sun.java2d.noddraw", "true"); 必须在创建 window
  • 之前设置
  • window属性,如未修饰和背景颜色,必须在使用前设置WindowUtils.setWindowTransparent(this, true);,之后不能更改
  • 作为(对我来说理想的)副作用,所有未绘制的透明像素现在都可以点击(意味着鼠标事件会传播到此 window 后面的其他 windows)