在您決定開發(fā) Windows 提供的常規(guī)免費自定義控件范圍之外的控件之后,您必需確定自己的控件將有多少獨到之處 — 在功能和外觀兩方面。例如,我們假定您正在創(chuàng)建一個類似于計速表的控件。由于公共控件庫 (ComCtrl32.dll) 中沒有類似的控件,您完全需要自己進(jìn)行以下操作:編寫所有控件功能需要的代碼,進(jìn)行繪制,默認(rèn)終端用戶的交互,以及控件與其父窗口之間需要的任意消息處理。
(#add 兩方面,公共控件庫中沒有類似的 完全重寫; 只想調(diào)整公共控件功能,則可以部分修改)
另一方面,還包括一些您只想調(diào)整公共控件功能的情況。例如,我們假定您想創(chuàng)建一個屏蔽編輯控件,它只允許接受指定的字符。如果使用 MFC,通常涉及從 MFC 提供的類派生一個類,該類封裝了一個公共控件(在屏蔽編輯控件中,通常為 CEdit),重寫必需的虛函數(shù)(或處理指定的消息),然后加入自定義的代碼。
本文討論的重點介于兩者之間 — 公共控件賦予您想要的大部分功能,但控件的外觀并不是您想要的。例如,列表視圖控件提供在許多視圖風(fēng)格中顯示數(shù)據(jù)列表的方式 — 小圖標(biāo)、大圖標(biāo)、列表和詳細(xì)列表(報告)。然而,如果您想要一個網(wǎng)格控件,那結(jié)果怎樣呢?盡管公共控件庫里沒有特別包含網(wǎng)格,但是列表視圖控件與它較為接近,它以行和列顯示數(shù)據(jù),并有一個相關(guān)的標(biāo)頭控件。因此,許多人以一個標(biāo)準(zhǔn)的列表視圖控件為起點創(chuàng)建自己的網(wǎng)格控件,然后重寫該控件及其子項的呈現(xiàn)方式或繪制方式。
即使“只”進(jìn)行繪制,您仍然有至少四種選項可用,它們都具有鮮明的優(yōu)缺點:
·處理 WM_PAINT
·所有者繪制(owner draw)
·自定義繪制(custom draw)
·處理 WM_CTLCOLOR
處理 WM_PAINT
最極端的選擇是執(zhí)行一個 WM_PAINT 處理程序,并且自己完成所有的繪制。這意味著,您的代碼將需要進(jìn)行一些與呈現(xiàn)控件相關(guān)的瑣事 — 創(chuàng)建適當(dāng)?shù)脑O(shè)備上下文(一個或多個),決定控件的大小和位置,繪制控件等。在繪制過程中,很少需要這種級別的控件。
所有者繪制
控制控件繪制的另一種方法是利用所有者繪制。事實上,您也許聽開發(fā)人員提到過所有者繪制控件,因為它是用于開發(fā)自定義控件最普通的技術(shù)。該技術(shù)普遍使用的主要原因在于,Windows 可為您提供很多幫助。在呈現(xiàn)控件的那一刻,Windows 就已經(jīng)創(chuàng)建并填寫了設(shè)備上下文,決定了控件的大小和位置,并且向您傳遞信息以使您了解此刻繪制的需求。對于列表控件(例如,列表框和列表視圖),Windows 將為列表中的每一項調(diào)用繪制代碼,這意味著您只需繪制這些項,而無需考慮控件的其他方面。注意,所有者繪制可用于大多數(shù)控件。然而,它不能用于編輯控件;并且考慮到列表控件,它只能用于報表視圖樣式。
自定義繪制
對于繪制自己的控件而言,這可能是最少為人所知的技術(shù)。事實上,許多技術(shù)能力較高的開發(fā)人員也混淆了術(shù)語所有者繪制 (owner-draw) 和自定義繪制 (custom-draw)。關(guān)于自定義控件,首先需要了解,它僅針對于指定的公共控件:標(biāo)頭、列表視圖、rebar、工具欄、工具提示、跟蹤條和樹視圖。此外,盡管所有者繪制只允許繪制報告視圖風(fēng)格的列表視圖控件,而自定義繪制則使您能夠處理列表視圖控件所有視圖風(fēng)格的繪制。使用自定義繪制的另一個明顯優(yōu)勢是,您可以對希望繪制的內(nèi)容進(jìn)行嚴(yán)格挑選。實現(xiàn)方式是,在控件繪制的每個階段由 Windows 向代碼發(fā)送一個消息。這樣,您可以決定在每個階段是自己進(jìn)行所有的繪制工作,增加默認(rèn)的繪制,還是允許 Windows 為該階段執(zhí)行所有的繪制。(鑒于自定義繪制是本文的一個主題,因此您很快會看到它的工作方式。)
處理 WM_CTLCOLOR消息
這可能是幫助決定如何呈現(xiàn)控件最簡單的方式。正如消息名所指,當(dāng)要繪制一個控件,并且它能讓您的代碼決定要使用的畫筆時,發(fā)送 WM_CTLCOLOR 消息(#add 似乎不對,應(yīng)該用消息反射)。通常情況下,如果您只想更改控件的顏色(#addSetTextColor SetBkColor),并且不提供除控件本身之外的更多功能,則使用該技術(shù)。此外,對于由 Internet Explorer 引入的公共控件(列表視圖、樹視圖、rebar 等),不發(fā)送該消息,并且它只與標(biāo)準(zhǔn)控件(編輯、列表框等)協(xié)同使用。
CTLCOLOR_STATIC Static control
CTLCOLOR_BTN Button control
CTLCOLOR_EDIT Edit control
CTLCOLOR_LISTBOX List-box control
CTLCOLOR_SCROLLBAR Scroll-bar control
CTLCOLOR_DLG Dialog box
CTLCOLOR_MSGBOX Message box
不會為組合框中的下拉列表框調(diào)用OnCtlColor函數(shù),因為下拉列表框?qū)嶋H上是組合框的子窗口,而不是窗口的子窗口。要改變下拉列表框的顏色,創(chuàng)建一個CComboBox,在重載的OnCtlColor中的nCtlColor參數(shù)中檢查CTLCOLOR_LISTBOX。在這個處理函數(shù)中,為設(shè)置文本的背景必須使用SetBkColor成員函數(shù)。
自定義繪制:
既然您已經(jīng)了解了繪制控件可用的各種選項(包括使用自定義繪制的好處),那么,讓我們來看看實現(xiàn)一個自定義繪制控件需要的三個主要步驟。
·執(zhí)行一個 NM_CUSTOMDRAW 消息處理程序。
·指定處理所需的繪制階段。
·篩選特定的繪制階段(在這些階段中,您需要加入自己的特定于控件的繪制代碼)。
1,執(zhí)行一個NM_CUSTOMDRAW 消息處理程序
當(dāng)需要繪制一個公共控件時,MFC 會將控件的自定義繪制通知消息(最初發(fā)送到控件的父窗口)以 NM_CUSTOMDRAW 消息的形式反饋給控件。以下是一個 NM_CUSTOMDRAW 處理程序的示例。
void CMyCustomDrawControl::OnCustomDraw(NMHDR* pNMHDR, LRESULT*pResult)
{
LPNMCUSTOMDRAW pNMCD =reinterpret_cast(pNMHDR);
...
}
正如您所見,NM_CUSTOMDRAW 處理程序?qū)⒁粋€指針傳遞給 NMHDR 類型的結(jié)構(gòu)。然而,該值不足以用于象 NMHDR 這樣只包含三個成員(hwndFrom、idFrom 和 code)的結(jié)構(gòu)。
因此,您通常需要將該結(jié)構(gòu)指針轉(zhuǎn)換為信息量更大的結(jié)構(gòu) — LPNMCUSTOMDRAW。LPNMCUSTOMDRAW指向 NMCUSTOMDRAW,它包含諸如 dwDrawStage、dwItemSpec 和 uItemState 這樣的成員 — 它們是決定當(dāng)前繪制階段及確切繪制(例如,控件本身、或控件的一個項目或子項)所必需的。
這里值得注意的是,還可以將 NMHDR 指針指向特定于正在繪制控件的類型的結(jié)構(gòu)。表 1 顯示控件的一個列表及其相關(guān)的自定義繪制結(jié)構(gòu)類型名。
表 1:控件及其相關(guān)的自定義繪制結(jié)構(gòu)
控件 | 結(jié)構(gòu)(在 commctrl.h 中定義) |
Rebar、Trackbar、AuthTicket、My.Resources、My.Settings、My.User 和 My.WebServices。 | NMCUSTOMDRAW |
List-view | NMLVCUSTOMDRAW |
Toolbar | NMTBCUSTOMDRAW |
Tooltip | NMTTCUSTOMDRAW |
Tree-view | NMTVCUSTOMDRAW |
2,指定處理所需的繪制階段
正如我在前面提到的,繪制一個控件存在一些“階段”。特別是,您可以將繪制過程理解為一系列階段,其中控件通知其父窗口需要繪制的內(nèi)容。事實上,控件甚至?xí)诶L制控件及其各項前后發(fā)送一個通知,從而讓編程人員更好地控制該過程。
在所有情況下,單一的 NM_CUSTOMDRAW 處理程序在每個繪制階段都進(jìn)行調(diào)用。然而,謹(jǐn)記:自定義繪制允許您在自己的繪制中合并默認(rèn)的控件繪制,您需要指定您將處理哪個繪制階段。這通過設(shè)置 NM_CUSTOMDRAW 處理程序的第二個參數(shù) (pResult) 完成。事實上,如果您從未設(shè)置該值,則用初始階段的 CDDS_PREPAINT 調(diào)用函數(shù)后,您的函數(shù)將不再被調(diào)用!
從技術(shù)上講,只有兩個階段指定需要的繪制階段(CDDS_PREPAINT 和 CDDS_ITEMPREPAINT),它們影響發(fā)送通知消息的內(nèi)容。然而,通常只在處理程序的最后指定代碼將處理的繪制階段。表 2 列出用于指定所需繪制階段(代碼關(guān)注的)的值。
表 2:自定義繪制返回標(biāo)志
自定義繪制返回標(biāo)志 | 含義 |
CDRF_DEFAULT | 指示控件自行繪制。該值為默認(rèn)值,不應(yīng)該將它與其他值組合在一起。 |
CDRF_SKIPDEFAULT | 用于指定控件根本不進(jìn)行任何繪制。 |
CDRF_NEWFONT | 當(dāng)代碼更改繪制項/子項的字體時使用。 |
CDRF_NOTIFYPOSTPAINT | 使通知信息在控件或每個項/子項繪制后發(fā)送。 |
CDRF_NOTIFYITEMDRAW | 指出項(或子項)將進(jìn)行繪制。注意,它下面的值與 CDRF_NOTIFYSUBITEMDRAW 相同。 |
CDRF_NOTIFYSUBITEMDRAW | 指出子項(或項)將進(jìn)行繪制。注意,它下面的值與 CDRF_NOTIFYITEMDRAW 相同。 |
CDRF_NOTIFYPOSTERASE | 當(dāng)刪除控件后需要通知代碼時使用。 |
以下為一個示例,其中的代碼指定,當(dāng)繪制控件的項 (CDRF_NOTIFYITEMDRAW) 及子項(CDRF_NOTIFYPOSTPAINT),以及繪制完成時,應(yīng)該調(diào)用 NM_CUSTOMDRAW 處理程序。
void CListCtrlWithCustomDraw::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT*pResult)
{
LPNMCUSTOMDRAW pNMCD =reinterpret_cast(pNMHDR);
...
*pResult = 0; // Initialize value
*pResult |= CDRF_NOTIFYITEMDRAW;
*pResult |= CDRF_NOTIFYSUBITEMDRAW;
*pResult |= CDRF_NOTIFYPOSTPAINT;
}
3,篩選指定的繪制階段
一旦指定要關(guān)注的階段后,您需要處理這些階段。因為繪制過程的每個階段只有一個消息要發(fā)送,慣例是執(zhí)行一個 switch 語句以決定準(zhǔn)確的繪制階段。不同的繪制階段由以下標(biāo)志定義:
CDDS_PREPAINT
CDDS_ITEM
CDDS_ITEMPREPAINT
CDDS_ITEMPOSTPAINT
CDDS_ITEMPREERASE
CDDS_ITEMPOSTERASE
CDDS_SUBITEM
CDDS_POSTPAINT
CDDS_PREERASE
CDDS_POSTERASE
對于一個 CListCtrl 派生的類,有一個 NM_CUSTOMDRAW 處理程序的示例,其中您可以發(fā)現(xiàn),代碼決定當(dāng)前繪制階段的方式:
void CMyCustomDrawControl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMCUSTOMDRAW pNMCD =reinterpret_cast(pNMHDR);
switch(pNMCD->dwDrawStage)
{
case CDDS_PREPAINT:
...
break;
case CDDS_ITEMPREPAINT:
...
break;
case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
...
break;
...
}
*pResult = 0;
}
注意,為了決定子項(例如,列表視圖控件)繪制的階段,您必需使用按位 or 操作符,它有兩個值:其中一個為 CDDS_ITEMPREPAINT 或者CDDS_ITEMPOSTPAINT,另一個為 CDDS_SUBITEM。
要說明它,我們假定您想在繪制列表視圖項之前進(jìn)行一些處理。將編寫 switch 語句來處理 CDDS_ITEMPREPAINT。
case CDDS_ITEMPREPAINT:
...
break;
然而,如果是您所關(guān)注子項的預(yù)繪制階段,則將如下操作:
case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
...
break;
示例:創(chuàng)建一個列表視圖控件自定義繪制控件
如前面提到的,您可以完全控制控件及其項的繪制,或者僅執(zhí)行一小部分特定于應(yīng)用程序的繪制,并讓控件繼續(xù)進(jìn)行。本文的焦點更多地偏重于控件繪制技術(shù)而非高級的繪制技術(shù),我們將演練一個簡單的示例,其中列表視圖控件是一個自定義的繪制,因此項的文本將在創(chuàng)建拼接外觀的交替單元中顯示為不同的顏色。
·創(chuàng)建一個基于 Visual C++ 2005 對話框的項目,名為ListCtrlColor。
·從 Class View 中選擇 Project 菜單選項,并單擊 Add Class 調(diào)用 Add Class 對話框。
·從分類列表中選擇 MFC,然后從模板列表中選擇 MFC Class。
·單擊 Add 按鈕,調(diào)用 MFC Class Wizard 對話框。
·對于 Class name,鍵入值 CListCtrlWithCustomDraw 并選擇 CListCtrl的 Base class。
·單擊 Finish 按鈕,生成類的標(biāo)頭和執(zhí)行文件。
·對于 Class View,右鍵單擊 CListCtrlWithCustomDraw 類,并選擇Properties 上下文菜單選項。
·顯示 Properties 窗口時,單擊頂部的 Messages 按鈕,顯示一個兩列的消息列表,您可以為其實現(xiàn)處理程序。
·在消息列表中單擊 NM_CUSTOMDRAW 項,然后下拉第二列的組合框箭頭,并選擇值 OnNMCustomdraw。
·現(xiàn)在,處理繪制代碼。這里,我們只簡單處理項和子項預(yù)繪制階段,指定基于當(dāng)前行(項)和列(子項)的文本和背景色。要進(jìn)行此操作,按如下所示修改 OnNMCustomdraw 函數(shù):
void CListCtrlWithCustomDraw::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT*pResult)
{
LPNMLVCUSTOMDRAW lpLVCustomDraw =reinterpret_cast(pNMHDR);
switch(lpLVCustomDraw->nmcd.dwDrawStage)
{
case CDDS_ITEMPREPAINT:
case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
if (0 == ((lpLVCustomDraw->nmcd.dwItemSpec+ lpLVCustomDraw->iSubItem) % 2))
{
lpLVCustomDraw->clrText =RGB(255,255,255); // white text
lpLVCustomDraw->clrTextBk =RGB(0,0,0); // black background
}
else
{
lpLVCustomDraw->clrText =CLR_DEFAULT;
lpLVCustomDraw->clrTextBk =CLR_DEFAULT;
}
break;
default: break;
}
*pResult = 0;
*pResult |= CDRF_NOTIFYPOSTPAINT;
*pResult |= CDRF_NOTIFYITEMDRAW;
*pResult |= CDRF_NOTIFYSUBITEMDRAW;
}
現(xiàn)在,我們來測試新控件。要進(jìn)行此操作,您只需使用 CListCtrlWithCustomDraw 類將列表視圖控件放在對話框中,并對其進(jìn)行子類派生。下面是完成該操作的步驟。
·在 Resource 視圖中,打開應(yīng)用程序的主對話框 (IDD_LISTCTRLCOLOR_DIALOG)。
·從 Toolbox 中,將一個 List Control 拖放到該對話框。
·右鍵單擊列表控件,并選擇 Properties 上下文菜單選項。
·將 View 屬性設(shè)置為 Report。
·右鍵單擊控件,并選擇 Add Variable 上下文菜單選項。
·出現(xiàn) Add Member Variable Wizard 對話框時,指定m_lstBooks 的 Variable name,并單擊 Finish 按鈕。
·這時,您就有了一個 CListCtrl 派生類 (m_lstBooks),它將對話框上的列表視圖控件進(jìn)行子類派生。然而,m_lstBooks 需要從最新創(chuàng)建的 CListCtrlWithCustomDraw 派生,以便于調(diào)用您的繪制代碼。因此,打開對話框的標(biāo)題文件 (ListCtrlColorDlg.h),將 m_lstBooks 更改為 CListCtrlWithCustomDraw 類型。
·在 CListCtrlColorDlg 類開始之前,添加以下指令。
#include"ListCtrlWithCustomDraw.h"
·將下面的代碼添加到對話框的 OnInitDialog 成員函數(shù),這樣我們就能夠看到一些列表視圖行。
// Insert the columns
m_lstBooks.InsertColumn(0, _T("Author"));
m_lstBooks.InsertColumn(1, _T("Book"));
// Define the data
static struct
{
TCHAR m_szAuthor[50];
TCHAR m_szTitle[100];
} BOOK_INFO[] = {
_T("Tom Archer"),_T("Visual C++.NET Bible"),
_T("Tom Archer"),_T("Extending MFC with the .NET Framework"),
_T("Brian Johnson"),_T("XBox 360 For Dummies")
};
// Insert the data
int idx;
for (int i = 0; i < sizeof BOOK_INFO / sizeof BOOK_INFO[0]; i++)
{
idx = m_lstBooks.InsertItem(i,BOOK_INFO[i].m_szAuthor);
m_lstBooks.SetItemText(i, 1,BOOK_INFO[i].m_szTitle);
}
·現(xiàn)在,建立并運行應(yīng)用程序。圖 1 為應(yīng)用程序外觀的一個示例。

圖 1. 自定義繪制示例應(yīng)用程序
小結(jié)
當(dāng) Windows 首次作為“下一代”操作系統(tǒng)引入到應(yīng)用程序開發(fā)之中時,它作為新圖形用戶界面的一個主要論據(jù)就是其一致性。該論據(jù)的要點所在是其具有一個通用的外觀:統(tǒng)一的菜單項、通用控件等。這一通用性的感覺可能會一直延續(xù),直到有第二家公司想設(shè)計其自己的應(yīng)用程序。簡單說,提供外觀與其他應(yīng)用程序雷同的應(yīng)用程序,任何公司都不會逃離這一怪圈。
要建立一個唯一的且讓人過目難忘的用戶界面,其中一種方式是為應(yīng)用程序設(shè)計并開發(fā)自定義的控件。希望本文能對您有所幫助,現(xiàn)在,您了解到一種非常強大的技術(shù),它使您的應(yīng)用程序能從眾多競爭對手的應(yīng)用程序中脫穎而出。
OWNER DRAW實現(xiàn)自繪按鈕
一、準(zhǔn)備工作
在開始編碼之前,首先應(yīng)該確定好,更準(zhǔn)確的說應(yīng)該是設(shè)計好按鈕在各種狀態(tài)下的外觀。按鈕控件的幾中基本狀態(tài)包括:
Normal狀態(tài),就是按鈕一開始顯示時的樣子。
Over狀態(tài),鼠標(biāo)指針移動到按鈕上面時按鈕顯示的樣子。
Down狀態(tài),按下按鈕時顯示的樣子。
Focus狀態(tài),按鈕按下后松開的樣子,例如標(biāo)準(zhǔn)按鈕按下松開之后會看到按鈕內(nèi)部有一個虛線框。
Disable狀態(tài),當(dāng)然就是按鈕被設(shè)置成無效的時候的樣子啦。
我參考了一下WindowsXP中普通按鈕的實際樣子,設(shè)計出XP按鈕各種狀態(tài)的外觀,如下圖所示:

至于Down狀態(tài)主要是在Over狀態(tài)的基礎(chǔ)上將文字往右下的方向稍微平移,以實現(xiàn)下壓的效果。
二、實現(xiàn)原理及難點
下面我們開始類的創(chuàng)建,在Workspace的ClassView頁中右擊列表樹的根結(jié)點,選擇New Class…

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

下面簡要敘述一下按鈕的實現(xiàn)原理:
1. 在控件初始化時為按鈕添加Owner Draw的屬性。這是因為在MFC中,要想激活控件的自繪功能,要求該控件的屬性中必須包含屬性值BS_OWNERDRAW,這一步我們可以通過類向?qū)?CXPButton類添加PreSubclassWindow()函數(shù),在該函數(shù)中完成屬性值的設(shè)置。當(dāng)激活控件的自繪功能之后,每次控件狀態(tài)改變的時候都會運行函數(shù)DrawItem(),該函數(shù)的作用就是繪制控件在各種狀態(tài)下的外觀。
2. 添加WM_MOUSELEAVE消息函數(shù),當(dāng)鼠標(biāo)指針離開按鈕時,觸發(fā)該消息函數(shù),我們在函數(shù)中添加代碼,通知DrawItem函數(shù)鼠標(biāo)指針已經(jīng)離開了,讓按鈕重繪。
3. 添加WM_MOUSEHOVER消息函數(shù),當(dāng)鼠標(biāo)指針位于按鈕之上時,觸發(fā)該消息函數(shù),我們在函數(shù)重添加代碼,通知DrawItem函數(shù)鼠標(biāo)指針現(xiàn)在正在按鈕的上面,讓按鈕重繪。
4. 添加DrawItem函數(shù)。在DrawItem中根據(jù)按鈕當(dāng)前的狀態(tài)繪制按鈕的外觀??梢哉f自繪控件的大部分功能都是在這個函數(shù)中實現(xiàn)的。DrawItem函數(shù)包含了一個LPDRAWITEMSTRUCT的指針,本篇會在稍后予以講解。
這里有兩個難點,首先是WM_MOUSELEAVE和 WM_MOUSEHOVER不是標(biāo)準(zhǔn)的Windows消息函數(shù),它們不能通過類向?qū)硖砑?,所有的添加工作都需要通過手工輸入代碼來完成。另一個難點是 DrawItem中的LPDRAWITEMSTRUCT指針,它指向了一個DRAWITEMSTRUCT的結(jié)構(gòu),這個結(jié)構(gòu)中包含了控件的各種細(xì)節(jié),為我們提供了實現(xiàn)自繪功能的必要信息。
難點一:
事實上WM_MOUSELEAVE和WM_MOUSEHOVER兩個Windows消息是通過WM_MOUSEMOVE消息觸發(fā)的,而 WM_MOUSEMOVE是標(biāo)準(zhǔn)的Windows消息,因此我們可以通過類向?qū)頌镃XPButton類添加WM_MOUSEMOVE消息函數(shù)。

函數(shù)的代碼見如下,這段代碼非常有用,在其它的自繪控件中,如果想觸發(fā)WM_MOUSELEAVE和WM_MOUSEHOVER消息,也是使用類似的方法實現(xiàn)的。
voidCXPButton::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler codehere 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消息消息函數(shù)。在CXPButton類的聲明中(即在 XPButton.h文件中)找到afx_msg void OnMouseMove(UINT nFlags, CPoint point);的函數(shù)聲明,緊接其下輸入
afx_msgLRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam);
afx_msgLRESULT OnMouseHover(WPARAM wParam, LPARAM lParam);
然后在XPButton.cpp文件中找到ON_WM_MOUSEMOVE(),緊接其后輸入
ON_MESSAGE(WM_MOUSELEAVE,OnMouseLeave)
ON_MESSAGE(WM_MOUSEHOVER,OnMouseHover)
難點二:
下面我們看看DRAWITEMSTRUCE結(jié)構(gòu)為我們提供了哪些有用信息呢?
DRAWITEMSTRUCT結(jié)構(gòu)的定義如下:
typedefstruct tagDRAWITEMSTRUCT
{
UINT CtlType; //控件類型
UINT CtlID; //控件ID
UINT itemID; //菜單項、列表框或組合框中某一項的索引值
UINT itemAction; //控件行為
UINT itemState; //控件狀態(tài)
HWND hwndItem; //父窗口句柄或菜單句柄
HDC hDC; //控件對應(yīng)的繪圖設(shè)備句柄
RECT rcItem; //控件所占據(jù)的矩形區(qū)域
DWORD itemData; //列表框或組合框中某一項的值
}DRAWITEMSTRUCT, *PDRAWITEMSTRUCT, *LPDRAWITEMSTRUCT;
其實不僅是按鈕控件,其它控件,如ComboBox、ListBox、StaticText等都是通過DRAWITEMSTRUCT來記錄控件信息的。關(guān)于這個結(jié)構(gòu)的詳細(xì)文檔可參考本篇的附錄。
也許你早已看到許多自繪按鈕的例子,實際上自繪按鈕本身的函數(shù)結(jié)構(gòu)都是差不多的,它們顯示效果的區(qū)別主要取決于代碼編寫者對GDI作圖函數(shù)的運用與掌握程度。有興趣的朋友可以研究一下CXPButton類中DrawItem函數(shù)的數(shù)據(jù)結(jié)構(gòu),其實只要修改一下其中GDI繪圖函數(shù)的部分代碼,馬上又能做出另一個自繪按鈕控件了。
三、按鈕類的使用
下面演示CXPButton類的使用。往對話框中添加一個按鈕控件,假設(shè)它的ID值為IDC_BUTTON1。進(jìn)入類向?qū)В–lass Wizard)的Member Variables屬性頁,為IDC_BUTTON1添加一個變量m_btnNormal。確定退出后再進(jìn)行編譯,就可以看到重新定義過XP風(fēng)格按鈕了。

如果你是之間把CXPButton的源文件引入自己的工程中的,那么在上圖的Variable type中是看不到CXPButton選項的。但是可以通過以下方法加入:
1. 首先保存工程后退出。
2. 在工程的目錄下找到一個后綴名為.clw的文件,將其刪除。但是為了以防萬一還是建議你實現(xiàn)備份一下。
3. 重新打開工程,進(jìn)入類向?qū)?,此時會看到一下一個彈出對話框,我們選擇“是(Yes)”。

4. 再選擇“Add All”,這樣我們就可以在類向?qū)е惺褂肅XPButton的變量類型了。
四、小結(jié)與提示
對于按鈕來說,當(dāng)按鈕上面任何可見的部分發(fā)生變換的時候,都要調(diào)用DrawItem函數(shù)進(jìn)行重繪。自繪制按鈕必須設(shè)定BS_OWNERDRAW的屬性,設(shè)置的代碼在PreSubclassWindows函數(shù)中完成。另外為了防止系統(tǒng)字體設(shè)置的變化影響控件的表達(dá)效果,還可以在該函數(shù)中為控件指定某種固定的字體。但是要注意的是這個
讓我們來回顧一下實現(xiàn)自繪按鈕的基本步驟:
a. 確定設(shè)計方案;
b. 初始化,但是記得要在函數(shù)退出前恢復(fù)先前的GDI對象,并釋放所占領(lǐng)的資源;
c. 添加相應(yīng)消息函數(shù);
d. 添加繪圖函數(shù)DrawItem,在DrawItem中作圖的順序一般是先畫外邊框,再上底色,接著寫文字,最后是畫內(nèi)邊框。不過有些人也喜歡把邊框放到最后畫,這問題不大。
五、附錄
DRAWITEMSTRUCT結(jié)構(gòu)文檔 (根據(jù)Msdn翻譯)
DRAWITEMSTRUCT
DRAWITEMSTRUCT為需要自繪的控件或者菜單項提供了必要的信息。在需要繪制的控件或者菜單項對應(yīng)的WM_DRAWITEM消息函數(shù)中得到一個指向該結(jié)構(gòu)的指針。 DRAWITEMSTRUCT結(jié)構(gòu)的定義如下:
typedef struct tagDRAWITEMSTRUCT
{
UINTCtlType ;
UINTCtlID ;
UINTitemID ;
UINTitemAction ;
UINTitemState ;
HWNDhwndItem ;
HDC hDC;
RECTrcItem ;
ULONG_PTRitemData ;
} DRAWITEMSTRUCT;
結(jié)構(gòu)成員:
CtlType
指定了控件的類型,其取值如下表所示。
取值 | 描述 |
ODT_STATIC | 靜態(tài)文本控件 |
ODT_BUTTON | 按鈕控件 |
ODT_COMBOBOX | 組合框控件 |
ODT_LISTBOX | 列表框控件 |
ODT_LISTVIEW | 列表視圖控件 |
ODT_MENU | 菜單項 |
ODT_TAB | Tab控件 |
CtlID
指定了自繪控件的ID值,而對于菜單項則不需要使用該成員
itemID
表示菜單項ID,也可以表示列表框或者組合框中某項的索引值。對于一個空的列表框或組合框,該成員的值為–1。這時應(yīng)用程序只繪制焦點矩形(該矩形的坐標(biāo)由rcItem 成員給出)雖然此時控件中沒有需要顯示的項,但是繪制焦點矩形還是很有必要的,因為這樣做能夠提示用戶該控件是否具有輸入焦點。當(dāng)然也可以設(shè)置itemAction 成員為合適值,使得無需繪制焦點。
itemAction
指定繪制行為,其取值可以為下表中所示值的一個或者多個的聯(lián)合。
取值 | 描述 |
ODA_DRAWENTIRE | 當(dāng)整個控件都需要被繪制時,設(shè)置該值 |
ODA_FOCUS | 如果控件需要在獲得或失去焦點時被繪制,則設(shè)置該值。此時應(yīng)該檢查itemState成員,以確定控件是否具有輸入焦點。 |
ODA_SELECT | 如果控件需要在選中狀態(tài)改變時被繪制,則設(shè)置該值。此時應(yīng)該檢查itemState 成員,以確定控件是否處于選中狀態(tài)。 |
itemState
指定了當(dāng)前繪制操作完成后,所繪項的可見狀態(tài)。例如,如果菜單項應(yīng)該被灰色顯示,則可以指定ODS_GRAYED狀態(tài)標(biāo)志。其取值可以為下表中所示值的一個或者多個的聯(lián)合。
取值 | 描述 |
ODS_CHECKED | 如果菜單項將被選中,則可設(shè)置該值。該值只對菜單項有用。 |
ODS_COMBOBOXEDIT | 在自繪組合框控件中只繪制選擇區(qū)域。 |
ODS_DEFAULT | 默認(rèn)值。 |
ODS_DISABLED | 如果控件將被禁止,則設(shè)置該值。 |
ODS_FOCUS | 如果控件需要輸入焦點,則設(shè)置該值。 |
ODS_GRAYED | 如果控件需要被灰色顯示,則設(shè)置該值。該值只在繪制菜單時使用。 |
ODS_HOTLIGHT | Windows 98/Me, Windows 2000/XP: 如果鼠標(biāo)指針位于控件之上,則設(shè)置該值,這時控件會顯示高亮顏色。 |
ODS_INACTIVE | Windows 98/Me, Windows 2000/XP: 表示沒有激活的菜單項。 |
ODS_NOACCEL | Windows 2000/XP: 控件是否有快速鍵盤。 |
ODS_NOFOCUSRECT | Windows 2000/XP: 不繪制捕獲焦點的效果。 |
ODS_SELECTED | 選中的菜單項。 |
hwndItem
指定了組合框、列表框和按鈕等自繪控件的窗口句柄;如果自繪的對象時菜單項,則表示包含該菜單項的菜單句柄。
hDC
指定了繪制操作所使用的設(shè)備環(huán)境。
rcItem
指定了將被繪制的矩形區(qū)域。這個矩形區(qū)域就是上面hDC的作用范圍。系統(tǒng)會自動裁剪組合框、列表框或按鈕等控件的自繪制區(qū)域以外的部分。也就是說 rcItem中的坐標(biāo)點(0,0)指的就是控件的左上角。但是系統(tǒng)不裁剪菜單項,所以在繪制菜單項的時候,必須先通過一定的換算得到該菜單項的位置,以保證繪制操作在我們希望的區(qū)域中進(jìn)行。
itemData
對于菜單項,該成員的取值可以是由
CMenu::AppendMenu、
CMenu::InsertMenu或者
CMenu::ModifyMenu
等函數(shù)傳遞給菜單的值。
對于列表框或這組合框,該成員的值可以為由
ComboBox::AddString、
CComboBox::InsertString、
CListBox::AddString或者
CListBox::InsertString
等傳遞給控件的值。
如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC, itemData的取值為0。