搞Windows程序的人盡皆知分層窗口能夠實現很多不錯的效果,之前看過一些異形窗口的實現,所以就手癢也想自己搞一個玩一玩。自己動手實現過程才發現還是有不少問題的。
基本思路是:
1.將窗口擴展屬性設置為分層屬性WS_EX_LAYERED。
2.選一張透明的png圖片,并將其加載進來。
3.創建與窗口兼容的內存設備上下文,以及兼容位圖,將兼容位圖選入兼容設備上下文。
4.將png圖片繪制到內存設備上下文中。
5.設置BLENDFUNCTION結構,調用UpdateLayeredWindow。
第一步設置窗口的分層屬性比較簡單:
windowStyle = GetWindowLong(hWnd, GWL_EXSTYLE); windowStyle = windowStyle | WS_EX_LAYERED; SetWindowLong(hWnd, GWL_EXSTYLE, windowStyle);
第二步是將png圖片加載到程序中,ATL的CImage和GDI+的Image這兩個類比較常用。使用CImage直接通過Load方法加載絕對路徑圖片或者內存中的圖片,我這里就是使用CImage類實現。代碼:
// CImage類方式加載圖片 CImage img; img.Load(TEXT( "絕對路徑 png圖片 " )); // 將圖片與類關聯起來
然后是使用Image類方式:
// Image類方式加載圖片 Image* pImage = Image::FromFile(_T( " 絕對路徑png圖片 " ));
第三步比較繁瑣點。加載了圖片后就需要創建一個位圖句柄HBITMAP,創建位圖句柄有兩種方式:CreateCompatibleBitmap和CreateDIBSection這兩個函數。先介紹下這兩個函數。
HBITMAP CreateCompatibleBitmap( HDC hdc, // handle to DC int nWidth, // width of bitmap, in pixels int nHeight // height of bitmap, in pixels);
HBITMAP CreateDIBSection( HDC hdc, // handle to DC CONST BITMAPINFO *pbmi, // bitmap data UINT iUsage, // data type indicator VOID **ppvBits, // bit values HANDLE hSection, // handle to file mapping object DWORD dwOffset // offset to bitmap bit values );
首先看看CreateCompatibleBitmap函數的代碼:
// CreateCompatibleBitmap函數創建 hdc = GetDC(hWnd); // hWnd為需要分層窗口的句柄 hdcMem = CreateCompatibleDC(hdc); // 創建與hdc相兼容的內存句柄 hBitmap = CreateCompatibleBitmap(hdc, sz.cx, sz.cy); // 創建與hdc相兼容的位圖句柄 SelectObject(hdcMem,(HGDIOBJ)hBitmap); // 將位圖選入內存句柄作為畫板
然后是使用CreateDIBSection函數的代碼:
// CreateDIBSection函數創建 hdc = GetDC(hWnd); hdcMem = CreateCompatibleDC(hdc); BITMAPINFO bitmapinfo; bitmapinfo.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); bitmapinfo.bmiHeader.biBitCount = 32 ; bitmapinfo.bmiHeader.biHeight = sz.cy; bitmapinfo.bmiHeader.biWidth = sz.cx; bitmapinfo.bmiHeader.biPlanes = 1 ; bitmapinfo.bmiHeader.biCompression = BI_RGB; bitmapinfo.bmiHeader.biXPelsPerMeter = 0 ; bitmapinfo.bmiHeader.biYPelsPerMeter = 0 ; bitmapinfo.bmiHeader.biClrUsed = 0 ; bitmapinfo.bmiHeader.biClrImportant = 0 ; bitmapinfo.bmiHeader.biSizeImage = bitmapinfo.bmiHeader.biWidth * bitmapinfo.bmiHeader.biHeight * bitmapinfo.bmiHeader.biBitCount / 8 ; hBitmap = ::CreateDIBSection(hdcMem,&bitmapinfo, 0 ,NULL, 0 , 0 ); SelectObject(hdcMem,(HGDIOBJ)hBitmap);
第四步,將png圖片繪制到內存設備上下文中。這一步根據前面加載圖片的兩種方式對應不同的繪制函數。
CImage類方式代碼:
// CImage類方式代碼 // 將img關聯的png圖片繪制到內存句柄中 img.Draw(hdcMem, 0 , 0 , sz.cx, sz.cy, 0 , 0 , sz.cx, sz.cy); // hdcMem為內存兼容句柄,之后四個參數表明了在內存兼容句柄中繪制的位置,最后四個參數表示了img關聯的png圖片的位置
然后是Image類方式處理代碼:
// Image類方式代碼 Graphics g(hMemDC); g.DrawImage( pImage, 0 , 0 );
第五步:設置BLENDFUNCTION結構,調用UpdateLayeredWindow。
設置BLENDFUNCTION結構代碼如下:
BLENDFUNCTION bf; bf.AlphaFormat = AC_SRC_ALPHA; // 源位圖具有Alpha通道 bf.BlendFlags = 0 ; // 必須為0 bf.BlendOp = AC_SRC_OVER; // bf.SourceConstantAlpha = 255 ; // 設置透明度
然后就是調用UpdateLayeredWindow函數了
UpdateLayeredWindow(hWnd, hdc, NULL, &sz, hdcMem, &pt, NULL, &bf, ULW_ALPHA);
看看MSDN對UpdateLayeredWindow函數的介紹:
BOOL UpdateLayeredWindow( HWND hwnd, // 需要分層的窗口句柄 HDC hdcDst, // 需要分層窗口設備句柄 POINT *pptDst, // 窗口位置不發生變化可以設置為NULL SIZE *psize, // 窗口大小不發生變化可以設置為NULL HDC hdcSrc, // 繪制源的設備句柄 POINT *pptSrc, // COLORREF crKey, // 指定一個透明色,使用ULW_COLORKEY標志時有效,也就是說crKey為白色時候,那么位圖上所有白色的地方均為透明,其他地方不透明 BLENDFUNCTION *pblend, // 之前介紹過了 DWORD dwFlags // ULW_ALPHA使用Alpha通道,ULW_COLORKEY使用crKey作為透明色,ULW_OPAQUE不透明 );
這樣異形窗口就算完成了。
整理以下代碼如下:
LONG windowStyle = GetWindowLong(hWnd, GWL_EXSTYLE); windowStyle = windowStyle | WS_EX_LAYERED; SetWindowLong(hWnd, GWL_EXSTYLE, windowStyle); CImage img; img.Load(TEXT( " png圖片 " )); SIZE sz; // 圖片大小 sz.cx = img.GetWidth(); sz.cy = img.GetHeight(); SetWindowPos(hWnd, NULL, 0 , 0 , sz.cx, sz.cy, SWP_NOREDRAW); // 將窗口大小設置為圖片大小使之相互合適 HDC hdc = GetDC(hWnd); // 獲取窗口設備句柄 HDC hdcMem = CreateCompatibleDC(hdc); // 創建一個與hdc相兼容的內存設備句柄 HBITMAP hBitmap = CreateCompatibleBitmap(hdc, sz.cx, sz.cy); SelectObject(hdcMem,(HGDIOBJ)hBitmap); img.Draw(hdcMem, 0 , 0 , sz.cx, sz.cy, 0 , 0 , sz.cx, sz.cy); POINT pt; pt.x = 0 ; pt.y = 0 ; BLENDFUNCTION bf; bf.AlphaFormat = AC_SRC_ALPHA; bf.BlendFlags = 0 ; bf.BlendOp = AC_SRC_OVER; bf.SourceConstantAlpha = 255 ; UpdateLayeredWindow(hWnd, hdc, NULL, &sz, hdcMem, &pt, NULL, & bf, ULW_ALPHA); ReleaseDC(hWnd, hdc);
說說遇到的問題,最開始就碰到了一個問題發現png圖片沒有辦法產生異形的效果,然后才發現原來我使用的png圖片不是透明png圖片。
當png透明圖片問題解決后,發現窗口出現后,本來那個應該是透明的地方卻是白色不透明的了。這個問題網上也是有介紹的,我在 這里 得到了答案。下面我把原因重新貼一下:
PNG圖片的透明背景總是一片白色,后來才發現這其實是微軟GDI+的設計問題,PNG圖片是ARGB,使用GDI+載入圖片的時候,GDI+會默認已經進行了預剩運算(PARGB),即每象素的實際值是已經和ALPHA值按比例相乘的結果,實際上它根本就沒有做預乘,在使用透明圖片的象素ALPHA通道的時候,CImage內部正是調用的AlphaBlend,沒有預乘的圖當作預乘的圖片處理的結果就是這相當于一張和純白背景進行了預剩,所以圖象總是出現白色背景。
下面給出解決這個問題的代碼,代碼來源 這里 ,處理還是比較簡單的:
if (pImage->GetBPP() == 32 ) // 確認該圖像包含Alpha通道 { for (inti= 0 ; i<pImage->GetWidth();i++ ) { for ( int j= 0 ; j<pImage->GetHeight(); j++ ) { byte *pByte = ( byte *)pImage-> GetPixelAddress(i, j); pByte[ 0 ]= pByte[ 0 ] * pByte[ 3 ]/ 255 ; pByte[ 1 ]= pByte[ 1 ] * pByte[ 3 ]/ 255 ; pByte[ 2 ]= pByte[ 2 ] * pByte[ 3 ]/ 255 ; } } }
這樣異形窗口也算是完成了,但是這樣生成的exe文件需要依賴外部的png圖片,所以我應該把那張png圖片放到資源文件中,然后直接生成的exe就包含了那張png圖片。這就涉及到了如何從資源中加載圖片的問題。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
