Отключите фоновый рисунок в JFrame, чтобы правильно отображать эффекты Aero (DWM) - PullRequest
11 голосов
/ 20 октября 2010

У меня проблемы с использованием функции DWM в Windows Vista / 7 в окнах Java. Я хочу, чтобы фон моей рамки использовал стиль Aero. Windows API для этого предоставляется функцией 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 Ответ

3 голосов
/ 03 ноября 2010

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

Самый очевидный ответ будет

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);
    }
}

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

Очевидно, что он хрупкий, поскольку полагается на отражение. На вашем месте я бы посмотрел на то, что 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));
}
...