2010-10-20 51 views
11

我在Java窗口中使用Windows Vista/7的DWM功能時遇到問題。我想讓我的框架的背景使用Aero風格。這樣做的Windows API由dwmapi庫中的函數DwmExtendFrameIntoClientArea提供。我已經設法通過JNA正確調用該程序,並且它完成了它應該做的事情(例如,您可以看到,例如,在調整幀的大小時,在下一次重繪之前,您可以在尚未繪製的區域中看到適當的空氣效果,見附圖)。在JFrame中禁用背景圖以正確顯示Aero(DWM)效果

但是在某處(我無法弄清楚)Aero效果的背景是如何繪製的,效果會丟失。

我已經嘗試過:

  • 使用自定義ContentPane不透明度設置爲false
  • 設置LayeredPane的不透明度和RootPane
  • 使用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.jarplatform.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); 
    } 

} 
+0

會發生什麼事,當你使用Windows的外觀和感覺,而不是默認的金屬? – Jonathan 2010-10-20 15:58:50

+0

我正在使用Windows外觀。但是Metal L&F和Windows L&F都存在同樣的問題。沒有不同。 – 2010-10-21 10:26:02

回答

3

偉大的問題。

最明顯的答案是

WindowUtils.setWindowOpaque(this, false); 

這就給了你想要的視覺效果,但遺憾的是阻止您能點擊窗口!

我嘗試的第二件事是重寫paint()方法以執行與opaque標誌設置爲false時Window.paint()相同的操作。那沒有做任何事情。

然後我嘗試使用反射。將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()做什麼,並嘗試以不依賴於反射的方式進行復制。

編輯:在檢查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)); 
} 
+0

聽起來不錯,我會嘗試。感謝您的詳細解答。 – 2010-11-04 06:31:19

+0

'WindowUtils.setWindowOpaque(this,false);'只顯示效果。其實它是一個小故障,你看到它不起作用,如果你選擇到另一個窗口,然後回到它。 而'setLayersOpaque'也沒有改變任何東西。 順便說一下,我發現航空行爲的另一個怪胎,我已經將它添加到我的問題。它看起來越來越像一個人不能一起擺動。 – 2010-11-07 11:15:53

+0

什麼沒有關於'setLayersOpaque'的工作?我記得試圖從中看出來,並且工作。 – 2010-11-07 21:01:55