天天看點

使用DWM實作Aero Glass效果

從Windows Vista開始,Aero Glass效果被應用在了Home Premium以上的系統中(Home Basic不具有該效果)。這種效果是由DWM(Desktop Window Manager)來控制的。對于一般的程式,預設将在視窗邊框應用這種效果。但如果我們想要更多的控制,比如讓客戶區的一部分也呈現這種效果,那也非常的簡單。不需要我們在程式裡做任何複雜的算法,我們隻需要調API,交給DWM去做就可以了。

DWM相關操作的MSDN說明:Desktop Window Manager (DWM) APIs.

Header Dwmapi.h 

Library Dwmapi.lib 

DLL Dwmapi.dll 

一、Composition(視窗合成) and Non-client Rendering(非客戶區渲染)

       非客戶區通常包括視窗标題欄和視窗邊框。預設狀态下,非客戶區會被渲染成毛玻璃效果,這也稱為Compostion。有幾個函數可以控制系統和目前視窗的渲染方式。同時也有Windows消息用于接受渲染模式的改變。

       1.檢測系統是否開啟Aero Glass。使用函數DwmIsCompositionEnabled檢測系統目前是否開啟了Aero Glass特效。它接受一個BOOL參數,并将目前狀态存儲到其中。函數原型:HRESULT DwmIsCompositionEnabled(BOOL *pfEnabled);

       2.開啟/關閉Aero Glass。使用函數DwmEnableComposition開啟或關閉系統Aero Glass效果,傳入DWM_EC_ENABLECOMPOSITION開啟,傳入DWM_EC_DISABLECOMPOSITION關閉。

       3.開啟/關閉目前視窗的非客戶區渲染。函數DwmSetWindowAttribute用于設定視窗屬性,屬性DWMWA_NCRENDERING_POLICY控制目前視窗是否使用非客戶區渲染。DWMNCRP_ENABLED開啟,DWMNCRP_DISABLED關閉。當系統的Aero Glass關閉時,設定無效。與之對應,使用函數DwmGetWindowAttribute可以檢測目前視窗屬性。

       4.響應系統Aero Glass的開啟或關閉。當Aero Glass被開啟或關閉時,Windows會發送消息WM_DWMCOMPOSITIONCHANGED,使用函數DwmIsCompositionEnabled檢測狀态。

       5.響應視窗非客戶區渲染的開啟或關閉。目前視窗的非客戶區渲染開啟或關閉時,Windows會發送消息WM_DWMNCRENDERINGCHANGED,wParam訓示目前狀态。

二、Transition(視窗動畫) and ColorizationColor(主題顔色)

       Transition控制是否以動畫方式顯示視窗的最小化和還原。通過使用函數DwmSetWindowAttribute,設定屬性DWMWA_TRANSITIONS_FORCEDISABLED,開啟或關閉視窗動畫。該設定隻對目前視窗有效。

       當使用者通過控制台修改主題顔色時,Windows将發送消息WM_DWMCOLORIZATIONCOLORCHANGED,程式中通過函數DwmGetColorizationColor取得目前主題顔色,以及是否透明。通過響應顔色的變更,可以讓程式的顔色風格随主題風格而變化。

三、開啟客戶區域Aero Glass效果

       函數DwmEnableBlurBehindWindow開啟客戶區的Aero Glass效果,第一個參數為視窗句柄,第二個參數為一個DWM_BLURBEHIND結構。其中fEnable設定是否開啟客戶區Glass效果。hRgnBlur設定Glass效果的區域,該項設定為NULL将使整個客戶區呈現Glass效果,設定為一個正确的區域後,該區域将呈現Glass效果, 而區域以外為完全透明。要呈現透明效果需要客戶區原始的顔色為黑色,可以在WM_PAINT消息中繪制客戶區,下面的代碼使用GDI+,在Aero Glass開啟時将整個視窗繪制為黑色,Aero Glass關閉時繪制為灰色:

[cpp] view plain copy print ?

  1. case WM_PAINT:  
  2.     {  
  3.         PAINTSTRUCT ps;  
  4.         HDC hDC = BeginPaint(hWnd, &ps);  
  5.         //不要直接使用視窗句柄建立Graphics,會導緻閃爍  
  6.         Graphics graph(hDC);  
  7.         //清除客戶區域  
  8.         RECT rcClient;  
  9.         GetClientRect(hWnd, &rcClient);  
  10.         BOOL bCompEnabled;  
  11.         DwmIsCompositionEnabled(&bCompEnabled);  
  12.         SolidBrush br(bCompEnabled? Color::Black : Color::DarkGray);  
  13.         graph.FillRectangle(&br, Rect(rcClient.left, rcClient.top,   
  14.             rcClient.right, rcClient.bottom));  
  15.         EndPaint(hWnd, &ps);  
  16.     }  
  17.     break;  

        GDI+的初始化和關閉仍然是必須的:

[cpp] view plain copy print ?

  1. //初始化GDI+  
  2. ULONG_PTR token;  
  3. GdiplusStartupInput input;  
  4. GdiplusStartup(&token, &input, NULL);  
  5. //*********************************  
  6. //關閉GDI+  
  7. GdiplusShutdown(token);  

       下面代碼将整個客戶區設定為Glass效果:

[cpp] view plain copy print ?

  1. DWM_BLURBEHIND bb = {0};  
  2. bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;  
  3. bb.fEnable = true;  
  4. bb.hRgnBlur = NULL;  
  5. DwmEnableBlurBehindWindow(hWnd, &bb);  
使用DWM實作Aero Glass效果

      下面代碼将客戶區中心一個橢圓的區域設定為Glass效果:

[cpp] view plain copy print ?

  1. RECT rect;  
  2. GetWindowRect(hWnd, &rect);  
  3. int width = 300, height = 200;  
  4. //居中橢圓形  
  5. HRGN hRgn = CreateEllipticRgn((rect.right - rect.left)/2 - width/2,   
  6.     (rect.bottom - rect.top)/2 - height/2, (rect.right - rect.left)/2 + width/2,   
  7.     (rect.bottom - rect.top)/2 + height/2);  
  8. DWM_BLURBEHIND bb = {0};  
  9. bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;  
  10. bb.fEnable = true;  
  11. bb.hRgnBlur = hRgn;  
  12. DwmEnableBlurBehindWindow(hWnd, &bb);  
使用DWM實作Aero Glass效果

四、視窗邊框向客戶區擴充

      上面的方式中,非客戶區和客戶區之間仍然有界限。如何增大Glass效果的範圍,并且消除界限呢?那就是使視窗邊框向客戶區擴充,利用函數DwmExtendFrameIntoClientArea實作。函數接受一個視窗句柄和一個MARGINS類型的參數。MARGINS指定了在上下左右4個方向上擴充的範圍。如果4個值均為-1,則擴充到整個客戶區。

[cpp] view plain copy print ?

  1. MARGINS margins = {50, 50, 50, 50};  
  2. DwmExtendFrameIntoClientArea(hWnd, &margins);  
使用DWM實作Aero Glass效果

[cpp] view plain copy print ?

  1. MARGINS margins2 = {-1};    //将擴充到整個客戶區  
  2. DwmExtendFrameIntoClientArea(hWnd, &margins2);  
使用DWM實作Aero Glass效果

五、在視窗上繪制圖形

      PNG圖檔帶有alpha通道,可以與Aero Glass很好的配合。利用GDI+顯示PNG圖檔非常友善,下面的代碼将一張PNG圖檔加載到記憶體中:

[cpp] view plain copy print ?

  1. Bitmap bmp  = Bitmap::FromFile(L"Ferrari.png", false);  

      在WM_PAINT消息進行中,将整個客戶區繪制為黑色以後,利用GDI+将圖檔繪制到視窗客戶區:

[cpp] view plain copy print ?

  1. //繪制圖形  
  2. int width = bmp->GetWidth();  
  3. int height = bmp->GetHeight();  
  4. Rect rc(30, 30, width, height);  
  5. graph.DrawImage(bmp, rc, 0, 0, width, height, UnitPixel);  
使用DWM實作Aero Glass效果

六、文本的繪制

      當視窗大範圍的透明之後,視窗上的文字的閱讀成了一個問題。Windows的解決辦法是為文字加上發光效果(Glowing),标題欄的文本使用的就是這種方式。我們在自己的程式中可以使用DrawThemeTextEx函數來繪制發光的文字。該函數的原型定義如下:

[cpp] view plain copy print ?

  1. HRESULT DrawThemeTextEx(          HTHEME hTheme,  
  2.     HDC hdc,  
  3.     int iPartId,  
  4.     int iStateId,  
  5.     LPCWSTR pszText,  
  6.     int iCharCount,  
  7.     DWORD dwFlags,  
  8.     LPRECT pRect,  
  9.     const DTTOPTS *pOptions  
  10. );  

      hTheme是一個主題句柄,可以使用OpenThemeData獲得,OpenThemeData函數接受一個視窗句柄,和主題類的名稱。iPartId和iStateId分别代表主題類中的Part和State,所有可用的主題類、Part和state在SDK的幫助文檔中可以檢視到。pszText是要繪制的文本。iCharCount為文字個數,-1代表繪制全部文本。dwFlags指定文本格式。pRect為文本繪制區域。pOptions中可以設定文本的發光、陰影等效果。HDC是一個裝置上下文句柄,為了實作類似于标題欄中文本的發光效果,這裡不能使用由BeginPaint得到的句柄,而是要使用CreateCompatibleDC建立一個記憶體中的句柄,并且要建立一張位圖,通過記憶體句柄将文本繪制到位圖上。然後再将位圖轉移到視窗上。下面的函數封裝了繪制發光文本的過程:

[cpp] view plain copy print ?

  1. //繪制發光文字  
  2. void DrawGlowingText(HDC hDC, LPWSTR szText, RECT &rcArea,   
  3.     DWORD dwTextFlags = DT_LEFT | DT_VCENTER | DT_SINGLELINE, int iGlowSize = 10)  
  4. {  
  5.     //擷取主題句柄  
  6.     HTHEME hThm = OpenThemeData(GetDesktopWindow(), L"TextStyle");  
  7.     //建立DIB  
  8.     HDC hMemDC = CreateCompatibleDC(hDC);  
  9.     BITMAPINFO bmpinfo = {0};  
  10.     bmpinfo.bmiHeader.biSize = sizeof(bmpinfo.bmiHeader);  
  11.     bmpinfo.bmiHeader.biBitCount = 32;  
  12.     bmpinfo.bmiHeader.biCompression = BI_RGB;  
  13.     bmpinfo.bmiHeader.biPlanes = 1;  
  14.     bmpinfo.bmiHeader.biWidth = rcArea.right - rcArea.left;  
  15.     bmpinfo.bmiHeader.biHeight = -(rcArea.bottom - rcArea.top);  
  16.     HBITMAP hBmp = CreateDIBSection(hMemDC, &bmpinfo, DIB_RGB_COLORS, 0, NULL, 0);  
  17.     if (hBmp == NULL) return;  
  18.     HGDIOBJ hBmpOld = SelectObject(hMemDC, hBmp);  
  19.     //繪制選項  
  20.     DTTOPTS dttopts = {0};  
  21.     dttopts.dwSize = sizeof(DTTOPTS);  
  22.     dttopts.dwFlags = DTT_GLOWSIZE | DTT_COMPOSITED;  
  23.     dttopts.iGlowSize = iGlowSize;  //發光的範圍大小  
  24.     //繪制文本  
  25.     RECT rc = {0, 0, rcArea.right - rcArea.left, rcArea.bottom - rcArea.top};  
  26.     HRESULT hr = DrawThemeTextEx(hThm, hMemDC, TEXT_LABEL, 0, szText, -1, dwTextFlags , &rc, &dttopts);  
  27.     if(FAILED(hr)) return;  
  28.     BitBlt(hDC, rcArea.left, rcArea.top, rcArea.right - rcArea.left,   
  29.         rcArea.bottom - rcArea.top, hMemDC, 0, 0, SRCCOPY | CAPTUREBLT);  
  30.     //Clear  
  31.     SelectObject(hMemDC, hBmpOld);  
  32.     DeleteObject(hBmp);  
  33.     DeleteDC(hMemDC);  
  34.     CloseThemeData(hThm);  
  35. }  

      在繪制了圖形後,加入下面代碼繪制一段文本:

[cpp] view plain copy print ?

  1. //繪制文本  
  2. RECT rcText = {10, 10, 300, 40};  
  3. DrawGlowingText(hDC, L"  一點點中文 and some english", rcText);  

     因為字型發光的緣故,在文本左側留下一個空格看起來會舒服一些。效果如下:

使用DWM實作Aero Glass效果

七、縮略圖關聯

       DWM API中還有一個功能,即縮略圖關聯。它允許我們将一個視窗的縮略圖顯示到自己視窗的客戶區。縮略圖不同于截圖,它是實時更新的。下面的代碼将在視窗客戶區顯示QQ影音播放器的縮略圖:

[cpp] view plain copy print ?

  1. HRESULT hr = S_OK;  
  2. HTHUMBNAIL thumbnail = NULL;  
  3. HWND hWndSrc = FindWindow(_T("QQPlayer Window"), NULL);  
  4. hr = DwmRegisterThumbnail(hWnd, hWndSrc, &thumbnail);  
  5. if (SUCCEEDED(hr))  
  6. {  
  7.     RECT rc;  
  8.     GetClientRect(hWnd, &rc);  
  9.     DWM_THUMBNAIL_PROPERTIES dskThumbProps;  
  10.     dskThumbProps.dwFlags = DWM_TNP_RECTDESTINATION | DWM_TNP_VISIBLE | DWM_TNP_OPACITY ;  
  11.     dskThumbProps.fVisible = TRUE;  
  12.     dskThumbProps.opacity = 200;  
  13.     dskThumbProps.rcDestination = rc;  
  14.     hr = DwmUpdateThumbnailProperties(thumbnail,&dskThumbProps);  
  15. }  

       首先通過視窗标題查找到源視窗句柄,然後使用DwmRegisterThumbnail注冊縮略圖關聯,注冊成功後,通過DwmUpdateThumbnailProperties更新縮略圖屬性,其中設定了是否可視、透明度以及目标繪制區域。得到下面的效果:

使用DWM實作Aero Glass效果