轉帖|其它|編輯:郝浩|2010-11-19 11:47:17.000|閱讀 863 次
概述:對于Windows系統中各種控件換膚功能,要數滾動條的換膚最難實現了,尤其是控件自帶的系統滾動條,如Edit、ListBox、ListView、TreeView等自帶的系統滾動條,要想實現其自定義的皮膚功能,用常規辦法似乎都無法實現。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
對于Windows系統中各種控件換膚功能,要數滾動條的換膚最難實現了,尤其是控件自帶的系統滾動條,如Edit、ListBox、ListView、TreeView等自帶的系統滾動條,要想實現其自定義的皮膚功能,用常規辦法似乎都無法實現。
對于常規的皮膚定制一般都是通過定制WM_PAINT、WM_ERASEBKGND、 WM_CTLCOLORxxx、NM_CUSTOMDRAW來實現。然而系統滾動條的繪制,常規的、很陽光的方法行不通,微軟把一條康莊大道堵死了。根據我的觀察測試,系統滾動條有許多的消息都對其執行了繪制,這包括WM_NCPAINT、WM_NCMOUSEMOVE、WM_NCMOUSELEAVE、 WM_HSCROLL、WM_VSCROLL、WM_KEYDOWN、WM_MOUSEWHEEL等等,這些消息中有些可以定制,但有些沒法定制。比如 WM_HSCROLL就無法定制,如果我們處理這個消息,就必須自己控制滾動條的范圍、位置、翻頁值,而這些值我們通常無法得到,微軟根本沒有告訴我們,對于不同的控件它操控滾動條到底采用何種策略,不曉得。假若我們不去定制這個WM_HSCROLL,而默認的處理它又執行滾動條的繪制,這真是進退無路,叫人束手無策。當然不是完全沒有辦法,死路一條。為了克服障礙,翻越障礙,每個人有不同的策略,有攀巖翻越的,安全性不高;也有走小路繞行的,比較費力。
在網上反復搜索,自己也仔細研究,基本有兩種辦法來實現系統滾動條換膚,一種方法是HOOKAPI,也就是攔截API的辦法,還有一種是模擬法。
攔截API,實際上是修改操作系統的API入口。因為系統繪制滾動條是通過各種繪制函數來實現的,比如SetScrollInfo(),基本系統通過這一類函數實現滾動條的繪制,對這個API進行攔截,也就是我們自己寫一個 SetScrollInfo(),來親自實現滾動條的繪制,以便由此實現滾動條的自定義繪制,實現我們想要的各種風格的皮膚外觀。API的攔截有兩種:一種是修改系統所裝入內存可執行模塊的導入地址,替換成我們所寫的偽API的地址,使API調用能夠自動跳轉到這個偽API上;還有一種是直接修改API函數首地址處的若干機器指令,保存現場,寫入跳轉指令,使系統在執行到這個API時能自動跳轉到我們所寫的偽API上。我要說的是,這個攔截辦法還真是有些邪門,有安全隱患,病毒就常干這種事情。對操作系統進行這類暴力破解式的修改,很容易引起系統防火墻或反病毒軟件報錯,Windows原則上不允許干這種事;其二,一旦使用此法的程序因異常而崩潰,原本對操作系統的修改沒有還原,這可能會傷害到系統里的所有進程的穩定性,導致死機或運行異常。對于商業性的工程開發,往往很忌諱這一點,都傾向于追求穩定,通常是寧用拙法,不玩巧技;其三,此法有線程同步問題:因為正當你修改程序指令同時,若其他進程里的線程恰好執行到這里時候問題就會爆發出來,單CPU可能沒啥問題,系統內存和CPU緩沖可以做到同步,但多核系統就難說了;另外這個方法還有移植性問題:在一種版本的系統中,通過攔截API有效,在另一種版本的系統中,就不見得還有效,畢竟微軟實現滾動條的繪制,它沒規定一定就用哪個函數來實現,也許新系統中它另有高招,API攔截也就不靈了。
下面我要詳細講的是模擬法了,這個辦法不用攔截API,所有的技術實現都約束在系統所允許的范圍內,咱一絲不茍的當遵紀守法的良民。
所謂模擬法就是在系統滾動條的區域放置一個模擬窗口,這個窗口專門用于繪制系統滾動條。當然我們也要攔截帶滾動條控件的若干消息,以確保模擬窗口的繪制正確。下面只以ListView控件的水平滾動條為例,進行說明,垂直滾動條換膚可以列推。
首先我們要在滾動條的區域上創建一個模擬窗口,恰好覆蓋滾動條:
HWND hListView =...;//ListView窗口的句柄
HWND pWnd = ::GetParent(hListView);
HWND hBuddy =::CreateWindowEx(WS_EX_NOACTIVATE, "Buddy_Window", "",WS_CLIPSIBLINGS|WS_DISABLED|WS_CHILD, 0, 0, 0, 0, pWnd, NULL,gModule, NULL);
Buddy_Window是你注冊的模擬窗口類。注意,窗口一定要有WS_DISABLED 風格,這是個關鍵,這個風格能夠確保鼠標操作能夠透過模擬窗口,而直接操控到所覆蓋的滾動條,模擬窗口本身不接受任何鼠標鍵盤的輸入。另外讀取滾動條的矩形以及它的各個元素的可視、可用、壓下等狀態可通過GetScrollBarInfo()這個API來完成,不過要說明一點,這個API有些Bug,大家可去下載FreeCL2.03 版源碼,里頭修正了這個問題。
完成創建模擬窗口之后,你要給ListView安裝一個你寫的窗口過程,以攔截各種導致滾動條屬性改變的種種消息:
LRESULT CALLBACK MyListViewProc(HWNDhwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
case WM_LBUTTONDOWN:
......
case WM_LBUTTONDBLCLK:
......
case WM_NCMOUSEMOVE:
......
case WM_NCMOUSELEAVE:
......
}
......
gOldListViewProc =(WNNPROC)::GetWindowLong(hListView, GWL_WNDPROC);
::SetWindowLong(hListView, GWL_WNDPROC,LONG(&MyListViewProc));//安裝窗口過程
消息處理 :
首先我們要攔截WM_NCLBUTTONDOWN 和WM_NCLBUTTONDBLCLK這兩個消息,當你在滾動條上按下鼠標時,就立刻觸發WM_NCLBUTTONDOWN,如果連續快速按兩下鼠標就還有WM_NCLBUTTONDBLCLK。注意雙擊時只有一個WM_NCLBUTTONDOWN消息,而不是兩個。第二次按鼠標會出現一個 WM_NCLBUTTONDBLCLK。實際上這兩個消息我們完全可以等而視之,做相同的處理。系統在處理這個兩個消息時,都會在其內部處理中觸發許多其他消息,其中有若干個WM_HSCROLL、WM_CAPTURECHANGED、WM_NCMOUSELEAVE。我們主要是要處理 WM_HSCROLL,因為它最有價值。在你松開鼠標后,系統發送并處理完最后一個WM_HSCROLL之后,這才從WM_NCLBUTTONDOWN或 WM_NCLBUTTONDBLCLK中返回:
if(msg == WM_NCLBUTTONDOWN || msg== WM_NCLBUTTONDBLCLK)
{
//注意默認處理會有N個WM_HSCROLL消息出現,要等你的按下拖拽操作完成后,這個調用才返回
//在這個調用內部,我估計系統會進入一種消息循環,因為按住左鍵之后,WM_NCMOUSEMOVE
//和WM_NCLBUTTONUP都不再觸發了。其內部估計是捕捉了WM_NCMOUSEMOVE消息,因之反復刷新滾動
//盒的位置,若有必要你可安裝鼠標鉤子,以捕捉鼠標移動消息,以及時刷新模擬窗口中滾動盒的
//的位置。若只是響應WM_HSCROLL消息,你可能覺得滾動盒的拖拽比較滯,不平滑。
//SetWindowsHookEx(...);
LRESUTL code= ::CallWindowProc(gOldListViewProc, hListView, msg, wParam,lParam);
//UnhookWindowsHookEx(...);
returncode;
}
我建議大家去微軟的網站下載ControlSpy2.0 這個小工具,它用來監視控件的消息,這東西很有用。
下面我們再看WM_HSCROLL消息,這個消息一般是系統處理WM_NCLBUTTONDOWN或者WM_NCLBUTTONDBLCLK時產生的,但有時也可能是程序刻意發送的,和滾動條操作沒有關系。
if(msg == WM_HSCROLL)
{
::CallWindowProc(gOldListViewProc, hListView, msg, wParam,lParam);
//1)讀取滾動條的數據
//2)再在模擬窗口上繪制滾動條
//......
return0;
}
另外還有許多其他的消息可能導致滾動條屬性的變化,如用戶的鍵盤操作、鼠標滾輪操作可能導致滾動條的Thumb位置發生改變,這些操作分別導致WM_KEYDOWN和WM_MOUSEWHEEL,而系統又在這兩個消息中執行滾動條的繪制,因此你必須截獲它們,但處理方法同WM_HSCROLL,這里不再啰嗦了。
當我們沒有按下鼠標左鍵時,只是簡單地在滾動條移動鼠標時,我們會發現滾動條的按鈕和 Thumb都會自動高亮,其實這些變化都是系統在WM_NCMOUSEMOVE和WM_NCMOUSELEAVE中繪制完成的。比如當鼠標進入到 Thumb上時,它就高亮了,當鼠標離開它,它又變灰色了,你要分別處理WM_NCMOUSEMOVE和WM_NCMOUSELEAVE這兩個消息。注意只有ListView應用主題風格之后才可能有WM_NCMOUSELEAVE消息,傳統風格的ListView就沒有這個消息,只有 WM_NCMOUSEMOVE消息,因此處理高亮還真有些麻煩。正是因為主題風格和傳統風格的差異,因此我建議你也處理WM_THEMECHANGED消息,以識別不同的主題模式,實現不同高亮處理。看上面那個圖,TreeView的滾動條是主題風格的,但ListView的滾動條是傳統風格的,但我加了換膚功能。
另外,我們還應當攔截處理ListView的WM_NCPAINT,將滾動條的區域摳掉,不讓它繪制滾動條。盡管滾動條被模擬窗口遮住了,但還是有必要這樣做,以防止出現意外情況:
if(msg == WM_NCPAINT)
{
HRGN wRgn =NULL;
RECTsbRect = GetScrollBarRect();//讀取滾動條矩形的一個函數
HRGNsRgn = ::CreateRectRgn(sbRect.left, sbRect.top, sbRect.right,sbRect.bottom
if(wParam ==1)
{
RECT wRect;
::GetWindowRect(hListView, &wRect);
wRgn = ::CreateRectRgn(wRect.left, wRect.top, wRect.right,wRect.bottom););
wRgn = ::CombineRgn(wRng, wRgn, sRgn, RGN_DIFF);
}
else
{
wRgn= (HRGN)wParam;
wRgn = ::CombineRgn(wRng, wRgn, sRgn, RGN_DIFF);
}
::DeleteObject(sRgn);
::CallWindowProc(gOldListViewProc, hListView, WM_NCPAINT,WPARAM(wRgn), 0);
if(wParam ==1)
::DeleteObject(wRgn);
return0;
}
另外,還要攔截WM_WINDOWPOSCHANGING消息,因為當ListView的位置、尺寸、Z秩序發生改變時要及時調整模擬窗口的位置、尺寸、Z秩序。為何要在WM_WINDOWPOSCHANGING中進行,而不選擇在 WM_WINDOWPOSCHANGED或WM_MOVE或WM_SIZE中進行呢?因為在WM_WINDOWPOSCHANGING中處理,可讓模擬窗口先于ListView調整自己的位置、尺寸和Z,可避免一些繪制問題。實際我在FreeCL中既用了WM_WINDOWPOSCHANGING,也用到 WM_WINDOWPOSCHANGED兩個消息。在處理WM_WINDOWPOSCHANGING時調整位置、尺寸、Z秩序,在處理 WM_WINDOWPOSCHANGED時,強制重繪模擬窗口。
末了還要說明一點的是,系統可能因為用戶改變控件尺寸導致其滾動條自動消失或自動顯示,或者調整滾動條的可用狀態,如你拉寬支持多行顯示的Edit控件,會導致滾動條箭頭按鈕變灰,Thumb消失。這些動作通常都是在 WM_WINDOWPOSCHANGED中完成的,因此這個消息也需要攔截處理:
if(msg == WM_WINDOWPOSCHANGED)
{
LRESULTlReturn = ::CallWindowProc(gOldListViewProc, hListView,WM_WINDOWPOSCHANGED,
wParam, lParam);
if(::GetNextWindow(hBuddy, GW_HWNDNEXT ) !=hListView)
{ //調整模擬窗口的Z-Order,確保其緊貼ListView之上
HWND hAbove = ::GetNextWindow(hListView, GW_HWNDPREV);
UINT flag =SWP_NOACTIVATE|SWP_NOREDRAW|SWP_NOMOVE|SWP
_NOSIZE|SWP_NOSENDCHANGING;
::SetWindowPos(hBuddy, hAbove?hAbove:HWND_TOP, 0, 0, 0, 0,flag);
}
//測試滾動條的顯隱狀態是否改變,并因之調整模擬窗口的顯隱狀態
//如果滾動條仍舊顯示或滾動條某些元素的顯示狀態有變化或控件位置、尺寸可能改變,
//強制重繪模擬窗口
//......
returnlReturn;
}
最后要處理WM_SHOWWINDOW和WM_DESTROY,當ListView隱藏時讓模擬窗口也隨之消失;當它顯示時,讓模擬窗口也隨之顯示。當ListView銷毀時會觸發WM_DESTROY,這時也需要銷毀模擬窗口,這很重要。
總的來講,模擬法是蠻麻煩的,但比較安全,可靠性高。對開發者來講,都是對消息進行特定處理,是常規手段,對最終用戶來講,用此法實現的滾動條自繪,完全能夠滿足要求,具有充分的自由度,甚至可以對滾動條添加更多特定的功能,比如可以在上面添加其他按鈕,就算是加動畫、加廣告也都是可以的。
本站文章除注明轉載外,均為本站原創或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@ke049m.cn
文章轉載自:博客轉載