作為windows工程師,UI開發是無可避免的工作,無論你是寫一個供銷存系統,還是一款聊天IM,UI開發總是會占據你大量的時間。前段時間在公司開發項目中,帶著些許私心實現了一個構想了較長時間的UI引擎,自已在使用過程中感覺極大的加快了UI開發的效率,希望與大家分享,並用大家的建議來不斷完善。
接下來將以幾個在實際工作中常見的UI開發問題為例,介紹實現方法及效果,相信這幾個問題能引起客戶端UI開發同仁的共鳴。
1.多格式圖片支持
2.文字和超鏈接
3.自繪按鈕
4.髒處理與區域刷新
5.異形窗體(包括像素級透明異形窗體)
1.多格式圖片支持
UI開發離不開圖片,windows的api提供了一些加載圖片的方法,如常用的LoadImage,使用很簡單。但其功能也跟其用法一樣簡單,只能加載bmp,ico等幾種格式。眾所周知,bmp是不帶alpha通道的,一旦需要實現陰影等alpha漸變的效果,系統提供的api就有些捉襟見肘了。當然很多人會想到大名鼎鼎的CxImage,這也是個不錯的選擇。我在內部也是封裝了CxImage幫忙加載和保存多格式的圖片,但加載之後的圖像數據處理都是自處理的了,因為CxImage在處理RGB轉hsl,旋轉等特效時大量使用了浮點運算,效率不能使人十分滿意。我把所有的浮點運算都轉為整形運算,並大量使用了SSE2指令進行優化,實測證明在旋轉,HSL轉換,灰化等特效時,效率可以提高4-10倍(CPU為T2330 1.6GHz)。圖片加載支持三種方式:從文件;從資源;從dc。需要說明的是從資源加載時請將資源類型命名為IMAGE。
演示代碼如下:
//GetSonicUI是引擎導出的唯一函數,是類廠和引擎總控,負責創建對象和銷毀對象等。
ISonicImage * pImg = GetSonicUI()->CreateImage();
pImg->Load("C:\\1.png");
pImg->Draw(hdc, 10, 10);
GetSonicUI()->DestroyObject(pImg);
OK,一個帶透明通道的png圖片繪制就完成了,是不是輕松惬意。
2.文字和超鏈接
UI開發過程中經常最麻煩的是繪制文字,需要你不停的初始化字體,設定字體屬性,如果產品人員要求文字按一定的格式排版或輸出彩色文字,那簡直就是我們的噩夢了。而在自己的界面加入超鏈接,網上已經有不少演示代碼了,但我相信ISonicString是一個更簡單的實現方案。ISonicString是一個可以進行消息交互的UI組件對象。只需要像html語言一樣加入一些類似的控制符,你就可以隨心所欲的控制字體的大小顏色,超鏈接等屬性,非常方便。
ISonicString * pStr = GetSonicUI()->CreateString();
pStr->Format("/c=%x, a='http://hi.csdn.net/zskof', font, font_height=16/點我打開鏈接", RGB(0, 0, 255));
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
pStr->TextOut(hdc, 0, 0, hWnd);
EndPaint(hWnd, &ps);
}
break;
}
.
.
.
}
如何,只需要創建,然後像CString的Format一樣格式化一個字符串,在WM_PAINT響應中輸出即可,只需要三步,你就得到了一行藍色的功能完整的超鏈接,是不是很方便。通過控制字符,你還可以設定下劃線的樣式,鼠標形狀,響應鼠標時變色等細節,具體參看ISonicUI.h中的注釋即可。
ISonicString也可以將文字和圖片混合輸出,或使圖片帶有超鏈接屬性,需要用'p'控制符指定一個ISonicImage的id:
ISonicImage * pImg = GetSonicUI()->CreateImage();
pImg->Load("C:\\1.png");
ISonicString * pStr = GetSonicUI()->CreateString();
pStr->Format("/c=%x/你好嗎,朋友/p=%d, a='http://hi.csdn.net/zskof'/", RGB(0, 0, 255), pImg->GetObjectId());
這樣就可以像寫網頁一樣在你的界面上進行文字和圖片的混合排版輸出了。
3.自繪按鈕
自繪按鈕恐怕是UI編寫中最常見也是重復度最高的工作,通常是繼承自CButton然後ownerdraw出來。我的實現是不使用窗體的純自繪。其實超鏈接也可以理解為按鈕的一種,所以我自繪按鈕的使用方式跟超鏈接也大同小異。
void WINAPI OnMove(ISonicString * pStr, LPVOID)
{
g_pEffect->MoveGently(0, 0);
}
// 加載三態圖片資源
ISonicImage * pImgNormal = GetSonicUI()->CreateImage();
pImgNormal->Load(BMP_NORMAL);
pImgNormal->SetColorKey(RGB(255, 0, 255));
ISonicImage * pImgHover = GetSonicUI()->CreateImage();
pImgHover->Load(BMP_HOVER);
pImgHover->SetColorKey(RGB(255, 0, 255));
ISonicImage * pImgClick = GetSonicUI()->CreateImage();
pImgClick->Load(BMP_CLICK);
pImgClick->SetColorKey(RGB(255, 0, 255));
// ISonicString * g_pTest[10]
g_pTest[10]->Format("/a, p=%d, ph=%d, pc=%d, linkt='點我移動'/", pImgNormal->GetObjectId(), pImgHover->GetObjectId(),
pImgClick->GetObjectId());
g_pTest[10]->Delegate(DELEGATE_EVENT_CLICK, NULL, NULL, OnMove);
同樣的,格式化好後的ISonicString在OnPaint的時候輸出即可,這樣你就擁有一個具有三態變換的漂亮按鈕,其中'p'關鍵字代表normal態,'ph'代表hover態,'pc'代表click態。如果從美工那裡得到的源圖是一張圖片三態平鋪的也不要緊,只需要將ph,pc都指向同一張img即可,內部會自動進行源區域裁剪。另外用過QQ2009的人可能會發現,2009的很多按鈕三態變換是漸變的,體驗很好,ISonicString一樣可以做,只需要格式化時稍稍修改一下,g_pTest[10]->Format("/a, p=%d, ph=%d, pc=%d, linkt='點我移動', animation=40/", pImgNormal->GetObjectId(),
pImgHover->GetObjectId(), pImgClick->GetObjectId());
增加一個'animation=40'的控制符(40是漸變速度),就可以得到一個QQ2009一樣漂亮的三態漸變按鈕了。按鈕的點擊響應是用“委托”的方式,你需要向按鈕委托一個形如void WINAPI Func(ISonicBase *, LPVOID)的全局函數或類的成員函數,以供引擎在按鈕被點擊時回調。
圖一:自繪按鈕
4.髒處理與區域刷新
我們都知道gdi的繪制效率是不高的,無法像DDraw直接操作顯存buffer那麼爽快,所以InvalidateRect才提供了局部刷新的參數,而局部刷新也是gdi下進行優化的關鍵所在。可在實際操作中,我不常看見有人做這麼精細的切割,都是一個InvalidateRect(hwnd, NULL, TRUE)了事。這也難怪,我隨便TextOut一個字符串,我如果要去關心它占據了多少區域,區域之間的交叉裁剪等等,未免就太繁瑣了。所以我的引擎提供了一個ISonicPaint對象,意如其名,就是一塊畫布。創建這塊畫布時,你可以指定其擁有自己的memDC,而出於節省gdi對象的考慮,你也可以指定其是一塊無memDC的畫布,如何取捨根據實際情況。
創建一個畫布ISonicPaint * pPaint = GetSonicUI()->CreatePaint();
pPaint->Create(FALSE/*是否需要memDC*/, m_rtString.Width()/*寬*/, m_rtString.Height()/*高*/);
畫布創建之後,只需要在WM_PAINT中調用畫面的Draw方法即可,很簡單。如果你想在這個畫布上做畫,就需要像自繪按鈕一樣,向畫布委托一個你自己的繪制過程,以便在每次重繪時調用。示例代碼如下:
class CTest
{
public:
void RenderImage(ISonicPaint * pPaint, LPVOID);
};
void CTest::RenderImage(ISonicPaint * pPaint, LPVOID)
{
if(pPaint->GetCurrentPaint() == NULL)
{
return;
}
HDC hdc = pPaint->GetCurrentPaint()->hdc;
int x = pPaint->GetCurrentPaint()->x;
int y = pPaint->GetCurrentPaint()->y;
// draw here
...
}
CTest test;
pPaint->Delegate(DELEGATE_EVENT_PAINT, NULL, &test, CSonicString::RenderImage);
如此每次只需要調用pPaint->Redraw()便會進行畫布的區域重繪。這裡需要說明的是,ISonicString,包括接下來要介紹的幾個對象都是基於畫布的,也就是說引擎的所有對象都是具有髒處理檢查和區域自繪制優化的,可以極大的提高運行效率。
除了委托繪制以外,你還可以向畫布上直接添加UI對象,畫布支持的對象有:ISonicImage, ISonicString, ISonicPaint
5.異形窗體(包括像素級透明異形窗體)
異形窗體也是UI特效中經常需要使用的技術,常見的有兩種實現方法。一種方法是根據圖片裁剪出一個rgn,然後調用SetWindowRgn,另一個方法是將窗體設為WS_EX_LAYERED屬性,調用SetLayeredWindowAttributes或UpdateLayeredWindow實現透明裁剪。前一種方法效率較低,而且拖動窗體時會出現難看的殘影,後一種方法表現效果更好,拖動時可以避免殘影出現,但不能作用於WS_CHILD屬性的窗體。二者各有優劣。SonicUI同時提供了這兩種實現方法,可以根據情況選擇。
方法1:...
// ISonicImage * pImg
SetWindowRgn(hWnd, pImg->CreateRgn());
方法2:
...
// ISonicImage * pImg
// ISonicWndEffect * pEffect
pEffect->Attach(hWnd, TRUE); // 使用像素級alpha模式attach
pEffect->SetShapeByImage(pImg);
圖二:異形窗體效果圖
值得一提的是,如果使用UpdateLayeredWindow做窗體的像素級alpha特效,文字輸出就成為了一個麻煩,因為gdi的文字輸出函數是不帶alpha通道的,直接TextOut上去無法正常表現。不過ISonicString可以幫你解決這一難題,我在內部已經為文字增加了alpha通道,可以很好的適應背景。
結束語:引擎中還有滾動字幕組件,動畫組件等常用的UI表現組件,限於篇幅就不一一介紹了,請大家參照ISonicUI.h中的說明自己試用。設計這個引擎時的基本原則就是輕便,高效,如果大家在使用過程中有什麼好的建議或需求,也敬請聯系我,幫助我完善這個引擎。因為引擎完整實現代碼有數萬行,而且現在還屬於公司財產,就暫不公布完整源碼了,但對某些技術實現細節有興趣的朋友,可以與我聯系,共同學習進步。
本文配套源碼