Отключите фоновый рисунок в JFrame, чтобы правильно отображать эффекты Aero (DWM)

У меня возникают проблемы с использованием DWM-функций Windows Vista/7 на Java-окнах. Я хочу, чтобы фон моего фрейма использовал стиль Aero. Для этого API Windows предоставляет функция DwmExtendFrameIntoClientArea в библиотеке dwmapi. Мне удалось правильно вызвать процедуру через JNA, и она делает то, что она должна делать (вы можете видеть это, например, при изменении размера кадра, перед следующей перерисовкой вы видите правильные аэроэффекты в области, еще не нарисованной, см. прикрепленное изображение).

Но где-то (я не могу понять, где) фон окрашивается над эффектом Aero, и эффект теряется.

Что я уже пробовал:

  • Использование пользовательского ContentPane с непрозрачностью, установленной на false
  • Установка непрозрачности LayeredPane и RootPane на false
  • Использование Frame вместо JFrame
  • Установите цвет фона JFrame/ContentPane на черный/полностью прозрачный
  • Используйте setLayersOpaque и его собственный вариант, см. первый ответ для более подробной информации.

До сих пор мне не удавалось удалить этот фон. Это ограничение AWT/Swing? Как я могу удалить этот фон или использовать эффект Aero правильно?

Ваша помощь с благодарностью.

Снимок экрана

Вот скриншот кадра без какого-либо содержимого, установив непрозрачность RootPane, LayeredPane и ContentPane на false. Я сделал это быстро, изменяя размер. Вы видите, что эффект правильно применяется к области, на которой Java еще не рисовала.

http://i55.tinypic.com/v614qo.png (Как новый пользователь я не могу отправить изображение напрямую...)

Нечетное поведение

После дальнейшего исследования я столкнулся со следующим нечетным поведением. Если размер окна 150x150 или ниже содержимого отображается прозрачно. Это очень проблематично для обычных компонентов окна. Если вы рисуете непосредственно на кадре, переопределяя метод paint(), все становится полупрозрачным. Кроме того, система координат кажется немного выключенной, она появляется, когда нулевая точка JFrame установлена ​​на фактическую нулевую точку окна. Таким образом, Swing пытается рисовать области, где фактически находится граница окна, которая тогда, конечно, не видна.

Смотрите этот скриншот: http://d-gfx.kognetwork.ch/java_aero_bug.png

Пример кода

Это код, который я использую.

Требуется jna.jar и platform.jar. Доступно на домашней странице JNA.

import com.sun.jna.Function;
import com.sun.jna.Native;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinNT.HRESULT;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.UIManager;

public class AeroFrame extends JFrame {

    public AeroFrame() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JLabel label = new JLabel("Testlabel");
        label.setOpaque(false);

        add(label);

        pack();

        enableAeroEffect();
    }

    private void enableAeroEffect() {
        NativeLibrary dwmapi = NativeLibrary.getInstance("dwmapi");
        HWND aeroFrameHWND = new HWND(Native.getWindowPointer(this));
        MARGINS margins = new MARGINS();
        margins.cxLeftWidth = -1;
        margins.cxRightWidth = -1;
        margins.cyBottomHeight = -1;
        margins.cyTopHeight = -1;
        //DwmExtendFrameIntoClientArea(HWND hWnd, MARGINS *pMarInset)
        //http://msdn.microsoft.com/en-us/library/aa969512%28v=VS.85%29.aspx
        Function extendFrameIntoClientArea = dwmapi.getFunction("DwmExtendFrameIntoClientArea");
        HRESULT result = (HRESULT) extendFrameIntoClientArea.invoke(HRESULT.class,
                new Object[] { aeroFrameHWND, margins});
        if(result.intValue()!=0)
            System.err.println("Call to DwmExtendFrameIntoClientArea failed.");
    }

    /**
     * http://msdn.microsoft.com/en-us/library/bb773244%28v=VS.85%29.aspx
     */
    public class MARGINS extends Structure implements Structure.ByReference {
            public int cxLeftWidth;
            public int cxRightWidth;
            public int cyTopHeight;
            public int cyBottomHeight;
    }

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            JFrame.setDefaultLookAndFeelDecorated(true);

        } catch (Exception e) {
            e.printStackTrace();
        }
        new AeroFrame().setVisible(true);
    }

}

Ответ 1

Отличный вопрос.

Наиболее очевидным ответом будет

WindowUtils.setWindowOpaque(this, false);

Это дает вам визуальные эффекты, которые вы хотите, но, к сожалению, не позволяет вам щелкнуть по окну!

Во-вторых, я пытался переопределить метод paint() для выполнения тех же действий, что Window.paint(), когда флаг opaque установлен в значение false. Это ничего не делало.

Затем я попытался использовать Reflection. Отражающая установка Window.opaque в true дала те же результаты, что и при использовании WindowUtils.

Наконец, я попытался добавить это к enableAeroEffect():

Method m = null;
try {
    m = Window.class.getDeclaredMethod("setLayersOpaque", Component.class, Boolean.TYPE);
    m.setAccessible(true);
    m.invoke(null, this, false);
} catch ( Exception e ) {
    //TODO: handle errors correctly
} finally {
    if ( m != null ) {
        m.setAccessible(false);
    }
}

Это сработало! Окно по-прежнему правильно реагирует на события мыши, но фон не нарисован. Рисунок немного взломанный, но должен помочь вам на пути.

Очевидно, он хрупкий, поскольку он полагается на Reflection. Если бы я был вами, я бы посмотрел, что делает Window.setLayersOpaque(), и попытайтесь воспроизвести это так, чтобы не полагаться на Reflection.

Изменить. При проверке метода setLayersOpaque это, похоже, сводится к отключению двойной буферизации на прозрачных компонентах. Вызовите этот метод из своего метода enableAeroEffect(), и вы на своем пути:

//original source: Sun, java/awt/Window.java, setLayersOpaque(Component, boolean)
private static void setLayersTransparent(JFrame frame) {
    JRootPane root = frame.getRootPane();
    root.setOpaque(false);
    root.setDoubleBuffered(false);

    Container c = root.getContentPane();
    if (c instanceof JComponent) {
        JComponent content = (JComponent) c;
        content.setOpaque(false);
        content.setDoubleBuffered(false);
    }
    frame.setBackground(new Color(0, 0, 0, 0));
}