【轉】DWM 窗體玻璃效果實現


我一直盼望着 Windows 新版本的發布。令人感興趣的事情莫過於瀏覽 MSDN® 和 SDK 文檔,查找一些可以利用和依賴的最新創新,然后讓朋友和同事以及您的老板(如果幸運的話)大開眼界。Windows Vista™ 在這方面包含許多誘人的內容。自從聽說該版本將三維/組合層集成到桌面以來,我就特別希望獲得該版本。多年來我已編寫了數不清的三維應用程序,但我發現令人煩惱的一件事是,雖然能夠在三維應用程序中提供非常酷且令人賞心悅目的用戶界面,但在非三維應用程序中卻不能。有了 Windows Vista 和桌面窗口管理器 (DWM),這種情況就發生了改變(請參見圖 1)。

 

圖 1  DWM 可以實現諸如 Flip 3D 任務切換等功能 (單擊該圖像獲得較大視圖)
DWM 是一種新界面,用於管理如何將運行和呈現的各種窗口合並到 Windows Vista 桌面上。Windows ®Presentation Foundation (WPF) 提供了一種更高級別的層,控制着到桌面層的呈現,Windows Display Driver Model (WDDM) 用於處理到顯示器的實際低級呈現。本文僅討論如何使用 DWM 界面。有關該主題的詳細信息,請閱讀 David Chappell 編寫的 MSDN 文章“Windows Presentation Foundation 簡介”( msdn2.microsoft.com/aa663364.aspx)。WDDM 之所以是本文通篇關注的唯一內容,是因為它可以通過 DWM 界面實現新效果,而且可以修復一些特定問題。
 
DWM 的技術概覽
在除 Windows Vista Home Basic 之外的所有 Windows Vista 版本中均提供 DWM 界面,通過 dwm.exe 可以啟動該界面。系統中的所有應用程序都可以從 DWM 獲益,而無需進行修改或重新編譯。不過,選擇利用特定 DWM 功能的應用程序可以調用 dwmapi.dll 中的界面(DWM 的公用界面),然后將這些界面傳遞到 dwm.exe。界面聲明可在 dwmapi.h 中找到,並且可以從  windowssdk.msdn.microsoft.com 在線獲取最新的 API 信息。
Windows Vista 在設計上對每個窗口都使用圖形加速器,而不是僅針對三維 DirectX ® 應用程序。為了實現這一點,DWM 需要與 WDDM 通信,后者是圖形處理器和視頻內存的最終所有者。(DWM 依賴於 milcore.dll,后者是與 Windows Presentation Foundation 共享的組件,用於輸出和呈現到 DirectX。)呈現操作由 DWM 中的單獨線程處理,擁有 DirectX 設備的用戶無法對其進行訪問。另外,大多數應用程序本身有處理呈現操作和 UI 的線程(如典型的 Win32 ® 應用程序中的 USER 消息彈出線程),但其與 DWM 的呈現線程不沖突。DWM 獲取一個窗口列表,並在樹結構中管理其位圖,然后將其組合到最終桌面。換言之,每個應用程序均呈現自己的位圖,然后由 DWM 進行組合。
應用程序的主窗口線程呈現其場景,DWM 呈現線程對該場景進行訪問,並且呈現線程通過其 DirectX 界面更新桌面。傳遞的信息被壓縮為僅對以前呈現的更改(差異),大型數據(如圖像)則置於共享內存中。這就潛在地允許在一台計算機上生成場景,而最終的呈現操作可以在另一台計算機上完成。使用過 OpenGL 的三維程序員會對此體系結構非常熟悉,它允許服務器管理三維場景,並且僅將差異發送到客戶端計算機。您可以分布式呈現三維場景,並讓其在任何數量的客戶端計算機上使用完全硬件加速運行。此體系結構使 DWM 能夠為遠程桌面方案提供一級支持。
盡管 Windows Vista 將與舊版 Windows XP 兼容驅動程序一起運行,但需要讓 WDDM 視頻驅動程序來獲得所有 DWM 功能。與某些假定情況相反,DWM 不需要 DirectX 10,但它確實需要更多的視頻/紋理內存和支持 Shader Model 2.0 或更高版本的視頻卡。使用 WDDM 的最大改變是它引入了 Video Memory Manager (VidMM),后者可以在系統內存和視頻內存之間交換視頻內存分布。這意味着 WDDM 可以虛擬化視頻卡的資源,因而在共享和交換視頻內存方面以及在不同應用程序的不同線程之間對圖形處理器進行上下文切換方面可以做得更好。以前幾乎不可能穩定地運行多個三維應用程序,原因是驅動程序無法處理上下文切換。而且在 WDDM 出現之前,沒有可用的正式計划,因此通常會出現一個 DirectX 應用程序耗盡其他應用程序的資源。而使用 WDDM 就很難發生這種情況。Windows Vista 對驅動程序的控制也嚴格得多,並要求比以前 Windows 版本的驅動程序更強大可靠。
另外請注意,DirectX 10 是一個僅用於 Windows Vista 的 API。為 DirectX 以前版本設計的應用程序將運行於舊版 DirectX API 實現(預計將被稱為 DirectX 9 L)上。這將是 WDDM 之前的驅動程序支持的最后版本。DirectX 9 L 應用程序預計將運行於裝有 DirectX 9 L 的 Windows XP 上和 Windows Vista 上。DirectX 10 不包含舊式界面。

 

組合桌面的好處
所有這些新的子系統可讓您獨立呈現窗口,並在將它們呈現到桌面之前對其執行組合步驟。在 Windows Vista 和隨之發布的一些更新應用程序中可以很好地利用這一點。關於這一點,我要向您展示的兩個用法是 Aero™ 玻璃效果和縮略圖。玻璃效果僅在運行 Aero Glass 方案並打開組合功能時才可用。Aero Basic 不提供此效果。
由於每個窗口都在自己的視頻內存區中創建,因此需要由 DWM 將該窗口的最后組合呈現到桌面。這意味着,DWM 可以訪問桌面上的圖像,並可以將其與您的窗口呈現混合在一起,創建由二者組合的呈現。在與現有桌面圖像混合以創建霜凍玻璃效果的任何窗口區域中,這一點非常明顯。由於每個窗口都呈現到中間屏外表面,這意味着 DWM 是涉及更新玻璃效果的唯一程序。在移動使用玻璃效果的窗口時,不需要使任何基本窗口無效。DWM 會處理半透明可視圖像到新坐標的更新。可以指示 DWM 為要呈現玻璃效果的窗口添加某些客戶端區域,這樣可讓您創建供自己使用的玻璃區域。
此屏外組合使得桌面響應更快。由於每個窗口現在都從桌面中單獨呈現,因此就消除了您在更新較慢的應用程序(如 Web 瀏覽器)中經常遇到的問題。在 Windows 的以前版本中, 圖 2 顯示的內容並不陌生。在另一個應用程序上方來回移動窗口時會出現這種撕裂現象,這是因為下面的窗口更新的速度太慢。進行桌面組合之后,就不會再看到這種現象。
圖 2  慢速呈現導致撕裂現象 (單擊該圖像獲得較大視圖)
隨 Windows Vista 一起發布的一些應用程序利用此功能將玻璃效果呈現到客戶端區域中。其中一個最好的示例是 Windows Media ® Player,該程序將框架擴展到客戶端區域的底部,它在該區域中繪制一些自定義控件。最小版本看起來與 圖 3 類似。
圖 3  Windows Media Player 中的玻璃效果 (單擊該圖像獲得較大視圖)
當在我的桌面上來回移動窗口時,通過控件可以看到的圖像由播放器下面的桌面部分組成。如果將窗口移動到某個動畫上,我可以透過上面的窗口看到動畫!這就是組合桌面的功能所在。
如果關閉組合效果,則僅會在窗口中獲得“極光”效果,並且窗口將呈現不透明的默認窗口顏色,如 圖 4 所示。
圖 4  關閉玻璃效果的 Windows Media Player (單擊該圖像獲得較大視圖)
從本質上講,Aero Basic 和 Aero Glass 界面是 DWM UI 之前的標准和新標准之間的分水嶺。Aero Basic 界面為程序提供同一 API,以保持向后兼容性,但是運行 Basic 界面則意味着您在使用舊式窗口管理器,DWM 是不活動的。舊式界面意味着 UI 布局將像 Windows Vista 之前編寫的程序預期的那樣。DWM 控制着 Aero Glass 界面並限制對它的訪問。如果應用程序進入非客戶端區域(玻璃框架),DWM 將檢測到它,並切換到 Aero Basic 框架。
Windows Vista 中由於組合桌面而提供的其他新功能還有,新的 Windows Flip (Alt+Tab) 和 Flip 3D(Alt+Windows 鍵)任務切換器。Flip 3D 特別有趣,因為它的功能取決於 DWM 中的一些代碼,這些代碼將獲取場景圖形中的每個頂級窗口,並將其呈現到一系列扭曲的窗口上,您可以使用鍵盤或鼠標在這些窗口中進行滾動(請參見 圖 1)。
DWM 控制着窗口與桌面組合引擎的交互方式。若要讓您的程序集成到 DWM 功能中,需要了解 DWM 的工作方式以及如何與之交互。除了一些其他功能外,DWM 在其公共 API 中有四個主要功能區:
  • 基本桌面組合設置
  • 在客戶端窗口中呈現玻璃效果
  • 呈現縮略圖
  • 調整呈現效果以便與多媒體程序交互
我將在本文中講述前三部分。最后一部分為 DirectX 和視頻播放應用程序提供,因為 DWM 是異步運行的,如果不進行嚴格控制可能會導致采樣失真。

 

一些常見的組合函數
如果您希望在程序中使用桌面組合功能,則需要查詢和設置各種 DWM 參數。例如,如果某些應用程序切換為全屏顯示,然后 DWM 關閉組合,並以不透明的桌面背景顏色呈現,那么您的應用程序應識別這種情況並禁用特定於組合的功能。下面是將您的程序與 DWM 集成的一些基本函數:
DwmEnableComposition 啟用或禁用 DWM 組合。DWM 將在當前進程中或直到重設前保持此設置。更改設置會導致發出 WM_DWMCOMPOSITIONCHANGED 通知。多數應用程序不需要調用此函數,但您可能需要監視 Windows 的最終消息。
DwmIsCompositionEnabled 獲取桌面啟用 DWM 組合的狀態。
DwmSetWindowAttribute 為窗口設置指定的 DWM 屬性的值,控制如何處理 DWM 過渡,是否允許非客戶端呈現,以及 Flip 3D 將如何處理窗口。例如,如果對某個窗口關閉非客戶端呈現,則稍后擴展框架或使窗口后面的內容變模糊的要求將會失敗。
DwmGetWindowAttribute 為指定窗口檢索指定 DWMWINDOWATTRIBUTE 的當前值。
DwmGetColorizationColor 檢索用於 DWM 玻璃組合的當前顏色。此值基於當前顏色方案。更改此設置將導致發出 WM_WMCOLORIZATIONCOLORCHANGED 通知。
DwmDefWindowProc 在使用 WM_NCHITTEST 通知進行調用時以及由於在擴展了客戶端框架而需要處理 WM_NCCALCSIZE 及類似消息時,使 DWM 命中測試位於非客戶端區域。
在您的程序中呈現玻璃效果相當簡單。DWM 為此提供了兩個函數:
DwmExtendFrameIntoClientArea 一個簡單的函數,將非客戶端框架的邊緣擴展到窗口內。
DwmEnableBlurBehindWindow 一種更為復雜的函數,對玻璃效果的呈現方式提供更多的控制。
由於所有組合窗口都通過 DWM 呈現到一個屏外窗口,然后進行組合后再呈現到桌面上,因此獲取這些圖像並提供應用程序的實時縮略圖表示並不困難。DWM 為您提供了四個函數來控制縮略圖的呈現方式:
DwmQueryThumbnailSourceSize 返回 DWM 縮略圖的原始大小。
DwmRegisterThumbnail 創建目標窗口和源窗口之間的縮略圖關系。
DwmUnregisterThumbnail 刪除由 DwmRegisterThumbnail 創建的 DWM 縮略圖關系。
DwmUpdateThumbnailProperties 更新給定縮略圖的屬性。
DWM 提供了五個函數用於微調 DWM 的呈現方式,但這些函數超出了本文的討論范圍。
 
為 Aero Glass 做好准備
若要對 DWM 界面編程,您需要運行能夠顯示 Aero Glass 的 Windows Vista 版本。雖然最方便的方法是使用 C++ 代碼調用這些新函數,但如果可以的話,我喜歡用 C# 編寫用戶界面代碼。本文的所有代碼均使用 C# 編寫,但這意味着您必須經受嚴峻的考驗。若要使用本文討論的函數,您要么需要使用 C++ 和正確的庫中的鏈接,要么必須用 C# 為函數和結構編寫 P/Invoke 包裝。在本文的下載部分中包含一個庫,其中提供了 DWM 所需函數和結構的包裝,因此您可以從 C# 程序調用它。基本上,這只是從 dwmapi.dll 下載界面的一組說明。為使用本文中使用的用於玻璃效果和縮略圖的 DWM 函數,您需要創建 DWM 函數和數據結構的 C# 聲明。我為本文創建的聲明類似於 圖 5

 

Figure 5 DWM 的 C# 聲明

internal class DwmApi
{
    [DllImport("dwmapi.dll", PreserveSig = false)]
    public static extern void DwmEnableBlurBehindWindow(
        IntPtr hWnd, DWM_BLURBEHIND pBlurBehind);

    [DllImport("dwmapi.dll", PreserveSig = false)]
    public static extern void DwmExtendFrameIntoClientArea(
        IntPtr hWnd, MARGINS pMargins);

    [DllImport("dwmapi.dll", PreserveSig = false)]
    public static extern bool DwmIsCompositionEnabled();

    [DllImport("dwmapi.dll", PreserveSig = false)]
    public static extern void DwmEnableComposition(bool bEnable);

    [DllImport("dwmapi.dll", PreserveSig = false)]
    public static extern void DwmGetColorizationColor(
        out int pcrColorization, 
        [MarshalAs(UnmanagedType.Bool)]out bool pfOpaqueBlend);

    [DllImport("dwmapi.dll", PreserveSig = false)]
    public static extern IntPtr DwmRegisterThumbnail(
        IntPtr dest, IntPtr source);

    [DllImport("dwmapi.dll", PreserveSig = false)]
    public static extern void DwmUnregisterThumbnail(IntPtr hThumbnail);

    [DllImport("dwmapi.dll", PreserveSig = false)]
    public static extern void DwmUpdateThumbnailProperties(
        IntPtr hThumbnail, DWM_THUMBNAIL_PROPERTIES props);

    [DllImport("dwmapi.dll", PreserveSig = false)]
    public static extern void DwmQueryThumbnailSourceSize(
        IntPtr hThumbnail, out Size size);

    [StructLayout(LayoutKind.Sequential)]
    public class DWM_THUMBNAIL_PROPERTIES
    {
        public uint dwFlags;
        public RECT rcDestination;
        public RECT rcSource;
        public byte opacity;
        [MarshalAs(UnmanagedType.Bool)]
        public bool fVisible;
        [MarshalAs(UnmanagedType.Bool)]
        public bool fSourceClientAreaOnly;
        public const uint DWM_TNP_RECTDESTINATION = 0x00000001;
        public const uint DWM_TNP_RECTSOURCE = 0x00000002;
        public const uint DWM_TNP_OPACITY = 0x00000004;
        public const uint DWM_TNP_VISIBLE = 0x00000008;
        public const uint DWM_TNP_SOURCECLIENTAREAONLY = 0x00000010;
    }

    [StructLayout(LayoutKind.Sequential)]
    public class MARGINS
    {
        public int cxLeftWidth, cxRightWidth, 
                   cyTopHeight, cyBottomHeight;

        public MARGINS(int left, int top, int right, int bottom)
        {
            cxLeftWidth = left; cyTopHeight = top; 
            cxRightWidth = right; cyBottomHeight = bottom;
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public class DWM_BLURBEHIND
    {
        public uint dwFlags;
        [MarshalAs(UnmanagedType.Bool)]
        public bool fEnable;
        public IntPtr hRegionBlur;
        [MarshalAs(UnmanagedType.Bool)]
        public bool fTransitionOnMaximized;

        public const uint DWM_BB_ENABLE = 0x00000001;
        public const uint DWM_BB_BLURREGION = 0x00000002;
        public const uint DWM_BB_TRANSITIONONMAXIMIZED = 0x00000004;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int left, top, right, bottom;

        public RECT(int left, int top, int right, int bottom)
        {
            this.left = left; this.top = top; 
            this.right = right; this.bottom = bottom;
        }
    }
}
如果您使用的是 C#,則需要在您的代碼中創建與此類似的聲明。然后,如果您運行的是 Windows Vista,則可以進行 DWM 調用。當然,不應僅假定您的應用程序運行於 Windows Vista 上。為保險起見,您需要確認 Environment.OSVersion.Version.Major 至少為 6.0。或者,您可以捕獲由於嘗試通過 P/Invoke 調用不存在的函數而導致的異常。
如果希望使用玻璃效果,則您使用的計算機需要滿足三個要求。首先,需要運行 Windows Vista 的 Premium、Business 或 Ultimate 版本。其次,硬件應能夠運行 Aero 界面(詳細信息請參見 microsoft.com/windowsvista/getready/capable.mspx)。最后,需要在 Windows Vista 中選擇 Windows Aero 顏色方案。需要謹慎使用此操作,因為用戶過度使用該效果會對計算機的 GPU 造成負擔。
圖 6  窗口顏色和外觀選項 (單擊該圖像獲得較大視圖)
通過打開“個性化”控制面板並單擊“Window 顏色和外觀”選項啟用 Aero 方案。在此屏幕上(請參見 圖 6),確保選中了“啟用透明效果”選項,然后單擊“打開傳統風格的外觀屬性”鏈接。在“外觀設置”對話框中的“顏色方案”下單擊 Windows Aero(請參見 圖 7)。在單擊“確定”后,將會看到 Aero 界面和玻璃效果。如果願意,還可以自定義窗口顏色和不透明級別。
圖 7  Windows Aero 玻璃效果 (單擊該圖像獲得較大視圖)

 

對 DWM 編程
通過調用 DwmIsCompositionEnabled 可以檢查在您的程序中是否啟用了 Aero 方案。但是請注意,不但用戶可以隨時更改當前 Aero 方案,而且其他應用程序也能夠以編程方式啟用或禁用該方案。因此檢查一次此函數的結果可能不夠可靠。
DwmEnableComposition 函數可讓程序打開或關閉 Aero 方案。例如,如果您編寫的應用程序可能遇到兼容性問題,則在應用程序運行時可能需要禁用組合(如果您編寫的是全屏 DirectX 專用應用程序,將會自動禁用組合)。此設置僅在設置它的進程期間保持,當進程結束時,組合標志將被重設為其初始值。通常情況下,除非由於應用程序兼容性原因,否則應用程序不應使用此設置,而應讓系統或用戶作出決定。
當桌面組合的狀態更改時,將廣播 WM_DWMCOMPOSITIONCHANGED 消息。您無法通過參數了解是否啟用或禁用了組合,因此,如果感興趣,由您自己決定調用 DwmIsCompositionEnabled。執行檢查的代碼非常簡單,難點是當禁用組合時,如何決定窗口的外觀。
 
// Check to see if composition is Enabled
if (DwmIsCompositionEnabled())
{
    // enable glass rendering
}
else
{
    // fallback rendering
}
最后,即使啟用了 Aero 方案,用戶可能已經更改了玻璃顏色並使組合效果變得不透明。我編寫了一個小應用程序,使用該程序可以創建完全是玻璃效果的窗口,然后我在控制面板中更改了玻璃屬性(請參見 圖 8)。第一個圖像顯示窗口顏色和透明度的默認設置。然后我關閉了透明度,留下一個不透明窗口。我更改為紅色窗口顏色和默認的透明度,您仍可從中辨認出某些底層窗口圖像。在一個足夠大的不透明窗口中,您將觀察到放置在玻璃呈現中的高亮區域,它就是窗口上可見的斜紋“極光”效果。
圖 8a  更改顏色和透明度 
圖 8b
圖 8c
您可以通過調用 DwmGetColorizationColor 函數查看組合顏色和不透明度。如果此函數成功,它將設置一個 GDI+ ARGB 顏色值和布爾值,指示該顏色是否不透明。就像在控制面板中更改 Aero 方案一樣,當組合顏色更改時將會廣播一條消息。在出現這種情況時,將發送 WM_DWMCOLORIZATIONCOLORCHANGED,但是,在這種情況下,參數將告訴您新顏色和不透明度是什么。
 
protected override void WndProc(ref Message msg)
{
    switch (msg.Msg)
    {
        case WM_DWMCOLORIZATIONCOLORCHANGED:
            // The color format of currColor is 0xAARRGGBB.
            uint currColor = (uint)msg.WParam.ToInt32();
            bool opacityblend = (msg.LParam.ToInt32() != 0);
            ...
            break;
    }
}
在對非客戶端區域更改 DWM 呈現時,將發送 WM_DWMNCRENDERINGCHANGED 消息。如果啟用了 DWM 的非客戶端呈現,wParam 將為 true。當最大化或非最大化 DWM 組合窗口時收到 WM_DWMWINDOWMAXIMIZEDCHANGE 消息,您還將得到通知。如果已最大化窗口,wParam 將為 true。
前面我們介紹了兩個函數,您可以使用這兩個函數為您的程序提供玻璃效果。第一個是 DwmExtendFrameIntoClientArea。使用 Aero 方案的窗口在標題欄區域和窗口邊緣的邊界處具有玻璃效果(實質上是窗口的所有非客戶端區域)。此函數可讓您擴展呈現到客戶端區域的非客戶端區域的每個邊,並使用玻璃效果呈現它。換句話說,您可以將玻璃窗口框架的頂邊、左邊、右邊和底邊無縫擴展到您的窗口中。
第二個函數是 DwmEnableBlurBehindWindow,它可讓您呈現邊緣為任意形狀的玻璃效果,並指定更多參數來更好地控制呈現效果,但是,我認為多數使用玻璃效果的用戶只是將玻璃效果從邊緣擴展到客戶端區域。無論使用哪個函數,都需要密切跟蹤組合狀態,以查看是否應在呈現時啟用玻璃效果。這意味着要跟蹤四個 WM_DWM* 消息,或者調用 DwmIsCompositionEnabled,以便了解是關閉玻璃效果進行呈現,還是在打開時呈現。
首先來看一下較簡單的調用。此函數專用於無框架窗口(如任務欄、側欄、Tablet 筆輸入窗口和“開始”菜單);未定義有框架窗口上的行為。
DwmExtendFrameIntoClientArea 函數采用窗口句柄和 MARGINS 結構。窗口句柄是框架從邊緣擴展到客戶端區域的窗口。您需要設置包含像素數的 MARGINS 結構,以便將框架擴展到客戶端區域。MARGINS 結構的 C# 實現如 圖 5 所示。
剛開始您可能會感到迷惑,因為沒有其他 Win32 函數與此作用類似,但是基本上您可以從其他函數獨立地控制每個邊。選擇您要擴展的邊,並指定要呈現的效果在客戶端區域的深入程度(請參見 圖 9)。如果需要擴展多個邊,它們可以重疊。如果希望該效果動態跟蹤窗口大小,則每次在窗口大小更改時都需要調用 DwmExtendFrameIntoClientArea 函數。一種特殊情況是將一個或多個邊距設置為 -1,這樣會將玻璃效果擴展到整個窗口。若要重設邊距,只需將所有邊距值設置為 0,並再次調用 DwmExtendFrameIntoClientArea。
圖 9  客戶端區域中的玻璃邊距 (單擊該圖像獲得較大視圖)
如果您不希望將玻璃效果從框架擴展到客戶端區域,該怎么辦?DwmEnableBlurBehindWindow 函數可讓您更好地控制如何將玻璃效果添加到窗口。同樣,該函數采用您要添加玻璃效果的窗口的窗口句柄,但它還采用 DWM_BLURRBEHIND 結構,以便讓您設置有關如何在窗口上使用模糊效果的各種參數。其中最重要的一個參數是區域參數,它是一個 GDI 術語,用於描述通常由一系列直線和曲線構成的任意形狀的區域。
DWM_BLURRBEHIND 結構(如 圖 5 所示)包含控制如何顯示模糊效果的參數。
如果希望在客戶端區域打開玻璃效果,則可以將 fEnable 標志設置為 true。若要將其關閉,應將該標志設置為 false。hRgnBlur 參數是您創建的要顯示玻璃效果的區域的句柄。就像在 Dwm-ExtendFrameIntoClientArea 中將邊距值設置為 -1 一樣,在模糊結構中將 hRgnBlur 參數設置為 null 可以通知 DWM 將玻璃效果應用到整個窗口。
您可能會對最后一個參數 fTransitionOnMaximized 產生誤解。由於在最大化的窗口中關閉了玻璃效果,您會認為該標志與此有關。此標志實際控制當桌面上存在最大化窗口時,窗口是否過渡到最大化的顏色。遺憾的是,如果將此參數設置為 true,則在呈現窗口時,出現的不是透明區域,而只是極光效果。
您可以通過 dwFlags 參數告訴界面您要設置哪些參數;當希望設置一個參數時,需要在 dwFlags 參數中打開相應的位。這在整個 DWM 界面中是一致的。
不要忘記必須將玻璃顏色呈現到該區域。在指定區域創建玻璃效果時,使用與 DwmExtendFrameIntoClientArea 中相同的黑色畫筆效果比較好。
顯而易見,DWM 為您提供了兩個界面,較為復雜的界面可讓您構建任意形狀的區域,簡單的界面僅可讓您將窗口框架的玻璃效果擴展到您的客戶端區域,以便您可以將附加控件和類似內容繪制為類似於標題欄的一部分。在這兩種情況下,令人擔心的情況是,當重新調整窗口的大小和更新呈現玻璃效果的區域(如果該區域不是整個客戶端區域)時,區域的形狀會發生什么變化。本文提供的源代碼可讓您設置擴展的客戶端框架或區域,並能夠切換組合標志。

 

在玻璃區域上繪制
在窗口中使用玻璃效果作為背景需要一些技巧。如果呈現本身不透明的任何內容(如 GDI 函數),則會將您的項目呈現在玻璃區域上,但有時會出現異常效果。如果您希望實際將呈現混合到玻璃界面中,則需要利用能夠使用 Alpha 顏色管道的功能,如 GDI+、Windows Presentation Foundation 或 Windows XP Theme API。
一個特殊問題是使用位模式 0x00000000 以黑色呈現 GDI 項目,在使用 Alpha 管道時也會碰巧出現完全透明的黑色。這意味着如果您使用黑色 GDI 畫筆或筆進行繪制,將會得到透明的顏色,而不是黑色。當您嘗試使用默認文本顏色控制位於玻璃區域中的文本標簽時,這種問題表現得就特別明顯。因為默認文本顏色通常為黑色,DWM 會認為它是透明的,因此文本將錯誤地寫入玻璃區域。 圖 10 顯示了一個這樣的示例。第一行使用 GDI+ 編寫,第二行是一個使用默認顏色的文本標簽控件。可以看出,其中的內容幾乎無法辨認,因為它實際上是錯誤呈現的文件,文本顯示為灰色,而不是黑色。
圖 10  透明對話框 
令人欣慰的是,可以通過多種方法解決此問題。其中一種方法是使用所有者描述的控件。另一種方法是呈現到具有 Alpha 管道的位圖。但控制文本的最簡單方法是讓 .NET Framework 2.0 為您使用 GDI+。通過在您的控件上設置 UseCompatibleTextRendering 屬性可以很容易地做到這一點。默認情況下,此屬性設置為 false,這樣,為 .NET Framework 的以前版本編寫的控件將以相同的方式呈現。但是,如果將其設置為 true,則文本將正確呈現。您可以使用 Application.SetUseCompatibleTextRenderingDefault 方法在全局設置該屬性。如果您使用的是 Visual Studio ® 2005,則模板代碼將包括一個調用,以便在創建窗體之前在主例程中將兼容文本呈現設置為 false。您可以編輯它,將其設置為 true(如下所示),這時在玻璃窗口中進行編寫時,所有控件看上去都會是正確的。
 
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(true);
    Application.Run(new GlassForm());
}
您可以在 Miguel A. Lacouture 在 2006 年 3 月份的《MSDN 雜志》上發表的文章“ Build World-Ready Apps Using Complex Scripts In Windows Forms Controls”中找到有關此內容和使用 TextRenderer 類的詳細信息。
在開始呈現窗口之前應啟用玻璃效果。組合引擎將查看您窗口的 Alpha 值,並將模糊效果應用到透明區域。這在使用某些 GDI 函數時可能會出現問題,因為這些函數不保留 Alpha 值。您可以在需要時使用 GDI+,但應謹慎使用,因為 GDI+ 調用在軟件中呈現,而不是在硬件上呈現,因此窗口刷新頻率較高時使用 GDI+ 調用可能導致耗費大量的系統資源。
可以通過相同方法在 DirectX 應用程序中獲得玻璃效果。您需要做的只是控制呈現目標的 Alpha 值和使用兩個啟用玻璃效果的 DWM 函數之一。無論在何處指示 DWM 使用玻璃效果,它都將使用呈現目標的 Alpha 值。無論在其他任何地方,呈現目標均應為不透明的,否則將得到未定義的行為。

 

縮略圖
縮略圖是打開的應用程序上由 DWM 呈現的實時顯示窗口。縮略圖由 Flip 和 Alt+Tab 任務切換器使用。實質上,您可以請求應用程序窗口的縮略圖,並讓其在應用程序中呈現。縮略圖 API 將為您提供應用程序窗口的實時表示。
縮略圖易於使用,因為 Windows 已為您做好了大部分艱苦工作。困難之處在於獲取應用程序的 HWND。在獲取所需的 HWND 后,您只需注冊一個縮略圖,並將該 HWND 與要呈現縮略圖的 HWND 和該窗口中的位置相關聯。這時操作系統將負責以后的更新。每當源窗口更改時,更改將反映到目標窗口中。
若要使用縮略圖,必須首先使用 DwmRegisterThumbnail 函數注冊縮略圖。您提供了兩個窗口句柄:源 HWND(即需要縮略圖視圖的窗口)和目標 HWND(需要呈現縮略圖的窗口)。在使用縮略圖結束之后,您需要通過調用 DwmUnregisterThumbnail 讓 DWM 知道關系已結束。在創建了縮略圖之后,DwmRegisterThumbnail 函數將返回一個縮略圖句柄,所有其他縮略圖函數將把該句柄作為參數。在注冊縮略圖之后,您需要調用 DwmUpdateThumbnailProperties 才能更新縮略圖。 圖 11 顯示了呈現另一窗口實時縮略圖的窗體示例代碼。
public partial class Thumbnail : Form
{
    private IntPtr m_hThumbnail;

    public Thumbnail() { InitializeComponent(); }

    public void CreateAndShow(IntPtr sourceWindow)
    {
        m_hThumbnail = DwmApi.DwmRegisterThumbnail(
            Handle, sourceWindow);

        DwmApi.DWM_THUMBNAIL_PROPERTIES m_ThumbnailProperties = 
            new DwmApi.DWM_THUMBNAIL_PROPERTIES();
        m_ThumbnailProperties.dwFlags = 
            DwmApi.DWM_THUMBNAIL_PROPERTIES.DWM_TNP_VISIBLE +
            DwmApi.DWM_THUMBNAIL_PROPERTIES.DWM_TNP_OPACITY +
            DwmApi.DWM_THUMBNAIL_PROPERTIES.DWM_TNP_RECTDESTINATION +
            DwmApi.DWM_THUMBNAIL_PROPERTIES.
                DWM_TNP_SOURCECLIENTAREAONLY;
        m_ThumbnailProperties.opacity = 255;
        m_ThumbnailProperties.fVisible = true;
        m_ThumbnailProperties.rcSource = 
            m_ThumbnailProperties.rcDestination = new DwmApi.RECT(0, 0,
                ClientRectangle.Right, ClientRectangle.Bottom);
        m_ThumbnailProperties.fSourceClientAreaOnly = false;

        DwmApi.DwmUpdateThumbnailProperties(
            m_hThumbnail, m_ThumbnailProperties);

        Show();
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null)) components.Dispose();
        base.Dispose(disposing);

        if (m_hThumbnail != IntPtr.Zero)
        {
            if (DwmApi.DwmIsCompositionEnabled()) 
                DwmApi.DwmUnregisterThumbnail(m_hThumbnail);
            m_hThumbnail = IntPtr.Zero;
        }
    }
}
除注冊和取消注冊縮略圖的兩個函數外,還有其他兩個函數與縮略圖一起使用。DwmQueryThumbnailSourceSize 返回指定縮略圖的原始大小。DwmUpdateThumbnailProperties 可讓您更新給定 DWM 縮略圖的屬性。它采用 DWM_THUMBNAIL_PROPERTIES 結構,其 C# 實現如 圖 5 所示。
在不想使用整個源窗口時,使用 DWM_THUMBNAIL_PROPERTIES 結構可以指定屬性的數量,如目標窗口中的目標矩形(rcDestination 成員)和要使用的源窗口的矩形區域(rcSource 成員)。
如果不想讓縮略圖完全不透明,還可以使用不透明度成員指定縮略圖的不透明度,其中 0 為透明,255 為不透明。如果希望縮略圖不可視,可以將 fVisible 標志設置為 false。如果僅希望使用縮略圖窗口的客戶端區域,而不是整個源窗口(其中包括非客戶端區域,如框架和標題欄),則只需將 fSourceClientAreaOnly 布爾值設置為 true。您可以通過 dwFlags 參數告訴界面要設置哪些參數。當設置某個參數時,您需要在 dwFlags 參數中打開相應的位。
最后,在諸如目標窗口大小方面沒有限制,通常使用縮略圖界面擴大和縮小源窗口。在保持長寬比方面存在限制。始終保持源窗口的長寬比。如果更改了源窗口的大小,縮略圖也將更改大小,以保持其本身處於指定的邊界內。
在本文隨附的源代碼中提供了一個按鈕,使用該按鈕將創建主應用程序窗口的微型實時縮略圖,如 圖 12 所示。
您會很容易發現,縮略圖的呈現是實時的。如果更改主應用程序窗口,則會看到縮略圖更新。您會發現,稍微發揮一下自己的聰明才智,就可以相當容易地使用縮略圖以及 FindWindow 和 GetWindow Win32 函數創建自己的任務切換器。
圖 12a  創建實時縮略圖 
圖 12b
 
總結
本文簡要概述了 DWM 界面。我認為您將會發現一些適用於這些 API 的優秀應用程序。尤其是,隨着人們習慣於將玻璃效果應用於自己的窗口,預計將來會有一些使用玻璃效果的非常吸引人的應用程序。若要了解詳細信息,建議您訪問 Greg Schechter 的博客,位置是: blogs.msdn.com/greg_schechter

 

小生在這只是起到搬運工的作用

原文地址:http://msdn.microsoft.com/zh-cn/magazine/cc163435.aspx#S4 

/****************************邪惡的分割線*******************************/ 

在這小生獻上實現的一段代碼

以下代碼實現了玻璃效果只需將代碼拷入然后構造函數里調用方法dwmInitialize();即可.

需添加命名空間using System.Runtime.InteropServices;

補充一點,將窗體樣式設置成None,通過以下代碼可以看到半透明窗體的效果.

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPaintBackground(PaintEventArgs e)
        {
            base.OnPaintBackground(e);
            e.Graphics.Clear(Color.FromArgb(100, 10, 10, 10));
        }
        /// <summary>
        /// dwm初始化
        /// </summary>
        private void dwmInitialize()
        {
            #region[dwmInitialize]
            int flag = 0;
            MARGINS mg = new MARGINS();
            mg.m_Buttom = -1;// 100;
            mg.m_Left = -1;// 100;
            mg.m_Right = -1;//100;
            mg.m_Top = -1;// 100;
            //判斷Vista系統
            if (System.Environment.OSVersion.Version.Major >= 6)
            {
                DwmIsCompositionEnabled(ref flag);    //檢測Aero是否為打開
                if (flag > 0)
                {
                    DwmExtendFrameIntoClientArea(this.Handle, ref mg);
                }
                else
                {
                    MessageBox.Show("Desktop Composition is Disabled!");
                }
            }
            else
            {
                MessageBox.Show("Please run this on Windows Vista.");
            }
            #endregion
        }
        /// <summary>
        /// 
        /// </summary>
        public struct MARGINS
        {
            public int m_Left;
            public int m_Right;
            public int m_Top;
            public int m_Buttom;
        };        
        [DllImport("dwmapi.dll")]
        private static extern void DwmIsCompositionEnabled(ref int enabledptr);
        [DllImport("dwmapi.dll", PreserveSig = false)]
        public static extern bool DwmIsCompositionEnabled();
        [DllImport("dwmapi.dll")]
        private static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margin);

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM