循序漸進實現仿QQ界面(六):異型菜單與內建滾動條自繪

本篇演示實現仿QQ界面的異型菜單與滾動條自繪。

 

先講解一下如何實現QQ的圓角菜單,這個要用到HOOK了,因爲菜單是一種特殊的窗口,無法用FindWindow或通過HMENU來獲取到窗口句柄,也就無法子類化。必須下鉤子,這裏下的是WH_CALLWNDPROC的鉤子:

 

 

查MSDN知道菜單的窗口類名是“#32768”,找到菜單窗口就好辦了,子類化後就可以做手腳了,做的手腳不多,只需要實現圓角矩形外形就夠了,菜單的自繪早已實現,是交由主窗口處理的,就不必費手腳了。

 

首先是WM_NCCALCSIZE消息,需要加大其非客戶區,爲畫圓角騰出空間來,但是注意騰出了空間畫圓角,客戶區的面積就縮小了,有可能會使菜單項顯示不全,因此需要在WM_WINDOWPOSCHANGING消息裏把吃掉的客戶區空間再吐出來,就是加大菜單窗口的尺寸,這樣纔不影響菜單項的顯示。怎麼實現及繪製圓角矩形窗口這個系列文章的第一篇就講過了,這裏就不多費口舌了。

 

回答一下有位朋友在我博客前面文章的留言,爲什麼子類化菜單後截不到WM_MOUSEMOVE消息,試了一下的確沒有,其實菜單的WM_MOUSEMOVE消息被替換掉了,變成了MN_MOUSEMOVE消息,值爲0x01EE,定義看這個鏈接:http://topic.csdn.net/t/20050713/18/4142641.html

 

接下來講解怎麼實現QQ左下角按鈕上彈出來的異型菜單,一般做法是自己實現一個異型窗口然後加載菜單,不過既然是HOOK了菜單,還是直接在菜單上實現比較好。首先一個問題是怎麼區分這個主菜單和其他圓角菜單,纔好實現不同的處理。方法是主菜單創建的時候調用SetMenuInfo爲其設置一個值,以後可以通過GetMenuInfo來檢測這個值以鑑別是主菜單:

 

 

 

鑑別出主菜單後就可以實現不同的外形處理了,這個菜單的外形可以有兩種方案,一個是帶上QQ圓形按鈕的外形,彈出時蓋住QQ按鈕。不過考慮到QQ圖案不能調色,繪製時要多道手腳專門畫那個企鵝,還是省點事採用挖掉那個圓形的外形,如下圖(左邊的圖像):

 


由於菜單項是可增減的,因此這個外形必須動態生成,幾何學得好的話可以用線條繪製出來,我是屬於幾何學得不好的,早忘光了:P,因此採用圓角矩形和圖片合成的方式來構造這個外形。看上圖中的右圖,蘭色邊框勾勒出了圓角矩形,可以用CreateRoundRectRgn生成,紅色虛線勾出了需要在這個圓角矩形區域挖掉和添加的部分,黃色是這兩個HRGN相交疊的部分,這塊區域是固定尺寸不會變動的,因此可以用圖片實現,根據這個圖來生成HRGN。這張圖片如下:

 


形狀有點怪異,黑色部分就是需要生成的HRGN形狀,生成HRGN後可以調用CombineRgn來與圓角矩形的HRGN合併,參數採用RGN_XOR:

 

m_hrgn是圓角矩形,hrgn是圖中黑色部分形狀的HRGN,採用RGN_XOR參數的效果是:合併兩個形狀,去掉重疊的部分,結果就是圓角矩形在左下角挖掉了圖中半圓的形狀,添加了下面的圓弧三角,恰好符合QQ主菜單的外形。

 

下面講解一下如何根據圖片來生成HRGN,原理是逐行掃描像素,發現是白色(透明色)不處理,發現是黑色(非透明色)就根據連續的像素數量調用CreateRectRgn來生成高度爲1的HRGN,每一掃描行可能會有一些不連續的點或線,可以通過CombineRgn,採用RGN_OR參數把這些不連續的HRGN合併起來,然後把整個圖象所有掃描行生成的這些HRGN再合併,最終就得到了圖片中指定形狀的HRGN,代碼有點複雜,這裏就不貼了,可以去看RingSDK圖象庫libsrc/ringdib/rdib.cpp裏面的RingDIB::CreateRgn函數代碼。

 

得到最終的HRGN後就可以通過SetWindowRgn實現異型菜單了,當然還要準備好窗口非客戶區的貼圖,左下角的貼圖如下:


怎麼貼圖繪製就不說了,前面的文章都講解過了。

 

接下來就是菜單的彈出問題,計算好位置彈出,貌似可以了,其實還是有工作要做,把窗口移到桌面上方,再點按鈕彈出菜單,暈,菜單是往下彈出了,缺口跑到主界面下方去了,必須限制菜單的彈出方向,這時要用到TrackPopupMenuEx函數的最後一個參數LPTPMPARAMS了,這個參數一般調用時是指定爲NULL,其實是可以用這個參數限制菜單的彈出方向的,這個參數說明如下:

指定rcExclude的座標,菜單彈出時就會避開這個區域,我們把主窗口下方的空間座標賦給這個rcExclude,主菜單彈出時爲了避免覆蓋這個座標,就只能往上彈出,這下終於達到我們期望的效果了。不過且慢,這個rcExclude只是個矩形,我們攔住了菜單不往下彈,卻攔不住左右,把窗口移到屏幕最右邊,再彈出菜單,繼續暈,菜單往左彈出了!缺口跑到了左邊,又露餡了。這下沒有取巧的辦法,只能實現菜單外形的左右翻轉,同時因爲左右翻轉,還需要調整菜單的彈出位置以使缺口套準QQ圓形按鈕。彈出菜單的代碼如下:

 

翻轉菜單不規則外形,其實是先把左下角HRGN圖象橫向翻轉,再生成HRGN。圖象橫向翻轉其實就是把圖象的每一行數據數組進行倒序,屬於C語言的入門功課了,這裏就不說了。

 

現在終於實現了QQ的異型主菜單,剩下最後好友列表區的滾動條了。

 

自繪滾動條的實現:

自繪滾動條其實很簡單,看過這個系列前面的文章大家應該會了,子類化後貼圖不是很難。但是QQ好友列表區的滾動條自繪卻不是那麼簡單,因爲好友列表是用ListBox控件實現,而ListBox的滾動條是內建的,是畫出來的,根本不是窗口!沒辦法子類化,這下完蛋,只能子類化ListBox,自己實現繪製滾動條的代碼。然而有個要命的地方,ListBox繪製滾動條既沒提供接口,也沒提供消息,光在WM_NCPAINT裏面繪製沒用,有很多消息都是直接進行了滾動條的繪製,這下子就需要圍追堵截,把可能繪製滾動條的消息全部截過來自己處理。除了WM_NCPAINT,需要攔截的消息如下:

 

WM_STYLECHANGED
WM_LBUTTONDBLCLK
WM_NCLBUTTONDBLCLK
WM_TIMER
WM_LBUTTONDOWN
WM_NCLBUTTONDOWN
WM_LBUTTONUP
WM_MOUSEWHEEL
WM_SIZE
WM_VSCROLL

那個暈哪!有這工夫還不如自己寫個窗口實現了。事實也確實是,研究這些消息的時間夠自己寫個窗口實現了。不過實現ListBox的滾動條自繪還是有意義的,一個個消息來吧。

 

首先當然要實現WM_NCPAINT消息,把滾動條畫出來,這個不難。實現之後窗口被其他程序覆蓋,再切換到前臺,滾動條象樣子了,不過滾動條一動就露餡了。

 

然後是WM_VSCROLL,ListBox有個窗口類型是LBS_DISABLENOSCROLL,說明沒有滾動條,窗口還是能滾動的,因此在這個消息裏面,先把窗口的WS_VSCROLL類型去掉,然後調用默認的窗口過程,再把WS_VSCROLL類型設置回來。現在效果是按翻頁滾動不會露餡,發送WM_VSCROLL消息設定滾動條位置不會露餡。

 

 

WM_SIZE消息做同樣處理,這下調整窗口大小也不會露餡了。

 

WM_STYLECHANGED,WM_LBUTTONDBLCLK,WM_NCLBUTTONDBLCLK三個消息攔截掉,直接return 0;這下在窗口上雙擊不會露餡了。

 

WM_LBUTTONDOWN,因爲設定了ListBox的LBS_NOINTEGRALHEIGHT類型,因此ListBox的最下面一個可視的選項有可能是顯示不完全的,如果選擇了這個選項,ListBox會滾動窗口讓這個選項完全顯示,這時會重繪滾動條,因此需要在這個消息裏檢測如果選中的是沒有顯示完整的選項,就發送wParam爲SB_LINEDOWN的WM_VSCROLL消息讓其顯示完全,然後選中該選項,return 0;其餘情況交給默認的窗口過程處理。

 

WM_MOUSEWHEEL:計算好要滾動的位置,然後連着發送下面兩個消息就OK了。

 

 

接下來就來到最困難的部分,點擊滾動條上的上下箭頭和拖動滑塊。這個需要先處理WM_NCLBUTTONDOWN和WM_LBUTTONUP消息,WM_NCLBUTTONDOWN消息裏面先檢測鼠標是否點在了滾動條上,是就要SetCapture捕獲鼠標,如果是按在上下箭頭或不在滑塊上,就設定一下定時器,在WM_TIMER消息裏發送wParam爲SB_LINEUP,SB_LINEDOWN,SB_PAGEUP,SB_PAGEDOWN的WM_VSCROLL消息,可以連續滾動。WM_LBUTTONUP消息裏ReleaseCapture,清除定時器,再發送wParam爲SB_ENDSCROLL的WM_VSCROLL消息終止滾動。

 

最難的就是拖動滑塊的處理了,必須自己繪製滑塊位置,定時器檢測並繪製滑塊位置,當檢測到滑塊移動的距離足夠需要滾動窗口的時候,就需要計算出滾動條的滑塊位置併發送wParam爲SB_THUMBPOSITION的WM_VSCROLL消息,ListBox的滾動條滾動範圍(GetScrollRange返回的值)是選項總數-1,繪製時是按像素,因此計算時難免會有誤差,會造成滑塊拖動時的偶爾抖動,不過總算是實現了ListBox內建的滾動條自繪,演示程序就先這樣了,要想達到商用級別尚需進行代碼優化和調整。

 

現在看看程序的截圖:

 

終於完成了,長吁一口氣,在此謝謝大家一直以來的支持和鼓勵。

 

演示程序下載地址:http://download.csdn.net/source/2125267

 

說明:在編寫這個演示程序的時候發現和修正了RingSDK的幾個BUG,因此編譯這個演示程序需要更新到最新版本的RingSDK,不更新的話主菜單和部分調色功能可能會不正常,不過不影響效果演示。這個演示程序的源代碼也已經提交到SVN,更新RingSDK的時候會自動下載下來,可以不用去下演示程序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章