3.3.4 WM_DRAWITEM
OnCtlColor只能修改元素的顏色,但不能修改元素的界面框架,WM_DRAWITEM則可以。
當一個(gè)具有Owner draw風(fēng)格的元素(包括按鈕、組合框、列表框和菜單等)需要顯示外觀(guān)時(shí),該元素會(huì )發(fā)送一條WM_DRAWITEM消息至它的隸屬窗口(Owner)。
WM_DRAWITEM的映射函數原型如下:
afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );
參數:
nIDCtl 該控件的ID,如果該元素為菜單,則nIDCtl為0
lpDrawItemStruct 指向DRAWITEMSTRUCT結構對象的指針,DRAWITEMSTRUCT的結構定義如下:
CtlType指定了控件的類(lèi)型,其取值如表3所示:
類(lèi)型值 含義
ODT_BUTTON 按鈕控件
ODT_COMBOBOX 組合框控件
ODT_LISTBOX 列表框控件
ODT_LISTVIEW 列表視圖
ODT_MENU 菜單項
ODT_STATIC 靜態(tài)文本控件
ODT_TAB Tab控件
表3 CtlType的類(lèi)型值與含義
CtlID 指定自繪控件的ID值,該成員不適用于菜單項
itemID表示菜單項ID,也可以表示列表框或者組合框中某項的索引值。對于一個(gè)空的列表框或組合框,該成員的值為?C1。這時(shí)應用程序只繪制焦點(diǎn)矩形(該矩形的坐標由rcItem 成員給出)雖然此時(shí)控件中沒(méi)有需要顯示的項,但是繪制焦點(diǎn)矩形還是很有必要的,因為這樣做能夠提示用戶(hù)該控件是否具有輸入焦點(diǎn)。當然也可以設置itemAction 成員為合適值,使得無(wú)需繪制焦點(diǎn)。
itemAction 指定繪制行為,其取值為表4中所示值的一個(gè)或者多個(gè)的聯(lián)合:
類(lèi)型值 含義
ODA_DRAWENTIRE 當整個(gè)控件都需要被繪制時(shí),設置該值。
ODA_FOCUS 如果控件需要在獲得或失去焦點(diǎn)時(shí)被繪制,則設置該值。此時(shí)應該檢查itemState成員,以確定控件是否具有輸入焦點(diǎn)。
ODA_SELECT 如果控件需要在選中狀態(tài)改變時(shí)被繪制,則設置該值。此時(shí)應該檢查itemState 成員,以確定控件是否處于選中狀態(tài)。
表4 itemAction的類(lèi)型值與含義
itemState 指定了當前繪制項的狀態(tài)。例如,如果菜單項應該被灰色顯示,則可以指定ODS_GRAYED狀態(tài)標志。其取值為表5中所示值的一個(gè)或者多個(gè)的聯(lián)合:
類(lèi)型值 含義
ODS_CHECKED 標記狀態(tài),僅適用于菜單項。
ODS_DEFAULT 默認狀態(tài)。
ODS_DISABLED 禁止狀態(tài)。
ODS_FOCUS 焦點(diǎn)狀態(tài)。
ODS_GRAYED 灰化狀態(tài),僅適用于菜單項。
ODS_SELECTED 選中狀態(tài)。
ODS_HOTLIGHT 僅適用于Windows 98/Me/Windows 2000/XP,熱點(diǎn)狀態(tài):如果鼠標指針位于控件之上,則設置該值,這時(shí)控件會(huì )顯示高亮顏色。
ODS_INACTIVE 僅適用于Windows 98/Me/Windows 2000/XP,非激活狀態(tài)。
ODS_NOACCEL 僅適用于Windows 2000/XP,控件是否有快速鍵。
ODS_COMBOBOXEDIT 在自繪組合框控件中只繪制選擇區域。
ODS_NOFOCUSRECT 僅適用于Windows 2000/XP,不繪制捕獲焦點(diǎn)的效果。
表5 itemState的類(lèi)型值與含義
hwndItem 指定了組合框、列表框和按鈕等自繪控件的窗口句柄;如果自繪的對象為菜單項,則表示包含該菜單項的菜單句柄。
hDC 指定了繪制操作所使用的設備環(huán)境。
rcItem 指定了將被繪制的矩形區域。這個(gè)矩形區域就是上面hDC的作用范圍。系統會(huì )自動(dòng)裁剪組合框、列表框或按鈕等控件的自繪制區域以外的部分。也就是說(shuō)rcItem中的坐標點(diǎn)(0,0)指的就是控件的左上角。但是系統不裁剪菜單項,所以在繪制菜單項的時(shí)候,必須先通過(guò)一定的換算得到該菜單項的位置,以保證繪制操作在我們希望的區域中進(jìn)行。
itemData
對于菜單項,該成員的取值為由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函數傳遞給菜單的值。
對于列表框或這組合框,該成員的取值為由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函數傳遞給控件的值。
如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值為0。
圖5是個(gè)相應的例子,它修改了按鈕的界面:

圖8 利用WM_DRAWITEM消息美化界面
實(shí)現代碼如下:
別忘了標記Owner draw屬性:

圖9 指定按鈕的Owner draw屬性
值得一提的是,CWnd內部截獲了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相應虛函數的調用,如CButton::DrawItem()。所以,以上例子也可以通過(guò)派生出一個(gè)CButton的派生類(lèi),并重載該類(lèi)的DrawItem()函數來(lái)實(shí)現。使用虛函數機制實(shí)現界面美化參見(jiàn)3.4章節。
3.3.5 WM_MEASUREITEM
僅僅WM_DRAWITEM還是不夠的,對于一些特殊的控件,如ListBox,系統在發(fā)送WM_DRAWITEM消息前,還發(fā)送WM_MEASUREITEM消息,需要你設置ListBox中每個(gè)項目的高度。
WM_DRAWITEM的映射函數原型如下:
afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
nIDCtl 該控件的ID,如果該元素為菜單,則nIDCtl為0
lpMeasureItemStruct指向MEASUREITEMSTRUCT結構對象的指針,MEASUREITEMSTRUCT的結構定義如下:
CtlType指定了控件的類(lèi)型,其取值如表6所示:
類(lèi)型值 含義
ODT_COMBOBOX 組合框控件
ODT_LISTBOX 列表框控件
ODT_MENU 菜單項
表6 CtlType的類(lèi)型值與含義
CtlID 指定自繪控件的ID值,該成員不適用于菜單項
itemID表示菜單項ID,也可以表示可變高度的列表框或組合框中某項的索引值。該成員不適用于固定高度的列表框或組合框。
itemWidth 指定菜單項的寬度
itemHeight指定菜單項或者列表框中某項的的高度,最大值為255
itemData
對于菜單項,該成員的取值為由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函數傳遞給菜單的值。
對于列表框或這組合框,該成員的取值為由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函數傳遞給控件的值。
圖示出了OnMeasureItem的效果:

圖10 利用WM_MEASUREITEM消息美化界面
相應的OnMeasureItem()實(shí)現如下:
同樣別忘了指定列表框的Owner draw屬性:

圖11 指定下拉框的Owner draw屬性
3.3.6 NM_CUSTOMDRAW
大家也許熟悉WM_NOTIFY,控件通過(guò)WM_NOTIFY向父窗口發(fā)送消息。在WM_NOTIFY消息體中,部分控件會(huì )發(fā)送NM_CUSTOMDRAW告訴父窗口自己需要繪圖。
可以反射N(xiāo)M_CUSTOMDRAW消息,如:
ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult);
參數:
pNMHDR 說(shuō)到底只是一個(gè)指針,大多數情況下它指向一個(gè)NMHDR結構對象,NMHDR結構如下:
其中:
hwndFrom 發(fā)送方控件的窗口句柄
idFrom 發(fā)送方控件的ID
code 通知代碼
對于某些控件來(lái)說(shuō),pNMHDR則會(huì )解釋成其它內容更豐富的結構對象的指針,如:對于列表控件來(lái)說(shuō),pNMHDR常常指向一個(gè)NMCUSTOMDRAW對象,NMCUSTOMDRAW結構如下:
hdr NMHDR對象
dwDrawStage 當前繪制狀態(tài),其取值如表7所示:
類(lèi)型值 含義
CDDS_POSTERASE 擦除循環(huán)結束
CDDS_POSTPAINT 繪制循環(huán)結束
CDDS_PREERASE 準備開(kāi)始擦除循環(huán)
CDDS_PREPAINT 準備開(kāi)始繪制循環(huán)
CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam參數有效
CDDS_ITEMPOSTERASE 列表項擦除結束
CDDS_ITEMPOSTPAINT 列表項繪制結束
CDDS_ITEMPREERASE 準備開(kāi)始列表項擦除
CDDS_ITEMPREPAINT 準備開(kāi)始列表項繪制
CDDS_SUBITEM 指定列表子項
表7 dwDrawStage的類(lèi)型值與含義
hdc指定了繪制操作所使用的設備環(huán)境。
rc指定了將被繪制的矩形區域。
dwItemSpec 列表項的索引
uItemState 當前列表項的狀態(tài),其取值如表8所示:
類(lèi)型值 含義
CDIS_CHECKED 標記狀態(tài)。
CDIS_DEFAULT 默認狀態(tài)。
CDIS_DISABLED 禁止狀態(tài)。
CDIS_FOCUS 焦點(diǎn)狀態(tài)。
CDIS_GRAYED 灰化狀態(tài)。
CDIS_SELECTED 選中狀態(tài)。
CDIS_HOTLIGHT 熱點(diǎn)狀態(tài)。
CDIS_INDETERMINATE 不定狀態(tài)。
CDIS_MARKED 標注狀態(tài)。
表8 uItemState的類(lèi)型值與含義
lItemlParam 當前列表項的綁定數據
pResult 指向狀態(tài)值的指針,指定系統后續操作,依賴(lài)于dwDrawStage:
當dwDrawStage為CDDS_PREPAINT,pResult含義如表9所示:
類(lèi)型值 含義
CDRF_DODEFAULT 默認操作,即系統在列表項繪制循環(huán)過(guò)程不再發(fā)送NM_CUSTOMDRAW。
CDRF_NOTIFYITEMDRAW 指定列表項繪制前后發(fā)送消息。
CDRF_NOTIFYPOSTERASE 列表項擦除結束時(shí)發(fā)送消息。
CDRF_NOTIFYPOSTPAINT 列表項繪制結束時(shí)發(fā)送消息。
表9 pResult的類(lèi)型值與含義(一)
當dwDrawStage為CDDS_ITEMPREPAINT,pResult含義如表10所示:
類(lèi)型值 含義
CDRF_NEWFONT 指定后續操作采用應用中指定的新字體。
CDRF_NOTIFYSUBITEMDRAW 列表子項繪制時(shí)發(fā)送消息。
CDRF_SKIPDEFAULT 系統不必再繪制該子項。
表10 pResult的類(lèi)型值與含義(二)
以下是一個(gè)利用NM_CUSTOMDRAW消息繪制出的多色列表框的例子:

圖12 利用NM_CUSTOMDRAW消息美化界面
對應代碼如下:
注意到上例采取了3.1所推薦的第2種實(shí)現方法,派生了一個(gè)新類(lèi)CCoolList。
3.4 使用MFC類(lèi)的虛函數機制
修改Windows界面,除了從Windows消息機制下功夫,也可以從MFC類(lèi)下功夫,這應該得益于類(lèi)的虛函數機制。為了防止諸如“面向對象技術(shù)”等術(shù)語(yǔ)在此泛濫,以下僅舉一段代碼作為例子:
這是MFC中viewcore.cpp中的源代碼,很多讀者總不明白OnDraw()和OnPaint()之間的關(guān)系,從以上的代碼中很容易看出,CView的WM_PAINT消息響應函數OnPaint()會(huì )自動(dòng)調用CView::OnDraw()。而作為開(kāi)發(fā)者的用戶(hù),可以通過(guò)簡(jiǎn)單的OnDraw()的重載實(shí)現對WM_PAINT的處理。所以說(shuō),對MFC類(lèi)的虛函數的重載是對消息機制的擴展。
以下列出了與界面美化相關(guān)的虛函數,參數說(shuō)明略去:
CButton::DrawItem
CCheckListBox::DrawItem
CComboBox::DrawItem
CHeaderCtrl::DrawItem
CListBox::DrawItem
CMenu::DrawItem
CStatusBar::DrawItem
CStatusBarCtrl::DrawItem
CTabCtrl::DrawItem
virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );
Owner draw元素自繪函數
很顯然,位圖菜單都是通過(guò)這個(gè)DrawItem畫(huà)出來(lái)的。限于篇幅,在此不再附以例程。
本文為白喬原創(chuàng ),曾經(jīng)在《電腦愛(ài)好者》合訂本上發(fā)表。
聯(lián)系客服