1. 程式人生 > >Layered Window 透明窗體的實現總結

Layered Window 透明窗體的實現總結

轉自:http://www.cnblogs.com/just-bg/p/3788412.html(侵刪)

此片文章是以前寫的, 剛剛新開了部落格, 就發出來跟大家分享下。

這篇文章主要講得是vc中各種分層、透明、不規則視窗的實現, 基本囊括GDI、GDI+能使用的所有方法。

本文講述了三種方法,其中第一種方法有兩種不同效果,第三種方法有兩種不同的實現方式。文中有方法使用了GDi+,關於GDI+的使

用請自行查詢資料,本文不進行細述。

  

方法一:窗體整體透明,支援子控制元件透明,支援OnPaint重繪。

這個方法比較簡單,使用win32 Api 中SetLayeredWindowAttributes

函式即可,關於該函式可查詢MSDN,用這種方法有兩種效果:

效果1:窗體整體透明,子控制元件也透明,可以實現半透明效果

//第一步要修改窗體屬性,WS_EX_LAYERED支援透明

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貼圖,並實現透明區域的透明。

此方法的優點是可以實現不規則形狀貼圖,滑鼠能穿透透明區,並且邊緣無鋸齒。

該方法根據實現方式可分為兩種方法

1、使用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方法就可。

2、使用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

以上所述三種方法本人都嘗試過,都是可行的。如有疑問請向我

諮詢。