在 Java 8 中设置装饰 JFrame 的不透明度

Set opacity of a decorated JFrame in Java 8

我想知道如何在Java的最新版本中获得透明的JFrame

目前,您只能使用

<JFrame>.setOpacity();

如果相框未装饰

我不喜欢未装饰的框架,所以我想知道如何绕过这个限制并将框架的不透明度设置为 0.5f 同时仍然保留标题栏、调整大小选项等.

我已阅读此处的文档:http://docs.oracle.com/javase/tutorial/uiswing/misc/trans_shaped_windows.html。该代码仅适用于 Java 6 并且不再运行。正如我所说,错误是:

Exception in thread "AWT-EventQueue-0" java.awt.IllegalComponentStateException: The frame is decorated
    at java.awt.Frame.setOpacity(Frame.java:960)
    at TranslucentWindowDemo.run(TranslucentWindowDemo.java:53)
    at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
    ...

我也尝试使用具有自定义 Alpha 值 (new Color(int, int, int, Alpha)) 的 Color 设置背景 (setBackground : Color),但它会引发完全相同的错误。 以这种方式设置 JPanel 的透明性是行不通的,因为它仍然会位于 JFrame 上,这不是透明的。

我在 Stack Overflow 上找不到其他正确解决此问题的答案。事实上,一些人建议可以通过以下方式解决这个问题:

JFrame.setDefaultLookAndFeelDecorated(true);

但是他们被误导了,可能指的是Java7,因为我已经测试过了,结果是一样的。

我也试过手动设置外观:

try {
    for (final LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
        if ("Nimbus".equals(info.getName())) {
            UIManager.setLookAndFeel(info.getClassName());
            break;
        }
    }

} catch [...] 

将此与上面建议的解决方案结合起来也没有用。

请参阅我在上面链接的示例代码 (Oracle doc) 中的 MCVE,因为这是我正在使用的示例。

有什么解决办法吗?

据我所知,基本答案是:不可能,至少系统外观 .如Is The Java Tutorials Translucent Window example giving trouble to those playing with jdk7?, the JavaDocs clearly indicate that “the window must be undecorated” for setOpacity()所示工作。

但是可以使用(丑陋的)跨平台外观,您可以通过编程方式将其设置为如下:

UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());

事实上,由于可以通过配置覆盖跨平台外观,最安全的实际上是将其显式设置为 Metal,如下所示:

UIManager.setLookAndFeel(new MetalLookAndFeel());

这样做的原因是 Frame.setOpacity() throws an exception when !isUndecorated(), and JFrame.frameInit() sets itself as undecorated when the look and feel's getSupportsWindowDecorations() returns true. It then calls getRootPane().setWindowDecorationStyle() with JRootPane.FRAME 的 JDK 实现,表明装饰将由根窗格而不是框架提供。

据我在 JDK 中所见,Metal 外观和感觉是唯一 getSupportsWindowDecorations() returns true,因为它是唯一一个覆盖它,默认实现只是 returns false.

但是,一些第三方外观也支持它。 Tiny Look and Feel 就是这种情况,正如我刚刚尝试的那样:

(请注意,我在 Ubuntu 上截取了此屏幕截图,TinyLAF 恰好有一个看起来像 Windows XP 的默认主题!)

另请参阅 this question 以获取已知的第三方外观列表。

尝试在创建 JFrame 之前添加此行 window:

JFrame.setDefaultLookAndFeelDecorated(true);

就是那一行,不要在开头替换JFrame,需要是JFrame。

(您也可以在创建 window 之前提到的教程中发现这一行)。

其实这个是可以的,使用reflect的dirty方案。如果我们深入研究 setOpacity 方法(继承自 java.awt.Frame class),我们将看到以下代码:

@Override
public void setOpacity(float opacity) {
    synchronized (getTreeLock()) {
        if ((opacity < 1.0f) && !isUndecorated()) {
            throw new IllegalComponentStateException("The frame is decorated");
        }
        super.setOpacity(opacity);
    }
}

其中 isUndecorated 是一个简单的 getter 到名为 undecorated 的字段(在 java.awt.Frame class 内)。

更改此字段的值即可解决问题,不会抛出此异常。

查看我制作的这个示例

public class JFrameOpacity {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            setSystemLookAndFeel();
            JFrame frame = new JFrame("Opacity to decorated Frame");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setLayout(new FlowLayout());

            JButton decreaseOpacity = new JButton("Reduce Opacity");
            decreaseOpacity.addActionListener(e -> {
                if (frame.getOpacity() - 0.1f <= 0.1f)
                    frame.setOpacity(0.1f);
                else
                    frame.setOpacity(frame.getOpacity() - 0.1f);
            });
            frame.add(decreaseOpacity);

            JButton increaseOpacity = new JButton("Increase Opacity");
            increaseOpacity.addActionListener(e -> {
                if (frame.getOpacity() + 0.1f >= 1f)
                    frame.setOpacity(1f);
                else
                    frame.setOpacity(frame.getOpacity() + 0.1f);
            });
            frame.add(increaseOpacity);

            frame.setSize(300, 300);
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
            try {
                undecorate(frame); //Change it after frame is visible
            } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
        });
    }

    private static void undecorate(Frame frame) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        Field undecoratedField = Frame.class.getDeclaredField("undecorated");
        undecoratedField.setAccessible(true);
        undecoratedField.set(frame, true);
    }

    private static void setSystemLookAndFeel() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
    }

}

预览:


我已经在 The Microsoft Windows Look and Feel (Windows 7 x64) 中对此进行了测试并且它有效。请注意我在调用 undecorate 方法时添加的注释。我对此做了一些测试,我意识到如果你在它至少一次可见之前取消装饰框架,当你让它可见时它会被取消装饰 - 它不会有这个标题栏和东西。

我不确定这是否会给应用程序带来其他问题,但您始终可以更改字段的值,更改其不透明度,然后将其设置回来。