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

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

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

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

  • Использование пользовательского ContentPane с непрозрачностью false
  • Установка непрозрачности LayeredPane и RootPane на false
  • Использование Frame вместо JFrame
  • Установите цвет фона _9 _ / _ 10_ на черный / полностью прозрачный
  • Используйте 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);
    }

}

person Daniel Dreibrodt    schedule 20.10.2010    source источник
comment
Что происходит, когда вы используете стиль Windows вместо стандартного Metal?   -  person Jonathan    schedule 20.10.2010
comment
Я использую стиль Windows. Но это та же проблема как с Metal L&F, так и с Windows L&F. Нет разницы.   -  person Daniel Dreibrodt    schedule 21.10.2010


Ответы (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));
}
person Mark Peters    schedule 03.11.2010
comment
Звучит хорошо, я попробую. Спасибо за подробный ответ. - person Daniel Dreibrodt; 04.11.2010
comment
WindowUtils.setWindowOpaque(this, false); только кажется, чтобы дать эффект. На самом деле это глюк, вы видите, что он не работает, если перейти к другому окну, а затем вернуться к нему. И setLayersOpaque тоже ничего особо не изменил. Кстати, в поведении Aero я обнаружил еще одну странность, я добавил ее к своему вопросу. Все больше и больше похоже на то, что вместе со свингом этого не сделать. - person Daniel Dreibrodt; 07.11.2010
comment
Что не сработало с setLayersOpaque? Я помню, как пытался переключаться туда и обратно, и это сработало. - person Mark Peters; 08.11.2010
comment
@Daniel: Также попробуйте отключить DirectDraw и включить openGL: -Dsun.java2d.noddraw=true -Dsun.java2d.opengl=true. - person Mark Peters; 08.11.2010
comment
Ах, включение OpenGL помогло, я этого раньше не делал. Теперь мне просто нужно избавиться от этого глючного рисунка. - person Daniel Dreibrodt; 08.11.2010