完美PNG半透明窗體解決方案

   當年Vista系統剛出來的時候,最吸引人的莫過於半透明磨砂的窗體界面了,迷倒了多少人。這個界面技術隨即引起了編程界的一陣騷動,不少人都在問:如何實現這一界面效果?固然,在Vista下卻是很簡單,系統自己支持,因此幾乎不須要寫一句代碼,可是當時仍是XP的天下,因而你們就能夠研究在XP下如何實現這一效果。html

        最早實現的應該是桌面天氣秀,還有笨笨鍾,後來魚魚軟件的魚魚桌面秀也成功在XP下模仿了Vista的側邊欄,的確,讓人很激動,可是他們保密,問也問不到到底是用了什麼技術,記得當年大富翁論壇(http://www.delphibbs.com 比較著名的Delphi論壇)上還爲此進行過討論,最後有個ID叫小雨哥的人提供了一個方法(固然是否是他原創我不得而知,小雨哥目前在盛大網絡,見過一面,無限膜拜中),下面是Delphi的實現代碼:        ios

01. var
02. pt1, pt2 : TPoint;
03. sz : TSize;
04. bf : TBlendFunction;
05. begin
06.  
07. bitmap:=tgpbitmap.Create(PNGFile);//這個PNGFile是具體的PNG圖片路徑
08. pt1 := Point(left,top); //窗口作上角的座標
09. pt2 := Point(0, 0); //這個就不用說了,一看見(0,0)就應該明白了
10. sz.cx := bitmap.GetWidth;  //尺寸不要超過圖像大小,否則窗口就什麼都沒有了,連個影子都沒有
11. sz.cy := bitmap.GetHeight;  //同上
12. bf.BlendOp := AC_SRC_OVER; //這些死記就好了
13. bf.BlendFlags := 0;                  //同上
14. if (nTran<0) or (nTran>255) then nTran:=255;
15. bf.SourceConstantAlpha := nTran;  //同上
16. bf.AlphaFormat := AC_SRC_ALPHA; //同上
17. DeleteObject(bmp); //前面就是在這裏犯的錯誤,否則佔用的內存會無限增大
18. bitmap.GetHBITMAP(0,bmp); // HBITMAP是windows標準位圖格式,支持透明,這裏是從tgpbitmap 轉化成 HBITMAP
19. DeleteDC(DC);
20. DC := CreateCompatibleDC(Canvas.Handle);
21. old_bmp := SelectObject(DC, bmp);
22. UpdateLayeredWindow(Handle, Canvas.Handle, @pt1, @sz, DC, @pt2,0, @bf,ULW_ALPHA);//調用UpdateLayeredWindow實現
23. end;

         這個方法實際上是生成一個PNG的窗體,咱們知道,PNG圖片是具備Alpha屬性的,因此,若是PNG是半透明磨砂裝的,那麼生成的窗體也就是半透明磨砂裝的,注意,上面的代碼須要使用GDIPlus類uses gdipapi, gdipobj;編程

        咱們這裏不討論這段代碼,這段代碼是別人寫的,看似很是完美,好比日期查詢器的系統初始化界面就能夠由上面的代碼生成:windows

        可是這段代碼有個致命的問題,你能夠試試在Form上面放一些控件,好比button,edit等,再次編譯你會神奇的發現,全部的控件都不顯示,這是怎麼回事呢?經過查閱MSDN,咱們發現問題出在UpdateLayeredWindow函數上。api

        MSDN中,關於該函數的Remarks中有這樣一段說明:網絡

The UpdateLayeredWindow function maintains the window's appearance on the screen. The windows underneath a layered window do not need to be repainted when they are uncovered due to a call to UpdateLayeredWindow, because the system will automatically repaint them. This permits seamless animation of the layered window.app

        大意是說,使用這個函數之後,下一層窗體不會再從新繪製,也就是說,窗體不會響應Onpaint事件來重繪全部控件,致使控件沒法看見,可是實際上控件是存在的,你能夠在響應位置上點擊一下button,你會發現button依然會響應點擊事件,但就是看不見。less

        這就頭疼了,若是不能使用控件,或者說控件看不見,光一個窗體再好看有什麼用呢?其實微軟貌似是用這個函數作無縫鏈接動畫用的,MSDN說的很清楚嘛:This permits seamless animation of the layered window.函數

        嗯,好吧,既然這個方法不行,那就換一個吧,因而有人想到了使用2個窗體來解決。post

        2個窗體怎麼解決呢?其實也很簡單,一個窗體做爲半透明的PNG放在後面,一個窗體做爲放置控件的窗體放在前面,而後只要2個窗體同步移動就能夠了,就拿日期查詢器來講吧,登錄窗體:

這個窗體上下都有半透明的邊框,上面也有控件顯示,也許你很差理解,若是我分解一下:

      

        怎麼樣?這樣你就發現了,實際上是2個窗體,後面一個背景窗體,前面一個Border:=none的控件窗體,而後兩個窗體同步移動便可,這樣咱們就僞造了一個半透明的窗體。事實上,不少軟件也是這麼作的,包括有些帶陰影的窗體也是一樣原理。

        至於同步移動,也很簡單,處理下OnMove消息就能夠了:

01. function WndNewProc(Wnd: HWND; uMsg: UINT; wPar: WPARAM; lPar: LPARAM): LRESULT; stdcall;
02. var Rect: TRect;
03. begin
04. Result := 0;
05. case uMsg of
06. WM_LBUTTONDOWN: SendMessage(Wnd, WM_SYSCOMMAND, SC_MOVE+2, 0);
07. else
08. begin
09. if ((uMsg = WM_MOVING) or (uMsg = WM_MOVE)) and GetWindowRect(Wnd, Rect) then
10. SetWindowPos(ComponentForm.Handle, 0, Rect.Left, Rect.Top, 0, 0, SWP_NOSIZE);
11. Result := DefWindowProc(Wnd, uMsg, wPar, lPar);
12. end;
13. end;
14. end;

        咱們假設放置控件的窗體名字叫ComponentForm,當咱們鼠標左鍵按下並移動背景窗體的時候,控件窗體跟着同步移動便可。

        固然,有時候咱們能夠直接用代碼生成背景窗體,這樣的話能夠減小一些程序體積,生成背景窗體咱們能夠用一個函數叫CreateWindowEx,注意這裏要用帶Ex的,表示會有附加參數,咱們只要把這個函數的第一個參數設置爲WS_EX_LAYERED就能夠了,他表示一個額外的層屬性。

        兩個方法均可以,咱們既能夠直接用2個窗體,也能夠用CreateWindowEx函數來生成背景窗體,效果是同樣的,看我的喜愛。

相關文章
相關標籤/搜索