MFC自繪製按鈕

MFC自繪製按鈕


如果你希望能夠在自己的程序中表現出新意,那麼你一定不會僅僅滿足於MFC提供那些標準控件。這時,我們就必須自己另外多做些工作了。就改變控件外觀這一 點來說,主要是利用控件的自繪功能(Owner Draw)實現的。本篇將和各位一起定義一個XP風格的CXPButton按鈕類,目的不在於介紹CXPButton類的使用技巧,而在於向各位闡述實現 自繪按鈕的方法。當然如果你覺得CXPButton有用的話,也可以把它的源文件保存下來,直接加入到自己的項目中。

本篇要點:
一、準備工作
二、實現原理及難點
三、按鈕類的使用
四、小結與提示
五、附錄

MFC自繪製按鈕 - 輝 - 回首望星辰 一、準備工作

在開始編碼之前,首先應該確定好,更準確的說應該是設計好按鈕在各種狀態下的外觀。按鈕控件的幾中基本狀態包括:
Normal狀態,就是按鈕一開始顯示時的樣子。
Over狀態,鼠標指針移動到按鈕上面時按鈕顯示的樣子。
Down狀態,按下按鈕時顯示的樣子。
Focus狀態,按鈕按下後鬆開的樣子,例如標準按鈕按下鬆開之後會看到按鈕內部有一個虛線框。
Disable狀態,當然就是按鈕被設置成無效的時候的樣子啦。

我參考了一下WindowsXP中普通按鈕的實際樣子,設計出XP按鈕各種狀態的外觀,如下圖所示:

MFC自繪製按鈕 - 輝 - 回首望星辰
至於Down狀態主要是在Over狀態的基礎上將文字往右下的方向稍微平移,以實現下壓的效果。

MFC自繪製按鈕 - 輝 - 回首望星辰 二、實現原理及難點

下面我們開始類的創建,在Workspace的ClassView頁中右擊列表樹的根結點,選擇New Class…

MFC自繪製按鈕 - 輝 - 回首望星辰

在彈出窗口中進行派生類的定義,如下圖所示,注意,你需要填寫的只有Name和Base class兩項,其餘的選項保持默認值就可以了。

MFC自繪製按鈕 - 輝 - 回首望星辰

按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消息函數。
MFC自繪製按鈕 - 輝 - 回首望星辰

函數的代碼見如下,這段代碼非常有用,在其它的自繪控件中,如果想觸發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);的函數聲明,緊接其下輸入

afx_msg LRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnMouseHover(WPARAM wParam, LPARAM lParam);

然後在XPButton.cpp文件中找到ON_WM_MOUSEMOVE(),緊接其後輸入

ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
ON_MESSAGE(WM_MOUSEHOVER, OnMouseHover)

當然最後還有函數的實現了,詳細代碼可見本篇提供的源程序,這裏就不再重複了。

難點二:
下面我們看看DRAWITEMSTRUCE結構爲我們提供了哪些有用信息呢?
DRAWITEMSTRUCT結構的定義如下:

typedef struct tagDRAWITEMSTRUCT {
UINT CtlType; //控件類型
UINT CtlID; //控件ID
UINT itemID; //菜單項、列表框或組合框中某一項的索引值
UINT itemAction; //控件行爲
UINT itemState; //控件狀態
HWND hwndItem; //父窗口句柄或菜單句柄
HDC hDC; //控件對應的繪圖設備句柄
RECT rcItem; //控件所佔據的矩形區域
DWORD itemData; //列表框或組合框中某一項的值
} DRAWITEMSTRUCT, *PDRAWITEMSTRUCT, *LPDRAWITEMSTRUCT;

其實不僅是按鈕控件,其它控件,如ComboBox、ListBox、StaticText等都是通過DRAWITEMSTRUCT來記錄控件信息的。關於這個結構的詳細文檔可參考本篇的附錄。

也許你早已看到許多自繪按鈕的例子,實際上自繪按鈕本身的函數結構都是差不多的,它們顯示效果的區別主要取決於代碼編寫者對GDI作圖函數的運用與掌握程 度。有興趣的朋友可以研究一下CXPButton類中DrawItem函數的數據結構,其實只要修改一下其中GDI繪圖函數的部分代碼,馬上又能做出另一 個自繪按鈕控件了。

MFC自繪製按鈕 - 輝 - 回首望星辰 三、按鈕類的使用

下面演示CXPButton類的使用。往對話框中添加一個按鈕控件,假設它的ID值爲IDC_BUTTON1。進入類嚮導(Class Wizard)的Member Variables屬性頁,爲IDC_BUTTON1添加一個變量m_btnNormal。確定退出後再進行編譯,就可以看到重新定義過XP風格按鈕了。

MFC自繪製按鈕 - 輝 - 回首望星辰

如果你是之間把CXPButton的源文件引入自己的工程中的,那麼在上圖的Variable type中是看不到CXPButton選項的。但是可以通過以下方法加入:

1. 首先保存工程後退出。
2. 在工程的目錄下找到一個後綴名爲.clw的文件,將其刪除。但是爲了以防萬一還是建議你實現備份一下。
3. 重新打開工程,進入類嚮導,此時會看到一下一個彈出對話框,我們選擇“是(Yes)”。

MFC自繪製按鈕 - 輝 - 回首望星辰

4. 再選擇“Add All”,這樣我們就可以在類嚮導中使用CXPButton的變量類型了。

MFC自繪製按鈕 - 輝 - 回首望星辰 四、小結與提示

對於按鈕來說,當按鈕上面任何可見的部分發生變換的時候,都要調用DrawItem函數進行重繪。自繪製按鈕必須設定BS_OWNERDRAW的屬性,設 置的代碼在PreSubclassWindows函數中完成。另外爲了防止系統字體設置的變化影響控件的表達效果,還可以在該函數中爲控件指定某種固定的 字體。但是要注意的是這個
讓我們來回顧一下實現自繪按鈕的基本步驟:
a. 確定設計方案;
b. 初始化,但是記得要在函數退出前恢復先前的GDI對象,並釋放所佔領的資源;
c. 添加相應消息函數;
d. 添加繪圖函數DrawItem,在DrawItem中作圖的順序一般是先畫外邊框,再上底色,接着寫文字,最後是畫內邊框。不過有些人也喜歡把邊框放到最後畫,這問題不大。

MFC自繪製按鈕 - 輝 - 回首望星辰 五、附錄

DRAWITEMSTRUCT結構文檔(根據Msdn翻譯)

DRAWITEMSTRUCT

DRAWITEMSTRUCT 爲需要自繪的控件或者菜單項提供了必要的信息。在需要繪製的控件或者菜單項對應的WM_DRAWITEM消息函數中得到一個指向該結構的指針。 DRAWITEMSTRUCT結構的定義如下:
typedef struct tagDRAWITEMSTRUCT {
UINT CtlType;
UINT CtlID;
UINT itemID;
UINT itemAction;
UINT itemState;
HWND hwndItem;
HDC hDC;
RECT rcItem;
ULONG_PTR itemData;
} DRAWITEMSTRUCT;

結構成員:

CtlType
指定了控件的類型,其取值如下表所示。

取值
描述

ODT_BUTTON
按鈕控件

ODT_COMBOBOX
組合框控件

ODT_LISTBOX
列表框控件

ODT_LISTVIEW
列表視圖控件

ODT_MENU
菜單項

ODT_STATIC
靜態文本控件

ODT_TAB
Tab控件

CtlID

指定了自繪控件的ID值,而對於菜單項則不需要使用該成員

itemID
表示菜單項ID,也可以表示列表框或者組合框中某項的索引值。對於一個空的列表框或組合框,該成員的值爲–1。這時應用程序只繪製焦點矩形(該矩形的座標由rcItem 成員給出)雖然此時控件中沒有需要顯示的項,但是繪製焦點矩形還是很有必要的,因爲這樣做能夠提示用戶該控件是否具有輸入焦點。當然也可以設置itemAction 成員爲合適值,使得無需繪製焦點。

itemAction
指定繪製行爲,其取值可以爲下表中所示值的一個或者多個的聯合。

取值
描述

ODA_DRAWENTIRE
當整個控件都需要被繪製時,設置該值

ODA_FOCUS
如果控件需要在獲得或失去焦點時被繪製,則設置該值。此時應該檢查itemState成員,以確定控件是否具有輸入焦點。

ODA_SELECT
如果控件需要在選中狀態改變時被繪製,則設置該值。此時應該檢查itemState 成員,以確定控件是否處於選中狀態。

itemState
指定了當前繪製操作完成後,所繪項的可見狀態。例如,如果菜單項應該被灰色顯示,則可以指定ODS_GRAYED狀態標誌。其取值可以爲下表中所示值的一個或者多個的聯合。

取值
描述

ODS_CHECKED
如果菜單項將被選中,則可設置該值。該值只對菜單項有用。

ODS_COMBOBOXEDIT
在自繪組合框控件中只繪製選擇區域。

ODS_DEFAULT
默認值。

ODS_DISABLED
如果控件將被禁止,則設置該值。

ODS_FOCUS
如果控件需要輸入焦點,則設置該值。

ODS_GRAYED
如果控件需要被灰色顯示,則設置該值。該值只在繪製菜單時使用。

ODS_HOTLIGHT
Windows 98/Me, Windows 2000/XP: 如果鼠標指針位於控件之上,則設置該值,這時控件會顯示高亮顏色。

ODS_INACTIVE
Windows 98/Me, Windows 2000/XP: 表示沒有激活的菜單項。

ODS_NOACCEL
Windows 2000/XP: 控件是否有快速鍵盤。

ODS_NOFOCUSRECT
Windows 2000/XP: 不繪製捕獲焦點的效果。

ODS_SELECTED
選中的菜單項。

hwndItem
指定了組合框、列表框和按鈕等自繪控件的窗口句柄;如果自繪的對象時菜單項,則表示包含該菜單項的菜單句柄。

hDC
指定了繪製操作所使用的設備環境。

rcItem
指定了將被繪製的矩形區域。這個矩形區域就是上面hDC的作用範圍。系統會自動裁剪組合框、列表框或按鈕等控件的自繪製區域以外的部分。也就是說 rcItem中的座標點(0,0)指的就是控件的左上角。但是系統不裁剪菜單項,所以在繪製菜單項的時候,必須先通過一定的換算得到該菜單項的位置,以保 證繪製操作在我們希望的區域中進行。

itemData
對於菜單項,該成員的取值可以是由

CMenu::AppendMenu、
CMenu::InsertMenu或者
CMenu::ModifyMenu

等函數傳遞給菜單的值。

對於列表框或這組合框,該成員的值可以爲由

ComboBox::AddString、
CComboBox::InsertString、
CListBox::AddString或者
CListBox::InsertString

等傳遞給控件的值。

如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC, itemData的取值爲0。

 

本文轉自:http://vanlee820816.blog.163.com/blog/static/17981351201032835729207/

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