使用DWM實現Aero Glass效果

來源:互聯網
上載者:User

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

一、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關閉時繪製為灰色:

case WM_PAINT:<br />{<br />PAINTSTRUCT ps;<br />HDC hDC = BeginPaint(hWnd, &ps);<br />//不要直接使用視窗控制代碼建立Graphics,會導致閃爍<br />Graphics graph(hDC);<br />//清除用戶端區域<br />RECT rcClient;<br />GetClientRect(hWnd, &rcClient);<br />BOOL bCompEnabled;<br />DwmIsCompositionEnabled(&bCompEnabled);<br />SolidBrush br(bCompEnabled? Color::Black : Color::DarkGray);<br />graph.FillRectangle(&br, Rect(rcClient.left, rcClient.top,<br />rcClient.right, rcClient.bottom));<br />EndPaint(hWnd, &ps);<br />}<br />break;

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

//初始化GDI+<br />ULONG_PTR token;<br />GdiplusStartupInput input;<br />GdiplusStartup(&token, &input, NULL);<br />//*********************************<br />//關閉GDI+<br />GdiplusShutdown(token);

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

DWM_BLURBEHIND bb = {0};<br />bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;<br />bb.fEnable = true;<br />bb.hRgnBlur = NULL;<br />DwmEnableBlurBehindWindow(hWnd, &bb);

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

RECT rect;<br />GetWindowRect(hWnd, &rect);<br />int width = 300, height = 200;<br />//置中橢圓形<br />HRGN hRgn = CreateEllipticRgn((rect.right - rect.left)/2 - width/2,<br />(rect.bottom - rect.top)/2 - height/2, (rect.right - rect.left)/2 + width/2,<br />(rect.bottom - rect.top)/2 + height/2);<br />DWM_BLURBEHIND bb = {0};<br />bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;<br />bb.fEnable = true;<br />bb.hRgnBlur = hRgn;<br />DwmEnableBlurBehindWindow(hWnd, &bb);

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

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

MARGINS margins = {50, 50, 50, 50};<br />DwmExtendFrameIntoClientArea(hWnd, &margins);

MARGINS margins2 = {-1};//將擴充到整個客戶區<br />DwmExtendFrameIntoClientArea(hWnd, &margins2);

五、在視窗上繪製圖形

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

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

      在WM_PAINT訊息處理中,將整個客戶區繪製為黑色以後,利用GDI+將圖片繪製到視窗客戶區:

//繪製圖形<br />int width = bmp->GetWidth();<br />int height = bmp->GetHeight();<br />Rect rc(30, 30, width, height);<br />graph.DrawImage(bmp, rc, 0, 0, width, height, UnitPixel);

六、文本的繪製

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

HRESULT DrawThemeTextEx( HTHEME hTheme,<br /> HDC hdc,<br /> int iPartId,<br /> int iStateId,<br /> LPCWSTR pszText,<br /> int iCharCount,<br /> DWORD dwFlags,<br /> LPRECT pRect,<br /> const DTTOPTS *pOptions<br />);

      hTheme是一個主題控制代碼,可以使用OpenThemeData
獲得,
OpenThemeData

函數接受一個視窗控制代碼,和主題類的名稱。iPartId和iStateId分別代表主題類中的Part和State,所有可用的主題類、Part和state在SDK的協助文檔中可以查看到。pszText是要繪製的文本。iCharCount為文字個數,-1代表繪製全部文本。dwFlags指定文字格式設定。pRect為文本繪製地區。pOptions中可以設定文本的發光、陰影等效果。HDC是一個裝置上下文控制代碼,為了實作類別似於標題列中文本的光暈效果,這裡不能使用由BeginPaint
得到的控制代碼,而是要使用CreateCompatibleDC
建立一個記憶體中的控制代碼,並且要建立一張位元影像,通過記憶體控制代碼將文本繪製到位元影像上。然後再將位元影像轉移到視窗上。下面的函數封裝了繪製發光文本的過程:

//繪製發光文字<br />void DrawGlowingText(HDC hDC, LPWSTR szText, RECT &rcArea,<br />DWORD dwTextFlags = DT_LEFT | DT_VCENTER | DT_SINGLELINE, int iGlowSize = 10)<br />{<br />//擷取主題控制代碼<br />HTHEME hThm = OpenThemeData(GetDesktopWindow(), L"TextStyle");<br />//建立DIB<br />HDC hMemDC = CreateCompatibleDC(hDC);<br />BITMAPINFO bmpinfo = {0};<br />bmpinfo.bmiHeader.biSize = sizeof(bmpinfo.bmiHeader);<br />bmpinfo.bmiHeader.biBitCount = 32;<br />bmpinfo.bmiHeader.biCompression = BI_RGB;<br />bmpinfo.bmiHeader.biPlanes = 1;<br />bmpinfo.bmiHeader.biWidth = rcArea.right - rcArea.left;<br />bmpinfo.bmiHeader.biHeight = -(rcArea.bottom - rcArea.top);<br />HBITMAP hBmp = CreateDIBSection(hMemDC, &bmpinfo, DIB_RGB_COLORS, 0, NULL, 0);<br />if (hBmp == NULL) return;<br />HGDIOBJ hBmpOld = SelectObject(hMemDC, hBmp);<br />//繪製選項<br />DTTOPTS dttopts = {0};<br />dttopts.dwSize = sizeof(DTTOPTS);<br />dttopts.dwFlags = DTT_GLOWSIZE | DTT_COMPOSITED;<br />dttopts.iGlowSize = iGlowSize;//發光的範圍大小<br />//繪製文本<br />RECT rc = {0, 0, rcArea.right - rcArea.left, rcArea.bottom - rcArea.top};<br />HRESULT hr = DrawThemeTextEx(hThm, hMemDC, TEXT_LABEL, 0, szText, -1, dwTextFlags , &rc, &dttopts);<br />if(FAILED(hr)) return;<br />BitBlt(hDC, rcArea.left, rcArea.top, rcArea.right - rcArea.left,<br />rcArea.bottom - rcArea.top, hMemDC, 0, 0, SRCCOPY | CAPTUREBLT);<br />//Clear<br />SelectObject(hMemDC, hBmpOld);<br />DeleteObject(hBmp);<br />DeleteDC(hMemDC);<br />CloseThemeData(hThm);<br />}

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

//繪製文本<br />RECT rcText = {10, 10, 300, 40};<br />DrawGlowingText(hDC, L" 一點點中文 and some english", rcText);

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

七、縮圖關聯

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

HRESULT hr = S_OK;<br />HTHUMBNAIL thumbnail = NULL;<br />HWND hWndSrc = FindWindow(_T("QQPlayer Window"), NULL);<br />hr = DwmRegisterThumbnail(hWnd, hWndSrc, &thumbnail);<br />if (SUCCEEDED(hr))<br />{<br />RECT rc;<br />GetClientRect(hWnd, &rc);<br />DWM_THUMBNAIL_PROPERTIES dskThumbProps;<br />dskThumbProps.dwFlags = DWM_TNP_RECTDESTINATION | DWM_TNP_VISIBLE | DWM_TNP_OPACITY ;<br />dskThumbProps.fVisible = TRUE;<br />dskThumbProps.opacity = 200;<br />dskThumbProps.rcDestination = rc;<br />hr = DwmUpdateThumbnailProperties(thumbnail,&dskThumbProps);<br />}

       首先通過視窗標題尋找到源視窗控制代碼,然後使用DwmRegisterThumbnail
註冊縮圖關聯,註冊成功後,通過DwmUpdateThumbnailProperties
更新縮圖屬性,其中設定了是否可視、透明度以及目標繪製地區。得到下面的效果:

 

原始碼下載

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.