如果你希望能夠在自己的程序中表現出新意,那麼你一定不會僅僅滿足於MFC提供那些標准控件。這時,我們就必須自己另外多做些工作了。就改變控件外觀這一點來說,主要是利用控件的自繪功能(Owner Draw)實現的。本篇將和各位一起定義一個XP風格的CXPButton按鈕類,目的不在於介紹CXPButton類的使用技巧,而在於向各位闡述實現自繪按鈕的方法。當然如果你覺得CXPButton有用的話,也可以把它的源文件保存下來,直接加入到自己的項目中。
一、准備工作
在開始編碼之前,首先應該確定好,更准確的說應該是設計好按鈕在各種狀態下的外觀。按鈕控件的幾中基本狀態包括:
Normal狀態,就是按鈕一開始顯示時的樣子。
Over狀態,鼠標指針移動到按鈕上面時按鈕顯示的樣子。
Down狀態,按下按鈕時顯示的樣子。
Focus狀態,按鈕按下後松開的樣子,例如標准按鈕按下松開之後會看到按鈕內部有一個虛線框。
Disable狀態,當然就是按鈕被設置成無效的時候的樣子啦。
我參考了一下WindowsXP中普通按鈕的實際樣子,設計出XP按鈕各種狀態的外觀,如下圖所示:
至於Down狀態主要是在Over狀態的基礎上將文字往右下的方向稍微平移,以實現下壓的效果。
二、實現原理及難點
下面我們開始類的創建,在Workspace的ClassView頁中右擊列表樹的根結點,選擇New Class…
在彈出窗口中進行派生類的定義,如下圖所示,注意,你需要填寫的只有Name和Base class兩項,其余的選項保持默認值就可以了。
按OK按鈕退出之後,我們可以在ClassView裡面看到新創建的類的名字。接下來我們可以為CXPButton類添加各種成員變量。因為自繪控件說穿了就是畫圖,所以在成員變量中可以看到各種與畫圖有關的數據類型,一般來說成員變量會在類的構造函數中初始化,在類的析構函數中銷毀。詳細代碼請參見本篇附帶的源程序。
下面簡要敘述一下按鈕的實現原理:
1. 在控件初始化時為按鈕添加Owner Draw的屬性。這是因為在MFC中,要想激活控件的自繪功能,要求該控件的屬性中必須包含屬性值BS_OWNERDRAW,這一步我們可以通過類向導為CXPButton類添加PreSubclassWindow()函數,在該函數中完成屬性值的設置。當激活控件的自繪功能之後,每次控件狀態改變的時候都會運行函數DrawItem(),該函數的作用就是繪制控件在各種狀態下的外觀。
2. 添加WM_MOUSELEAVE消息函數,當鼠標指針離開按鈕時,觸發該消息函數,我們在函數中添加代碼,通知DrawItem函數鼠標指針已經離開了,讓按鈕重繪。
3. 添加WM_MOUSEHOVER消息函數,當鼠標指針位於按鈕之上時,觸發該消息函數,我們在函數重添加代碼,通知DrawItem函數鼠標指針現在正在按鈕的上面,讓按鈕重繪。
4. 添加DrawItem函數。在DrawItem中根據按鈕當前的狀態繪制按鈕的外觀。可以說自繪控件的大部分功能都是在這個函數中實現的。DrawItem函數包含了一個LPDRAWITEMSTRUCT的指針,本篇會在稍後予以講解。
了解了基本的設計思路之後,剩下就看我們怎麼去實現了。我本人覺得這裡有兩個難點,首先是WM_MOUSELEAVE和WM_MOUSEHOVER不是標准的Windows消息函數,它們不能通過類向導來添加,所有的添加工作都需要通過手工輸入代碼來完成。另一個難點是DrawItem中的LPDRAWITEMSTRUCT指針,它指向了一個DRAWITEMSTRUCT的結構,這個結構中包含了控件的各種細節,為我們提供了實現自繪功能的必要信息。
難點一:
事實上WM_MOUSELEAVE和WM_MOUSEHOVER兩個Windows消息是通過WM_MOUSEMOVE消息觸發的,而WM_MOUSEMOVE是標准的Windows消息,因此我們可以通過類向導來為CXPButton類添加WM_MOUSEMOVE消息函數。
函數的代碼見如下,這段代碼非常有用,在其它的自繪控件中,如果想觸發WM_MOUSELEAVE和WM_MOUSEHOVER消息,也是使用類似的方法實現的。
void CXPButton::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if (!m_bTracking)
{
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.hwndTrack = m_hWnd;
tme.dwFlags = TME_LEAVE | TME_HOVER;
tme.dwHoverTime = 1;
m_bTracking = _TrackMouseEvent(&tme);
}
CButton::OnMouseMove(nFlags, point);
}
我們接著添加WM_MOUSELEAVE和WM_MOUSEHOVER消息消息函數。在CXPButton類的聲明中(即在XPButton.h文件中)找到afx_msg void OnMouseMove(UINT nFlags, CPoint point);的函數聲明,緊接其下輸入
本文示例代碼或素材下載