3.3.4 WM_DRAWITEM
OnCtlColor只能修改元素的顏色,但不能修改元素的界面框架,WM_DRAWITEM則可以。
當一個具有Owner draw風格的元素(包括按鈕、組合框、列表框和菜單等)需要顯示外觀時,該元素會發(fā)送一條WM_DRAWITEM消息至它的隸屬窗口(Owner)。
WM_DRAWITEM的映射函數(shù)原型如下:
afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );
參數(shù):
nIDCtl 該控件的ID,如果該元素為菜單,則nIDCtl為0
lpDrawItemStruct 指向DRAWITEMSTRUCT結(jié)構(gòu)對象的指針,DRAWITEMSTRUCT的結(jié)構(gòu)定義如下:
CtlType指定了控件的類型,其取值如表3所示:
類型值 含義
ODT_BUTTON 按鈕控件
ODT_COMBOBOX 組合框控件
ODT_LISTBOX 列表框控件
ODT_LISTVIEW 列表視圖
ODT_MENU 菜單項
ODT_STATIC 靜態(tài)文本控件
ODT_TAB Tab控件
表3 CtlType的類型值與含義
CtlID 指定自繪控件的ID值,該成員不適用于菜單項
itemID表示菜單項ID,也可以表示列表框或者組合框中某項的索引值。對于一個空的列表框或組合框,該成員的值為?C1。這時應用程序只繪制焦點矩形(該矩形的坐標由rcItem 成員給出)雖然此時控件中沒有需要顯示的項,但是繪制焦點矩形還是很有必要的,因為這樣做能夠提示用戶該控件是否具有輸入焦點。當然也可以設(shè)置itemAction 成員為合適值,使得無需繪制焦點。
itemAction 指定繪制行為,其取值為表4中所示值的一個或者多個的聯(lián)合:
類型值 含義
ODA_DRAWENTIRE 當整個控件都需要被繪制時,設(shè)置該值。
ODA_FOCUS 如果控件需要在獲得或失去焦點時被繪制,則設(shè)置該值。此時應該檢查itemState成員,以確定控件是否具有輸入焦點。
ODA_SELECT 如果控件需要在選中狀態(tài)改變時被繪制,則設(shè)置該值。此時應該檢查itemState 成員,以確定控件是否處于選中狀態(tài)。
表4 itemAction的類型值與含義
itemState 指定了當前繪制項的狀態(tài)。例如,如果菜單項應該被灰色顯示,則可以指定ODS_GRAYED狀態(tài)標志。其取值為表5中所示值的一個或者多個的聯(lián)合:
類型值 含義
ODS_CHECKED 標記狀態(tài),僅適用于菜單項。
ODS_DEFAULT 默認狀態(tài)。
ODS_DISABLED 禁止狀態(tài)。
ODS_FOCUS 焦點狀態(tài)。
ODS_GRAYED 灰化狀態(tài),僅適用于菜單項。
ODS_SELECTED 選中狀態(tài)。
ODS_HOTLIGHT 僅適用于Windows 98/Me/Windows 2000/XP,熱點狀態(tài):如果鼠標指針位于控件之上,則設(shè)置該值,這時控件會顯示高亮顏色。
ODS_INACTIVE 僅適用于Windows 98/Me/Windows 2000/XP,非激活狀態(tài)。
ODS_NOACCEL 僅適用于Windows 2000/XP,控件是否有快速鍵。
ODS_COMBOBOXEDIT 在自繪組合框控件中只繪制選擇區(qū)域。
ODS_NOFOCUSRECT 僅適用于Windows 2000/XP,不繪制捕獲焦點的效果。
表5 itemState的類型值與含義
hwndItem 指定了組合框、列表框和按鈕等自繪控件的窗口句柄;如果自繪的對象為菜單項,則表示包含該菜單項的菜單句柄。
hDC 指定了繪制操作所使用的設(shè)備環(huán)境。
rcItem 指定了將被繪制的矩形區(qū)域。這個矩形區(qū)域就是上面hDC的作用范圍。系統(tǒng)會自動裁剪組合框、列表框或按鈕等控件的自繪制區(qū)域以外的部分。也就是說rcItem中的坐標點(0,0)指的就是控件的左上角。但是系統(tǒng)不裁剪菜單項,所以在繪制菜單項的時候,必須先通過一定的換算得到該菜單項的位置,以保證繪制操作在我們希望的區(qū)域中進行。
itemData
對于菜單項,該成員的取值為由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函數(shù)傳遞給菜單的值。
對于列表框或這組合框,該成員的取值為由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函數(shù)傳遞給控件的值。
如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值為0。
圖5是個相應的例子,它修改了按鈕的界面:
圖8 利用WM_DRAWITEM消息美化界面
實現(xiàn)代碼如下:
別忘了標記Owner draw屬性:
圖9 指定按鈕的Owner draw屬性
值得一提的是,CWnd內(nèi)部截獲了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相應虛函數(shù)的調(diào)用,如CButton::DrawItem()。所以,以上例子也可以通過派生出一個CButton的派生類,并重載該類的DrawItem()函數(shù)來實現(xiàn)。使用虛函數(shù)機制實現(xiàn)界面美化參見3.4章節(jié)。
3.3.5 WM_MEASUREITEM
僅僅WM_DRAWITEM還是不夠的,對于一些特殊的控件,如ListBox,系統(tǒng)在發(fā)送WM_DRAWITEM消息前,還發(fā)送WM_MEASUREITEM消息,需要你設(shè)置ListBox中每個項目的高度。
WM_DRAWITEM的映射函數(shù)原型如下:
afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
nIDCtl 該控件的ID,如果該元素為菜單,則nIDCtl為0
lpMeasureItemStruct指向MEASUREITEMSTRUCT結(jié)構(gòu)對象的指針,MEASUREITEMSTRUCT的結(jié)構(gòu)定義如下:
CtlType指定了控件的類型,其取值如表6所示:
類型值 含義
ODT_COMBOBOX 組合框控件
ODT_LISTBOX 列表框控件
ODT_MENU 菜單項
表6 CtlType的類型值與含義
CtlID 指定自繪控件的ID值,該成員不適用于菜單項
itemID表示菜單項ID,也可以表示可變高度的列表框或組合框中某項的索引值。該成員不適用于固定高度的列表框或組合框。
itemWidth 指定菜單項的寬度
itemHeight指定菜單項或者列表框中某項的的高度,最大值為255
itemData
對于菜單項,該成員的取值為由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函數(shù)傳遞給菜單的值。
對于列表框或這組合框,該成員的取值為由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函數(shù)傳遞給控件的值。
圖示出了OnMeasureItem的效果:
圖10 利用WM_MEASUREITEM消息美化界面
相應的OnMeasureItem()實現(xiàn)如下:
同樣別忘了指定列表框的Owner draw屬性:
圖11 指定下拉框的Owner draw屬性
3.3.6 NM_CUSTOMDRAW
大家也許熟悉WM_NOTIFY,控件通過WM_NOTIFY向父窗口發(fā)送消息。在WM_NOTIFY消息體中,部分控件會發(fā)送NM_CUSTOMDRAW告訴父窗口自己需要繪圖。
可以反射NM_CUSTOMDRAW消息,如:
ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult);
參數(shù):
pNMHDR 說到底只是一個指針,大多數(shù)情況下它指向一個NMHDR結(jié)構(gòu)對象,NMHDR結(jié)構(gòu)如下:
其中:
hwndFrom 發(fā)送方控件的窗口句柄
idFrom 發(fā)送方控件的ID
code 通知代碼
對于某些控件來說,pNMHDR則會解釋成其它內(nèi)容更豐富的結(jié)構(gòu)對象的指針,如:對于列表控件來說,pNMHDR常常指向一個NMCUSTOMDRAW對象,NMCUSTOMDRAW結(jié)構(gòu)如下:
hdr NMHDR對象
dwDrawStage 當前繪制狀態(tài),其取值如表7所示:
類型值 含義
CDDS_POSTERASE 擦除循環(huán)結(jié)束
CDDS_POSTPAINT 繪制循環(huán)結(jié)束
CDDS_PREERASE 準備開始擦除循環(huán)
CDDS_PREPAINT 準備開始繪制循環(huán)
CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam參數(shù)有效
CDDS_ITEMPOSTERASE 列表項擦除結(jié)束
CDDS_ITEMPOSTPAINT 列表項繪制結(jié)束
CDDS_ITEMPREERASE 準備開始列表項擦除
CDDS_ITEMPREPAINT 準備開始列表項繪制
CDDS_SUBITEM 指定列表子項
表7 dwDrawStage的類型值與含義
hdc指定了繪制操作所使用的設(shè)備環(huán)境。
rc指定了將被繪制的矩形區(qū)域。
dwItemSpec 列表項的索引
uItemState 當前列表項的狀態(tài),其取值如表8所示:
類型值 含義
CDIS_CHECKED 標記狀態(tài)。
CDIS_DEFAULT 默認狀態(tài)。
CDIS_DISABLED 禁止狀態(tài)。
CDIS_FOCUS 焦點狀態(tài)。
CDIS_GRAYED 灰化狀態(tài)。
CDIS_SELECTED 選中狀態(tài)。
CDIS_HOTLIGHT 熱點狀態(tài)。
CDIS_INDETERMINATE 不定狀態(tài)。
CDIS_MARKED 標注狀態(tài)。
表8 uItemState的類型值與含義
lItemlParam 當前列表項的綁定數(shù)據(jù)
pResult 指向狀態(tài)值的指針,指定系統(tǒng)后續(xù)操作,依賴于dwDrawStage:
當dwDrawStage為CDDS_PREPAINT,pResult含義如表9所示:
類型值 含義
CDRF_DODEFAULT 默認操作,即系統(tǒng)在列表項繪制循環(huán)過程不再發(fā)送NM_CUSTOMDRAW。
CDRF_NOTIFYITEMDRAW 指定列表項繪制前后發(fā)送消息。
CDRF_NOTIFYPOSTERASE 列表項擦除結(jié)束時發(fā)送消息。
CDRF_NOTIFYPOSTPAINT 列表項繪制結(jié)束時發(fā)送消息。
表9 pResult的類型值與含義(一)
當dwDrawStage為CDDS_ITEMPREPAINT,pResult含義如表10所示:
類型值 含義
CDRF_NEWFONT 指定后續(xù)操作采用應用中指定的新字體。
CDRF_NOTIFYSUBITEMDRAW 列表子項繪制時發(fā)送消息。
CDRF_SKIPDEFAULT 系統(tǒng)不必再繪制該子項。
表10 pResult的類型值與含義(二)
以下是一個利用NM_CUSTOMDRAW消息繪制出的多色列表框的例子:
圖12 利用NM_CUSTOMDRAW消息美化界面
對應代碼如下:
注意到上例采取了3.1所推薦的第2種實現(xiàn)方法,派生了一個新類CCoolList。
3.4 使用MFC類的虛函數(shù)機制
修改Windows界面,除了從Windows消息機制下功夫,也可以從MFC類下功夫,這應該得益于類的虛函數(shù)機制。為了防止諸如“面向?qū)ο蠹夹g(shù)”等術(shù)語在此泛濫,以下僅舉一段代碼作為例子:
這是MFC中viewcore.cpp中的源代碼,很多讀者總不明白OnDraw()和OnPaint()之間的關(guān)系,從以上的代碼中很容易看出,CView的WM_PAINT消息響應函數(shù)OnPaint()會自動調(diào)用CView::OnDraw()。而作為開發(fā)者的用戶,可以通過簡單的OnDraw()的重載實現(xiàn)對WM_PAINT的處理。所以說,對MFC類的虛函數(shù)的重載是對消息機制的擴展。
以下列出了與界面美化相關(guān)的虛函數(shù),參數(shù)說明略去:
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元素自繪函數(shù)
很顯然,位圖菜單都是通過這個DrawItem畫出來的。限于篇幅,在此不再附以例程。
本文為白喬原創(chuàng),曾經(jīng)在《電腦愛好者》合訂本上發(fā)表。