1. 程式人生 > >循序漸進實現仿QQ介面(四):圓形按鈕與工具欄自繪

循序漸進實現仿QQ介面(四):圓形按鈕與工具欄自繪

這一篇本來應該演示如何實現仿QQ介面的中間客戶區與底部工具欄,不過在實現底部工具欄的時候發現圓形按鈕與工具欄自繪有不少取巧的方法,因此加插這麼一篇,講解一下如何實現圓形按鈕和工具欄自繪。

前面幾篇都是在講解如何實現QQ頂部的標題欄,是用視窗貼圖實現,也講到底部區域會用不同的方法實現,因此這裡底部的QQ按鈕和工具欄不是在主視窗上畫圖了,而是用控制元件實現。並且這裡講解的方法不侷限於使用RingSDK介面庫及實現這個仿QQ介面程式,類似的效果用MFC或API都可以輕易實現。

講到圓形按鈕,大家一定會想到要實現按鈕的自繪,然而有取巧的方法,就是用靜態文字控制元件進行模擬,完全不需要自繪。先截個QQ的圖作為資源,如下圖:


這張圖包含了兩個按鈕,一個收起/展開側邊欄的按鈕和彈出主選單的QQ按鈕,兩個都是圓形按鈕。我們先建一個跟這張圖一樣大小的無邊框的子視窗,用這張圖作為視窗背景,想來這個大家都會,用RingSDK介面庫只要呼叫一下SetBkgBitmap就一切搞定。然後建兩個靜態文字控制元件,一個13*13大小,一個36*36大小,位置正好覆蓋那兩個按鈕。靜態文字控制元件本來就是透明的,只要不設定文字,兩個控制元件就跟不存在一樣,不影響背景,但是佔據了位置卻正好可以模擬出按鈕的動作。當然,這兩個控制元件還要先CreateEllipticRgn,再SetWindowRgn一下,使其成為圓形,這樣滑鼠就必須進入到這個圓形按鈕區域才會有響應動作,這下不用費勁在主視窗判斷滑鼠位置了:)

首先是模擬滑鼠移上去的高亮狀態,滑鼠移到按鈕上,父視窗是沒有WM_MOUSEMOVE訊息的,因此這裡必須要由控制元件處理這個訊息,需要子類化這兩個靜態文字控制元件,MFC有個第3方的靜態文字控制元件,類名不記得了,是實現超文字連結的,如果有這個類的話,可以不用自己進行子類化,用這個類就可以了,RingSDK介面庫則已經封裝了這個功能,只要呼叫RingStatic::SetHyperlink就可以實現。實現這個超文字連結功能的原理是呼叫TrackMouseEvent,然後處理WM_MOUSEHOVER和WM_MOUSELEAVE訊息,這兩個訊息會在滑鼠移入控制元件和離開控制元件各發送一次,因此不需要象前面的貼圖按鈕一樣用個標誌記錄是否繪製過按鈕的各種狀態,只需要在WM_MOUSEHOVER訊息裡繪製按鈕的高亮狀態,在WM_MOUSELEAVE訊息裡恢復按鈕的原始狀態就行了。介面庫的RingStatic類因為在這兩個訊息裡有自己的處理,沒有把這兩個訊息轉發給父視窗,而是傳送了WM_LINKHOVER和WM_LINKLEAVE兩個自定義訊息,因此演示程式的繪製是在這兩個訊息裡處理,實際是跟WM_MOUSEHOVER和WM_MOUSELEAVE訊息一樣的。繪製程式碼很簡單,把兩張用到的資源圖片貼出來,大家一看就知道了,就是把圖片的不同區域繪製到視窗。第2張圖片採用連續繪製實現了動畫顯示,因為需要調色的關係,這張圖片是實時生成的。

程式碼裡面的m_dibXXX幾個物件在初始化的時候設定了繪製目標視窗是兩個靜態文字控制元件,因此這裡看不到GetDC之類的程式碼,封裝起來了,相應的因為目標DC是兩個靜態文字控制元件,座標也比較好計算。這些程式碼改成GDI操作也比較容易。靜態文字控制元件已經設定成圓形,DC以外的圖象會繪製到父視窗,這樣正好滿足我們的要求,因為那個小按鈕滑鼠移上去會有一圈光暈(不是很明顯),而這個光暈是在按鈕範圍以外。

除去初始化建立靜態文字控制元件,圖片調色和子類化靜態文字控制元件程式碼以外,只要響應上面兩個訊息就實現了QQ2009左下角兩個按鈕的模擬,是不是很簡單?子類化靜態文字控制元件的程式碼可以去看RingSDK介面庫的ringstatic.cpp或者是擴充套件的MFC超文字連結控制元件類的程式碼。QQ2009的這兩個按鈕沒有按下的狀態,但是很容易新增實現,演示程式裡的相關程式碼也很容易就可以改成MFC或API的,可以實現自己的比較酷的按鈕效果。順便說一下,建立的靜態文字控制元件必須帶上SS_NOTIFY視窗型別,否則點選後父視窗是收不到訊息的。父視窗的視窗型別不需要帶上WS_CLIPCHILDREN和WS_CLIPSIBLINGS型別,否則不會繪製靜態文字控制元件佔據的區域背景。

接下來說一下工具欄的自繪。

實現自繪工具欄一種方法是子類化,自己處理其WM_PAINT訊息。但是工具欄按鈕的狀態實在太多,按鈕形態,扁平形態,高亮,按下,不可用等等狀態,是否顯示文字,是否顯示下拉箭頭等等,要做出一個通用的自繪工具欄實在是一件很麻煩的事。這裡教你一個取巧的辦法,以QQ2009的工具欄為例,子類化工具欄是逃不掉的,但是隻要處理WM_ERASEBKGND訊息,刷上你要的背景就可以了,不需要處理WM_PAINT訊息。先建立工具欄,帶上TBSTYLE_FLAT型別,扁平按鈕,然後準備好如下兩張圖片:

接下來,下面所列出的程式碼是呼叫的API:

另一張圖片如法炮製,SendMessage(m_hWndToolbar,TB_SETHOTIMAGELIST,0,(LPARAM)himg);設定為高亮圖案。然後傳送TB_INSERTBUTTON訊息給工具欄新增按鈕,TBBUTTON結構的iBitmap設定為圖片中相應按鈕圖案的序號(從0開始),這樣生成的工具欄按鈕圖案是不侷限於256色的。滑鼠移到按鈕上,就顯示為第2張圖相應的高亮圖案,但是按鈕周圍還是有一個邊框,這是系統幫你畫的,高亮圖案上已經畫了個漂亮的邊框,我們不需要這個邊框,怎麼去掉呢,父視窗響應NM_CUSTOMDRAW的WM_NOTIFY訊息:

要求自繪按鈕時返回TBCDRF_NOEDGES,系統就不繪製按鈕邊框了,這樣這個工具欄乍看就跟QQ2009的工具欄一樣了。但是按鈕按下去的狀態就露餡了,因為高亮圖案上畫的邊框也跟著圖示一起按下去了,這個邊框畢竟不是真的邊框,只是高亮圖案罷了。當然如果這個效果你能接受,那也可以了,至此打住,可以省不少事。

現在要使按鈕按下去僅按下圖示,邊框位置不動。可是工具欄只能設定兩個IMAGELIST,沒有按下狀態的IMAGELIST可以設定,因此只能自繪,就需要判斷lpnmdraw->uItemState標誌,如果是CDIS_SELECTED,就自繪按鈕的按下狀態,可是要自繪按鈕,又很麻煩了,畫邊框,畫圖示,畫文字...,嘿嘿,這裡又有取巧的方法,我們只要畫個按下狀態的邊框就可以了,還是返回TBCDRF_NOEDGES,其他的系統會幫我們畫的。不過系統幫我們畫上去的是高亮圖案,又有一個按下去的邊框,所以我們要取消高亮圖案,建立工具欄的時候只需要傳送TB_SETIMAGELIST訊息設定按鈕圖示就可以了,高亮和按下的邊框我們自己畫,準備好下面一張圖:

左邊是高亮邊框,右邊是按下的邊框。自繪時根據lpnmdraw->uItemState標誌把這張圖的相應邊框畫上去:

OK,就這麼簡單,現在這個工具欄就跟QQ2009的工具欄的效果一樣了。

現在說說演示程式裡的工具欄實現,程式碼差不多,但是實際複雜得多,因為介面庫對此進行了封裝。介面庫實現了任意子視窗和控制元件都可以停靠,因此QQ按鈕視窗和工具欄是作為停靠視窗停靠在主介面的下方,只是設定了不能拖動。因為是停靠視窗,所以你在主視窗的WM_SIZE訊息裡看不到移動QQ按鈕視窗和工具欄的程式碼,封裝起來了。這個不是本篇重點,有興趣的可以去看介面庫裡ringdocksite.cpp的程式碼。

現在看看程式的截圖:

變化不是很大,本篇旨在教大家一個自繪按鈕和自繪工具欄的比較簡單的方法,不知道說清楚沒有,有問題或是不同意見歡迎留言討論。下一篇會實現QQ2009的客戶區域和左下角按鈕彈出的異型選單。