地址: http://blog.csdn.net/hujkay
作者:Jekkay Hu([email protected])
關鍵詞:MFC, 編寫異行窗體,自定義UI控件,VC++,異形控件,高仿QQ登陸界面, 截取QQ密碼,QQ釣魚
時間: 2014/4/12
1. 概述
在開發客戶端程序的時候,稍微有點正常審美觀的人都會對微軟的默認UI界面表示深惡痛絕,連最簡單的默認程序ICON都用那麼低的像素的ICON,質量差的令人髮指啊。所以,但凡好一點的軟件,都會去找一些界面庫,或者乾脆自己寫個界面庫來美化一下用戶交互界面,免得用戶吐血暴斃。網絡上有許多界面庫,其中不少是免費的,這裏推薦一款我經常用的界面庫:Skinsharp。這個界面庫,動態編譯版本是免費的,靜態編譯版本是收費的,當然網上肯定有不少破解版本,但是爲了中國軟件的前途,大家還是稍微掏點錢買正版吧,有點扯遠了,拽回來!使用第三方提供的界面庫固然能美化程序界面,但有的程序需要一些比較特殊的異形界面,那麼這些界面庫就很難滿足需求了,這樣我們就必須自己去編寫UI插件了。
2. 編寫自定義UI界面
編寫自定義UI插件,涉及到三方面:形狀,界面繪製和事件響應。下面分別對着三個方面進行詳細闡述一下。2.1 形狀
我們先隨便找個軟件,比如金山毒霸吧,如下:界面是不是很酷,相比Windows自身的界面是不是感覺是天上天下之別。而我們看到這些好看的界面形狀都不是方方正正,全是不規則的形狀,所以我們第一步要做的就是設定窗口的形狀。微軟提供了一個API函數,可以設定窗體的形狀:
int SetWindowRgn(HWND hWnd, HRGN hRgn, BOOL bRedraw);
其中HRGN是一個用於表示形狀的對象,我們可以用另外一個封裝好的類CRGN來進行操作。該CRGN提供了許多非常友好的接口函數,比如添加一塊區域,減少一塊區域等,具體的可以查看一下官網的MSDN,下面列舉一些常用的操作。
a. 創建一個矩形的區域
CRgn rgn;
rgn.CreateRectRgn(0, 0, nWidth,nHeight);
b. 在當前的區域中添加一塊的區域
rcXor.SetRect(0, 0, 1, 2);
rgn_xor.CreateRectRgn(0, y, border_offset[y], y + 1);
rgn.CombineRgn(&rgn, &rgn_xor, RGN_OR);
c. 在當前的區域中只取共同區域
rcXor.SetRect(0, 0, 1, 2);
rgn_xor.CreateRectRgn(0, y, border_offset[y], y + 1);
rgn.CombineRgn(&rgn, &rgn_xor, RGN_AND);
d. 在當前的區域中異或一塊的區域
rcXor.SetRect(0, 0, 1, 2);
rgn_xor.CreateRectRgn(0, y, border_offset[y], y + 1);
rgn.CombineRgn(&rgn, &rgn_xor, RGN_XOR);
區域創建了好了之後,就設定一下窗體的區域:
SetWindowRgn((HRGN)rgn, TRUE);
下面一段完整的代碼,供大家參考一下:
CDC* pDC = GetDC();
CRect rc;
GetWindowRect(rc);
rc.OffsetRect(-rc.left, -rc.top);
CRgn rgn;
rgn.CreateRectRgn(0, 0, rc.Width(), rc.Height());
CRgn rgn_xor;
CRect rcXor;
for (int y = 0; y < nSize; ++y)
{
rcXor.SetRect(0, y, border_offset[y], y + 1);
rgn_xor.CreateRectRgn(0, y, border_offset[y], y + 1);
rgn.CombineRgn(&rgn, &rgn_xor, RGN_XOR);
rgn_xor.DeleteObject();
}
for (int y = 0; y < nSize; ++y)
{
rcXor.SetRect(rc.right - border_offset[y], y, rc.right, y + 1);
rgn_xor.CreateRectRgn(rc.right - border_offset[y], y, rc.right, y + 1);
rgn.CombineRgn(&rgn, &rgn_xor, RGN_XOR);
rgn_xor.DeleteObject();
}
for (int y = 0; y < nSize; ++y)
{
rcXor.SetRect(0, rc.bottom - y - 1, border_offset[y], rc.bottom - y);
rgn_xor.CreateRectRgn(0, rc.bottom - y - 1, border_offset[y], rc.bottom - y);
rgn.CombineRgn(&rgn, &rgn_xor, RGN_XOR);
rgn_xor.DeleteObject();
}
for (int y = 0; y < nSize; ++y)
{
rcXor.SetRect(rc.right - border_offset[y], rc.bottom - y - 1, rc.right, rc.bottom - y);
rgn_xor.CreateRectRgn(rc.right - border_offset[y], rc.bottom - y - 1, rc.right,rc.bottom - y);
rgn.CombineRgn(&rgn, &rgn_xor, RGN_XOR);
rgn_xor.DeleteObject();
}
SetWindowRgn((HRGN)rgn, TRUE);
m_Rgn.DeleteObject();
m_Rgn.Attach(rgn.Detach());
ReleaseDC(pDC);
2.2 界面繪製
界面繪製是繁瑣的工作,推薦使用Gdiplus庫來支持一下,在stdafx.h加入如下的代碼,將其包含進來即可:#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")
自定義繪製界面需要重載一下主要下面的幾個函數:繪製客戶區域的函數:ON_WM_PAINT()
繪製非客戶區域的函數:ON_WM_NCPAINT()
背景擦除函數:ON_WM_ERASEBKGND()
限定窗體大小的函數:ON_WM_GETMINMAXINFO()
一般來說,自繪窗體控件最主要的就是ON_WM_PAINT()方法,大部分時間都耗在就是在裏面,這個活比較細緻,像素一個一個地調,沒有耐心的人估計會砸電腦。在這裏有技巧需要提一下,如果繪製的界面插件老一閃一閃的,可以先在內存裏面創建一塊畫板,在裏面畫好了之後然後在顯示到界面上,可參考如下的代碼:
CRect rcClient;
GetClientRect(&rcClient);
CPaintDC dc(this);
CDC MemDC;
MemDC.CreateCompatibleDC(&dc);
CBitmap memBmp;
memBmp.CreateCompatibleBitmap(&dc, rcClient.Width(), rcClient.Height());
CBitmap *pOldmap = MemDC.SelectObject(&memBmp);
DrawImageStyle(MemDC, rcClient);
dc.BitBlt(0, 0, rcClient.Width(), rcClient.Height(), &MemDC, 0, 0, SRCCOPY);
MemDC.SelectObject(pOldmap);
MemDC.DeleteDC();
2.3 事件
事件是指根據用戶的交互動作而觸發某些動作,主要是鼠標和鍵盤兩種。我這裏主要介紹下鼠標事件,鼠標事件可分爲:移動,按下,彈起,移入,移出,單擊,雙擊和滾動。在創建自定義創建窗口的時候,千萬不要重載單擊和雙擊的事件,這是非常不好的習慣,而且容易引發各種問題,最好的辦法是自己在按下按鈕的時候模擬單擊或者雙擊的信息,所以一般來說,重載下面幾個方法即可: ON_WM_MOUSEMOVE()
ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
ON_MESSAGE(WM_MOUSEHOVER, OnMouseHover)
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_LBUTTONDBLCLK()
事件和窗體繪製有着緊密的關係,比如一個按鈕,用戶鼠標移入時是一種狀態,按下時又是一種狀態,彈起式又是一狀態。因此,在繪製窗體時,要根據當前的用於的交互狀態繪製不同的形態,非常考驗一個人的變成能力。3. 小結
繪製自定義UI插件是個細活,必須如加工藝術品一樣有耐心,所以我但凡見到做UI插件的同仁,都心生佩服。週末閒來無事,根據其他高手的一些代碼,高仿了一下QQ的登陸界面,很酷吧,呵呵。
高仿的QQ 2014登陸程序的下載地址:http://download.csdn.net/user/hujkay
別幹壞事啊~~~
胡楊, Jekkay Hu
2014/4/12