從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 ?
- case WM_PAINT:
- {
- PAINTSTRUCT ps;
- HDC hDC = BeginPaint(hWnd, &ps);
- //不要直接使用視窗句柄建立Graphics,會導緻閃爍
- Graphics graph(hDC);
- //清除客戶區域
- RECT rcClient;
- GetClientRect(hWnd, &rcClient);
- BOOL bCompEnabled;
- DwmIsCompositionEnabled(&bCompEnabled);
- SolidBrush br(bCompEnabled? Color::Black : Color::DarkGray);
- graph.FillRectangle(&br, Rect(rcClient.left, rcClient.top,
- rcClient.right, rcClient.bottom));
- EndPaint(hWnd, &ps);
- }
- break;
GDI+的初始化和關閉仍然是必須的:
[cpp] view plain copy print ?
- //初始化GDI+
- ULONG_PTR token;
- GdiplusStartupInput input;
- GdiplusStartup(&token, &input, NULL);
- //*********************************
- //關閉GDI+
- GdiplusShutdown(token);
下面代碼将整個客戶區設定為Glass效果:
[cpp] view plain copy print ?
- DWM_BLURBEHIND bb = {0};
- bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
- bb.fEnable = true;
- bb.hRgnBlur = NULL;
- DwmEnableBlurBehindWindow(hWnd, &bb);
下面代碼将客戶區中心一個橢圓的區域設定為Glass效果:
[cpp] view plain copy print ?
- RECT rect;
- GetWindowRect(hWnd, &rect);
- int width = 300, height = 200;
- //居中橢圓形
- HRGN hRgn = CreateEllipticRgn((rect.right - rect.left)/2 - width/2,
- (rect.bottom - rect.top)/2 - height/2, (rect.right - rect.left)/2 + width/2,
- (rect.bottom - rect.top)/2 + height/2);
- DWM_BLURBEHIND bb = {0};
- bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
- bb.fEnable = true;
- bb.hRgnBlur = hRgn;
- DwmEnableBlurBehindWindow(hWnd, &bb);
四、視窗邊框向客戶區擴充
上面的方式中,非客戶區和客戶區之間仍然有界限。如何增大Glass效果的範圍,并且消除界限呢?那就是使視窗邊框向客戶區擴充,利用函數DwmExtendFrameIntoClientArea實作。函數接受一個視窗句柄和一個MARGINS類型的參數。MARGINS指定了在上下左右4個方向上擴充的範圍。如果4個值均為-1,則擴充到整個客戶區。
[cpp] view plain copy print ?
- MARGINS margins = {50, 50, 50, 50};
- DwmExtendFrameIntoClientArea(hWnd, &margins);
[cpp] view plain copy print ?
- MARGINS margins2 = {-1}; //将擴充到整個客戶區
- DwmExtendFrameIntoClientArea(hWnd, &margins2);
五、在視窗上繪制圖形
PNG圖檔帶有alpha通道,可以與Aero Glass很好的配合。利用GDI+顯示PNG圖檔非常友善,下面的代碼将一張PNG圖檔加載到記憶體中:
[cpp] view plain copy print ?
- Bitmap bmp = Bitmap::FromFile(L"Ferrari.png", false);
在WM_PAINT消息進行中,将整個客戶區繪制為黑色以後,利用GDI+将圖檔繪制到視窗客戶區:
[cpp] view plain copy print ?
- //繪制圖形
- int width = bmp->GetWidth();
- int height = bmp->GetHeight();
- Rect rc(30, 30, width, height);
- graph.DrawImage(bmp, rc, 0, 0, width, height, UnitPixel);
六、文本的繪制
當視窗大範圍的透明之後,視窗上的文字的閱讀成了一個問題。Windows的解決辦法是為文字加上發光效果(Glowing),标題欄的文本使用的就是這種方式。我們在自己的程式中可以使用DrawThemeTextEx函數來繪制發光的文字。該函數的原型定義如下:
[cpp] view plain copy print ?
- HRESULT DrawThemeTextEx( HTHEME hTheme,
- HDC hdc,
- int iPartId,
- int iStateId,
- LPCWSTR pszText,
- int iCharCount,
- DWORD dwFlags,
- LPRECT pRect,
- const DTTOPTS *pOptions
- );
hTheme是一個主題句柄,可以使用OpenThemeData獲得,OpenThemeData函數接受一個視窗句柄,和主題類的名稱。iPartId和iStateId分别代表主題類中的Part和State,所有可用的主題類、Part和state在SDK的幫助文檔中可以檢視到。pszText是要繪制的文本。iCharCount為文字個數,-1代表繪制全部文本。dwFlags指定文本格式。pRect為文本繪制區域。pOptions中可以設定文本的發光、陰影等效果。HDC是一個裝置上下文句柄,為了實作類似于标題欄中文本的發光效果,這裡不能使用由BeginPaint得到的句柄,而是要使用CreateCompatibleDC建立一個記憶體中的句柄,并且要建立一張位圖,通過記憶體句柄将文本繪制到位圖上。然後再将位圖轉移到視窗上。下面的函數封裝了繪制發光文本的過程:
[cpp] view plain copy print ?
- //繪制發光文字
- void DrawGlowingText(HDC hDC, LPWSTR szText, RECT &rcArea,
- DWORD dwTextFlags = DT_LEFT | DT_VCENTER | DT_SINGLELINE, int iGlowSize = 10)
- {
- //擷取主題句柄
- HTHEME hThm = OpenThemeData(GetDesktopWindow(), L"TextStyle");
- //建立DIB
- HDC hMemDC = CreateCompatibleDC(hDC);
- BITMAPINFO bmpinfo = {0};
- bmpinfo.bmiHeader.biSize = sizeof(bmpinfo.bmiHeader);
- bmpinfo.bmiHeader.biBitCount = 32;
- bmpinfo.bmiHeader.biCompression = BI_RGB;
- bmpinfo.bmiHeader.biPlanes = 1;
- bmpinfo.bmiHeader.biWidth = rcArea.right - rcArea.left;
- bmpinfo.bmiHeader.biHeight = -(rcArea.bottom - rcArea.top);
- HBITMAP hBmp = CreateDIBSection(hMemDC, &bmpinfo, DIB_RGB_COLORS, 0, NULL, 0);
- if (hBmp == NULL) return;
- HGDIOBJ hBmpOld = SelectObject(hMemDC, hBmp);
- //繪制選項
- DTTOPTS dttopts = {0};
- dttopts.dwSize = sizeof(DTTOPTS);
- dttopts.dwFlags = DTT_GLOWSIZE | DTT_COMPOSITED;
- dttopts.iGlowSize = iGlowSize; //發光的範圍大小
- //繪制文本
- RECT rc = {0, 0, rcArea.right - rcArea.left, rcArea.bottom - rcArea.top};
- HRESULT hr = DrawThemeTextEx(hThm, hMemDC, TEXT_LABEL, 0, szText, -1, dwTextFlags , &rc, &dttopts);
- if(FAILED(hr)) return;
- BitBlt(hDC, rcArea.left, rcArea.top, rcArea.right - rcArea.left,
- rcArea.bottom - rcArea.top, hMemDC, 0, 0, SRCCOPY | CAPTUREBLT);
- //Clear
- SelectObject(hMemDC, hBmpOld);
- DeleteObject(hBmp);
- DeleteDC(hMemDC);
- CloseThemeData(hThm);
- }
在繪制了圖形後,加入下面代碼繪制一段文本:
[cpp] view plain copy print ?
- //繪制文本
- RECT rcText = {10, 10, 300, 40};
- DrawGlowingText(hDC, L" 一點點中文 and some english", rcText);
因為字型發光的緣故,在文本左側留下一個空格看起來會舒服一些。效果如下:
七、縮略圖關聯
DWM API中還有一個功能,即縮略圖關聯。它允許我們将一個視窗的縮略圖顯示到自己視窗的客戶區。縮略圖不同于截圖,它是實時更新的。下面的代碼将在視窗客戶區顯示QQ影音播放器的縮略圖:
[cpp] view plain copy print ?
- HRESULT hr = S_OK;
- HTHUMBNAIL thumbnail = NULL;
- HWND hWndSrc = FindWindow(_T("QQPlayer Window"), NULL);
- hr = DwmRegisterThumbnail(hWnd, hWndSrc, &thumbnail);
- if (SUCCEEDED(hr))
- {
- RECT rc;
- GetClientRect(hWnd, &rc);
- DWM_THUMBNAIL_PROPERTIES dskThumbProps;
- dskThumbProps.dwFlags = DWM_TNP_RECTDESTINATION | DWM_TNP_VISIBLE | DWM_TNP_OPACITY ;
- dskThumbProps.fVisible = TRUE;
- dskThumbProps.opacity = 200;
- dskThumbProps.rcDestination = rc;
- hr = DwmUpdateThumbnailProperties(thumbnail,&dskThumbProps);
- }
首先通過視窗标題查找到源視窗句柄,然後使用DwmRegisterThumbnail注冊縮略圖關聯,注冊成功後,通過DwmUpdateThumbnailProperties更新縮略圖屬性,其中設定了是否可視、透明度以及目标繪制區域。得到下面的效果: