天天看點

mfc自繪控件的實作 收藏

mfc自繪控件的實作 收藏

http://blog.csdn.net/cug_stu/archive/2010/11/15/6010966.aspx

源碼已經上傳。

自繪按鈕的實作

                                                                                     作者:杜修杏

 一、準備工作

在開始編碼之前,首先應該确定好,更準确的說應該是設計好按鈕在各種狀态下的外觀。按鈕控件的幾中基本狀态包括:

Normal狀态,就是按鈕一開始顯示時的樣子。

Over狀态,滑鼠指針移動到按鈕上面時按鈕顯示的樣子。

Down狀态,按下按鈕時顯示的樣子。

Focus狀态,按鈕按下後松開的樣子,例如标準按鈕按下松開之後會看到按鈕内部有一個虛線框。

Disable狀态,當然就是按鈕被設定成無效的時候的樣子啦。

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

至于Down狀态主要是在Over狀态的基礎上将文字往右下的方向稍微平移,以實作下壓的效果。

 二、實作原理及難點

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

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

下面簡要叙述一下按鈕的實作原理:

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);的函數聲明,緊接其下輸入

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繪圖函數的部分代碼,馬上又能做出另一個自繪按鈕控件了。

 三、按鈕類的使用

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

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

1. 首先儲存工程後退出。

2. 在工程的目錄下找到一個字尾名為.clw的檔案,将其删除。但是為了以防萬一還是建議你實作備份一下。

3. 重新打開工程,進入類向導,此時會看到一下一個彈出對話框,我們選擇“是(Yes)”。

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

 四、小結與提示

對于按鈕來說,當按鈕上面任何可見的部分發生變換的時候,都要調用DrawItem函數進行重繪。自繪制按鈕必須設定BS_OWNERDRAW的屬性,設定的代碼在PreSubclassWindows函數中完成。另外為了防止系統字型設定的變化影響控件的表達效果,還可以在該函數中為控件指定某種固定的字型。但是要注意的是這個

讓我們來回顧一下實作自繪按鈕的基本步驟:

a. 确定設計方案;

b. 初始化,但是記得要在函數退出前恢複先前的GDI對象,并釋放所占領的資源;

c. 添加相應消息函數;

d. 添加繪圖函數DrawItem,在DrawItem中作圖的順序一般是先畫外邊框,再上底色,接着寫文字,最後是畫内邊框。不過有些人也喜歡把邊框放到最後畫,這問題不大。

 五、附錄

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。

本文來自CSDN部落格,轉載請标明出處:http://blog.csdn.net/cug_stu/archive/2010/11/15/6010966.aspx

繼續閱讀