此片文章是之前寫的, 剛剛新開了博客, 就發出來跟你們分享下。算法
這篇文章主要講得是vc中各類分層、透明、不規則窗口的實現, 基本囊括GDI、GDI+能使用的全部方法。api
本文講述了三種方法,其中第一種方法有兩種不一樣效果,第三種方法有兩種不一樣的實現方式。文中有方法使用了GDi+,關於GDI+的使函數
用請自行查詢資料,本文不進行細述。spa
方法一:窗體總體透明,支持子控件透明,支持OnPaint重繪。設計
這個方法比較簡單,使用win32 Api 中SetLayeredWindowAttributes指針
函數便可,關於該函數可查詢MSDN,用這種方法有兩種效果:code
效果1:窗體總體透明,子控件也透明,能夠實現半透明效果orm
//第一步要修改窗體屬性,WS_EX_LAYERED支持透明blog
LONG lWindowStyle = ::GetWindowLong(hwnd, GWL_EXSTYLE) |圖片
WS_EX_LAYERED;
//設置Alpha不透明度
BYTE byteAlpha = 150;
//注意最後一個參數爲LWA_ALPHA,第二個參數顏色掩碼(透明
//色無用)
SetLayeredWindowAttributes(m_hwnd, 0/*any*/, byteAlpha,
----------------------- Page 2-----------------------
LWA_ALPHA )
效果2:窗體總體透明,子控件不透明,實現不規則窗體,區域透明。
首先須要一張背景位圖,須要透明的地方用單一顏色填充,而後將其
貼在背景上,代碼以下
第一步跟效果一中同樣需修改窗體屬性
::SetWindowLong(hwnd, GWL_EXSTYLE, lWindowStyle);
//將紅色設爲透明色, 注意透明區域鼠標並不能穿透RGB(255, 0, 0)
//爲透明色
//注意最後一個參數爲LWA_COLORKEY,第三個參數透明度無用
::SetLayeredWindowAttributes(hwnd, RGB(255, 0, 0), 111/*any*/,
LWA_COLORKEY);
須要注意的是效果1和效果2能夠結合起來使用,最後一個參數改爲
LWA_COLORKEY | LWA_ALPHA便可。使用
SetLayeredWindowAttributes函數實現不規則形狀簡單易行,可是一般
會有鋸齒很難處理。
方法二:根據位圖進行區域裁剪 ,關鍵函數CombineRgn和SetWindowRgn。
該方法跟方法一同樣,須要將背景位圖須要透明的
地方填充爲單一顏色,該方法的原理是遍歷位圖中的每一個像素,將需
要透明的像素過濾,將其餘不須要透明的像素所在區域用
CombineRgn函數鏈接起來造成一個區域,而後用SetWindowRgn將貼
好背景圖的窗體放進這個區域。此方法好處是能夠實現鏤空,即鼠標
穿透透明區域。缺點是遍歷每一個像素對於大的位圖算法時間複雜度高,
效率很低。代碼以下:
void CMeterHeadDlg::SetupRegion(CDC & pDC, HBITMAP cBitmap, COLORREF TransColor) { CDC memDC; HBITMAP pOldMemBmp=NULL; BITMAP bit; CRect rect; GetWindowRect(rect); CRgn wndRgn; //建立於傳入dc兼容的臨時dc memDC.CreateCompatibleDC(pDC); //取得位圖參數,要用其長和寬¨ª ::GetObjectA(m_hBkBitmap, sizeof(bit), &bit); //將位圖選入臨時dc pOldMemBmp= memDC.SelectBitmap(m_hBkBitmap); //建立總的窗體區域 wndRgn.CreateRectRgn(0,0,rect.Width(),rect.Height()); for(int y=0;y<rect.Height()+1;y++) { CRgn rgnTemp; //保存臨時區域 int iX = 0; do { //等於透明色跳過找到下一個非透明色 if (memDC.GetPixel(iX, y) == TransColor) { rgnTemp.CreateRectRgn(iX,y,iX+1,y+1); //合併region,注意ComebineRgn最後一個參數爲「異或」 wndRgn.CombineRgn(wndRgn, rgnTemp, RGN_XOR); //刪除臨時region rgnTemp.DeleteObject(); } iX++; }while(iX <rect.Width()+1); iX = 0; } if(pOldMemBmp) memDC.SelectBitmap(pOldMemBmp); SetWindowRgn(wndRgn,TRUE); SetForegroundWindow(m_hWnd); DeleteDC(memDC); }
方法三:使用透明png貼圖,並實現透明區域的透明。
此方法的優勢是能夠實現不規則形狀貼圖,鼠標能穿透透明區,而且邊緣無鋸齒。
該方法根據實現方式可分爲兩種方法
一、使用CImage(ATL和MFC中都有該類,直接用win32 api沒有CImage,會麻煩點可能要用CreateFIle函數加載)繪製。
爲何咱們正常的的使用CImage加載png透明區老是有白色背景呢?查了不少資料才
發現這實際上是微軟GDI+的設計問題,PNG 圖片是ARGB,使用GDI+
載入圖片的時候,GDI+會默認已經進行了預剩運算(PARGB),即
每象素的實際值是已經和ALPHA值按比例相乘的結果,實際上它根
本就沒有作預乘, 在使用透明圖片的象素ALPHA通道的時候,
CImage 內部正是調用的AlphaBlend,沒有預乘的圖看成預乘的圖片
處理的結果就是這至關於一張和純白背景進行了預剩, 因此圖象總
是出現白色背景。因此咱們只須要對症下藥,載入圖片前與處理下即
可:
if (Image.GetBPP() == 32) //確認32位包含alpha通道 for (i = 0; i < Image.GetWidth(); i++) { for (j = 0; j < Image.GetHeight(); j++) { byte *pByte =(byte*)Image.GetPixelAddress(i, j); pByte[0] = pByte[0] * pByte[3] / 255; pByte[1] = pByte[1] * pByte[3] / 255; pByte[2] = pByte[2] * pByte[3] / 255; } } }
最後調用CImage中Draw方法就可。
二、使用GDI+貼圖,利用UpdateLayeredWindow
函數實現png透明區域透明。該函數請查詢MSDN。這種方法不支持子
控件透明, 不支持OnPaint重繪代碼以下:
首先在OnInitDialog中修改窗體屬性
ModifyStyleEx(0, WS_EX_LAYERED | WS_OVERLAPPED);
下面爲貼圖函數,注意因爲不支持OnPaint,因此需重繪是手動調用
貼圖函數,貼圖中使用到GDI+
//在OnInitDialog中初始化m_pBkImage ,m_pBkImage爲
//Gdiplus::Image指針,Image::FromFile是從外面文件夾中導入png文
//件,若果是從本地資源文件中導入,需使用其餘方法。
m_pBkImage = Image::FromFile(g_strResPath+_T("main_.png"));
下面爲貼圖函數
// 初始化時該函數放在OnInitDialog中調用,後面須要刷新時,手動 //調用,該方法貼的背景圖不能響應WM_PAINT消息,也不可以在 //OnPaint函數中調用該繪圖方法。 void CMainPanel::DrawAlphaPng() { CRect rcClient; GetClientRect(&rcClient); CClientDC dc(m_hWnd); CDC memDc; memDc.CreateCompatibleDC(dc.m_hDC); CBitmap bmp; bmp.CreateCompatibleBitmap(dc.m_hDC, rcClient.Width(), rcClient.Height()); memDc.SelectBitmap(bmp); //用GDI+顯示圖片 Graphics graph(memDc.m_hDC); graph.DrawImage(m_pBkImage, 0,0 ,rcClient.Width(), rcClient.Height()); BLENDFUNCTION _Blend; _Blend.BlendOp = 0; _Blend.BlendFlags = 0; _Blend.AlphaFormat = 1; _Blend.SourceConstantAlpha = 255; SIZE sz = {rcClient.Width(), rcClient.Height()}; //::UpdateLayeredWindow(m_hWnd, hDC, &ptWinPos,&sizeWindow, //hdcMemory, &ptSrc, 0, &stBlend, ULW_ALPHA); UpdateLayeredWindow(m_hWnd, dc, &CPoint(0, 0), &sz, memDc, &CPoint(0, 0), 0, &_Blend, ULW_ALPHA); bmp.DeleteObject(); graph.ReleaseHDC(memDc.m_hDC); ReleaseDC(dc.m_hDC); }
須要注意的是使用方法三中第二種方法雖然不會出現鋸齒,可是
會致使界面上的子空間所有透明,這樣咱們在界面上添加的控件都沒
用。我試過在界面上用Create方法建立及對控件重繪,可是沒用。解
決這個問題的方法是結合方法一中的效果二。須要兩個窗口A (背景
窗口),B(用於放置控件)。用方法三第二種方式實現窗口A,使用
方法一種第二種效果建立B,MFC中能夠在OnCtlColor函數中將背景
顏色設爲單一顏色,ATL中則沒有OnCtlColor,最簡單的作法是在
OnEraseBKGND消息函數中返回TRUE(背景會變成白色),而後將背
景顏色設爲透明色(這時B窗體會全透明,可是其上控件不會透明),
將A上所須要的控件放在B上相應位置,B窗口覆蓋在A上面與其重合
(因爲B透明因此B上控件看着像放在A上)。在移動B窗口時同時移
動A窗口。這樣就能達到咱們想要的效果。
除了這三種方法以外,使用GDI中TransparentBlt函數也能夠實現
透明,該函數能夠將一張有背景的貼圖消除背景貼在窗體上。關於該
函數使用就再也不介紹了,能夠查詢MSDN
以上所述三種方法本人都嘗試過,都是可行的。若有疑問請向我
諮詢。