" m) l1 d. ?$ @HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) & s( J) g/ o5 e, I{ # g# M& H! ? Z+ n$ { //设置透明背景模式& d9 B% t' Y1 p8 d0 W
pDC->SetBkMode(TRANSPARENT); 8 j+ J6 E0 t8 o7 c# t //设置背景刷子为空- b/ F5 g& D- l9 w# R! v
return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);7 x5 Q* W4 o" s& l( T
}; ~; s$ U3 E' d9 i& u# M0 D; w3 C
</TEXTAREA> ' {& S9 A" }% Q0 c+ p
<P>同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。 # w* U8 I z* t9 m4 e9 Z* o- j<P>3 s8 J. x, P: h8 u. t* ^1 m+ Q2 I
<P> X' N/ ~5 T* N6 U2 ^6 A<P><b>3.3.3 WM_CTLCOLOR </b> ; I3 q2 Z& ~5 f' z8 e% A0 S! H<P><b></b> ) E" t; K% c* w4 U7 S+ e
<P>在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。 ! Q$ k( a2 p/ k: R3 ^& q5 X3 t* H
<P>WM_CTLCOLOR的映射函数原型如下: ; Y' t) A/ O9 [' U# k
<P>afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );</P>( ~& z3 ^0 m, C+ L0 G8 h+ `3 ]3 G1 j
<P>返回值: 4 V2 Z- L( A' k<P>用以指定背景的刷子 3 D: W2 F u }8 E4 e; d<P>参数: 7 ^% `* A7 g0 c$ G/ W<P>pDC指定了绘制操作所使用的设备环境。 * m# b- f* I, n" z. A
<P>pWnd 控件指针 ! I6 Q8 u' X% \, P* s/ f1 e9 }<P>nCtlColor 指定控件类型,其取值如表2所示:</P>8 d8 B: B, w* ]9 [0 U( Z
<P>类型值 含义 - I4 A% t! r% J5 r<P>CTLCOLOR_BTN 按钮控件 - G% x0 @( C4 j<P>CTLCOLOR_DLG 对话框 3 l5 i& a# [( r1 o
<P>CTLCOLOR_EDIT 编辑控件 " ^ _- x2 F$ Y- F/ ~ |- C7 x<P>CTLCOLOR_LISTBOX 列表框 & q; T) L3 E3 O: R: P
<P>CTLCOLOR_MSGBOX 消息框 , g# }3 O, L3 f% m<P>CTLCOLOR_SCROLLBAR 滚动条 0 N& w, S$ F" t5 K, ^
<P>CTLCOLOR_STATIC 静态控件 ) _2 J5 y) L. M, B<P>表2 nCtlColor的类型值与含义</P>) E# J0 m% Q& P* ]: V
<P>作为一个简单的例子,观察以下的代码: * t( T% [; v/ h' `( s! D
<P><TEXTAREA readOnly>BOOL CUi5Dlg::OnInitDialog()4 _( B0 o) Z8 {# U6 M* c
{) _; b6 f. X* h9 y; x% U6 U
//… ' z8 G9 J! p6 G //创建字体 . ?5 e; A6 {; l6 G: Q( _# O" s //CFont CUi1View::m_Font1, CUi1View::m_Font2 9 M" ?* A( ^: }: s m_Font1.CreatePointFont(120, "Impact"); ; O! N6 S' R0 | m_Font3.CreatePointFont(120, "Arial"); ; ~; E. g' s4 R2 u+ @$ @. f0 m: M( O + j, R- ]7 J- |# u1 [ return TRUE; // return TRUE unless you set the focus to a control 0 ?% A, X% |2 i5 r7 J
}" I1 n9 V" x7 D2 C3 x2 h# g, P
. @$ k+ B9 }, B
HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) & `9 ^% j |- ~' v
{ + ]% _7 Y, c0 r* W/ \8 f4 [ HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);+ `5 T _$ ]& N4 [" u
if(nCtlColor == CTLCOLOR_STATIC)6 k; B- X* ~- e/ q1 v! L& p
{. L( v% ^. T9 a( v
//区分静态控件3 x2 g' I* R' a1 q/ U- v" x
switch(pWnd->GetDlgCtrlID())6 m0 M& [, l, u* J7 O, \3 w: H
{" ^5 y: ?! G# N' d, f
case IDC_STATIC1:% o* Z# ^+ @ @, S
{3 P0 _0 V/ r7 N
pDC->SelectObject(&m_Font1); 0 `, H0 w* y; ~. z- M0 j pDC->SetTextColor(RGB(0, 0, 255));. B, H8 P# ^4 [# n
break;# ~3 s" i3 K2 T( _5 U: f2 e0 L
}; u# [5 E6 M/ z& G) n5 x/ l7 t
case IDC_STATIC2:: M9 l: o+ o) x. h+ O9 @, Q
{8 C5 k3 Z/ m; `9 J8 o
pDC->SelectObject(&m_Font2);4 [' n! F& ?( a- Y& ~+ H
pDC->SetTextColor(RGB(255, 0, 0));5 I5 X. ]3 R) T( r: h
break; % P% i+ x5 H H6 f }* l" s2 P) L& C* }
} * F; d* }3 H: i5 l# f } 3 W% y3 q, k5 a ! X: s* ]$ S7 ^ return hbr;6 Y2 V5 H) {1 {, K! P6 D# s
} ' s! m2 \! { ]2 `& {</TEXTAREA> 6 o# U! v8 m. T) P7 f
<P>生成的界面如下: % W- n# A, D: R% d' P<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650321578.gif" border=0></P>9 y7 G$ g# w* H; j5 ^, n/ ]
<P align=center> 图7 利用WM_CTLCOLOR消息美化界面 </P>& r; g, X! W8 m
<P><b>3.3.4 WM_DRAWITEM </b>; g: u' c% b0 Z& m# @
<P><b></b> D- G' Y" y' r. E* Q<P>OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。 ) y: y3 s' y* ~8 N
<P>当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。 ) A, U/ C" p* e
<P>WM_DRAWITEM的映射函数原型如下: - |/ v* T% o4 C8 j, K<P>afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );</P>+ r1 M% C/ t: t" W' M3 f5 Z
<P>参数: 9 ?( Y9 ~; \3 h<P>nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 3 C5 ]- v I& e/ X<P>lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下: # ?* o8 ^. T6 ?" m
<P><TEXTAREA readOnly>typedef struct tagDRAWITEMSTRUCT . F2 _' ~" Z. g- P$ g, k{ 7 ~/ [$ S( [# c) M9 D8 `' z, p UINT CtlType; $ A/ D% n+ v1 r* d7 R UINT CtlID; - c: L Y9 i* e5 ~) i* K- `5 `
UINT itemID;1 z& j9 s( ~/ B0 E
UINT itemAction; Q4 x% V% u* W. `' P) f
UINT itemState; ) b2 i5 S4 J' p6 [( \! c HWND hwndItem; 5 A0 e9 ]. K: T HDC hDC;% i6 l/ C4 j( y2 ]# I8 P' M
RECT rcItem; : R" R1 d& \8 d! m2 I$ c DWORD itemData;$ {, ^) J( q+ y5 E" O& n3 Q) [- J; \& l
}DRAWITEMSTRUCT;/ l9 H% V$ [; j$ A4 d
</TEXTAREA> , X1 d4 N2 C; k
<P>CtlType指定了控件的类型,其取值如表3所示: ; q9 N: ?( f2 ^& i8 W$ @# }
<P>类型值 含义 : H3 [/ d! P, M6 \: l% }! f<P>ODT_BUTTON 按钮控件 , n, `& s, M; B; L9 m
<P>ODT_COMBOBOX 组合框控件 2 [, {7 w7 y, ^ t<P>ODT_LISTBOX 列表框控件 % L% N; @& _9 J% _# k$ u
<P>ODT_LISTVIEW 列表视图 6 u) X" Y- p7 ]
<P>ODT_MENU 菜单项 G; K/ L/ O# v% f& P. s3 [% g
<P>ODT_STATIC 静态文本控件 / Q) f0 C! J3 n+ X* Q
<P>ODT_TAB Tab控件 1 |. t1 j, x3 ?) o" C/ J
<P>表3 CtlType的类型值与含义</P>: H" i- f# \7 M1 r2 o
<P>CtlID 指定自绘控件的ID值,该成员不适用于菜单项 + m3 v j. ~1 c" M3 M
<P>itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。 4 Q4 m# C& X. O) E, ]/ u<P>itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:</P>2 w1 Z4 `/ {' ^1 r9 f( @
<P>类型值 含义 ( {( O1 _9 K" G' i! p<P>ODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。 " b4 T8 n, Y6 ^9 U
<P>ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。 / T, {5 J* m S) w; O: C' h<P>ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 - p7 V' l1 J) t; _7 `
<P>表4 itemAction的类型值与含义</P>2 B% v! M/ i4 C, C' X# m
<P>itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:</P> ! j1 b5 N; c3 p5 z" H" k<P>类型值 含义 $ V( F! }6 y% c3 [<P>ODS_CHECKED 标记状态,仅适用于菜单项。 * H# q* J8 Y" s% j6 i B# I# v& }
<P>ODS_DEFAULT 默认状态。 2 Y z9 C: A( ]; Y# d( r# o<P>ODS_DISABLED 禁止状态。 , X: \* s: G' P O j
<P>ODS_FOCUS 焦点状态。 1 E4 Y" @; z; e6 q# T4 ~; z
<P>ODS_GRAYED 灰化状态,仅适用于菜单项。 ) r( D! e7 S8 b6 g) u$ V% I4 B<P>ODS_SELECTED 选中状态。 F5 {& L% N1 B! d( ?0 {! R<P>ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。 # ]- j- K5 B& H# z9 | X
<P>ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 0 F+ h) x- Y5 `6 b* c/ a<P>ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。 / q$ k1 F* m" A- E. x) Z$ n! d, D
<P>ODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。 , z5 Z( B) S: D& v+ c2 [1 d
<P>ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。 & |% M# O( g/ Y) @+ {/ d! K
<P>表5 itemState的类型值与含义</P>$ J' D6 \8 N7 Y s( B9 d% k
<P>hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。 - W2 x$ f/ Q+ z( G
<P>hDC 指定了绘制操作所使用的设备环境。 ' W4 ^* q2 G* i( {# o) [: {$ j3 l<P>rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 ' v1 P3 o4 x( B$ W& D4 n2 G<P>itemData : o7 M& Z% x4 p1 j<P>对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 + t7 H/ I! i0 m" i# o( `<P>对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 $ @* G. G, Q0 e1 |0 j. g, r<P>如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。 ! _6 p8 p9 H$ r z
<P>图5是个相应的例子,它修改了按钮的界面: # C, N7 e! F# v<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650324712.gif" border=0></P> 8 m4 m- w1 {0 O! ]5 g9 d% {<P> # N: X2 y/ K* k+ f; L<P align=center>图8 利用WM_DRAWITEM消息美化界面</P> ( P" O/ }) h# e! |7 Y: B* W<P>实现代码如下: , T1 @* S- m R0 M
<P><TEXTAREA readOnly>BOOL CUi6Dlg::OnInitDialog() # V$ W6 Y, N& p& Q: f6 U4 x{, Y7 x* g6 {0 K$ y7 c$ b# K
//… - E0 o) T# l$ N: A: @ `4 p //创建字体0 g" L( p" y1 v2 h9 C! t, x6 Y
//CFont CUi1View::m_Font* N4 j T# r" w+ H1 R& Z* E! M4 e
m_Font.CreatePointFont(120, "Impact");* d/ {. N: R9 ?; P) g9 C0 T
//…4 k% E4 @# g6 e4 j* }! v
} + B. M( f. f# Z+ S- \ t d- ~; r' w; {: @
void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) . `2 w: u) j3 r2 g
{ % G; u$ ~' R& n( N5 d if(nIDCtl == IDC_HELLO_CFAN) 7 C; j9 L8 g- P9 C4 [- } _ { 5 Y; j: B+ S/ [/ }# V% Y# O //绘制按钮框架8 y* ]8 B% Y9 |! N
$ C# H% a j. l3 `' W UINT uStyle = DFCS_BUTTONPUSH;# y' h5 O1 w" Z: d3 r1 h( s7 ]) r
//是否按下去了? 4 \+ \4 X+ H- F- o if (lpDrawItemStruct->itemState & ODS_SELECTED)3 A: ?$ w; S7 M1 R
uStyle |= DFCS_PUSHED;& r4 [3 F: h q4 J# m; o
5 A' k1 S4 T7 t CDC dc;3 m* O2 @8 c( m2 {4 z8 F: K
dc.Attach(lpDrawItemStruct->hDC); # H) J" N- X2 w- v' ~ dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle); $ N- ~ z- Y' e- ^. H# Q# R: o; j8 E* {: K* X8 Y& a
//输出文字; _$ O/ e/ ~$ P. `- f3 q5 o; T, i+ j
dc.SelectObject(&m_Font); 1 z( c1 @* @8 g4 \1 x+ ~ dc.SetTextColor(RGB(0, 0, 255)); , j* _' F5 z8 H% W dc.SetBkMode(TRANSPARENT); " D6 O( k) H; |4 C& Y9 p( s9 _! ~( d. \. P/ D
CString sText; 6 G+ j: M$ j, _+ K6 K7 b m_HelloCFan.GetWindowText(sText);7 M* l) ^. q8 K: e" L Z8 r' j
dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText); 7 \% [' j c/ F2 `/ N) Y( \( k- _) I& C/ ^# H$ l# x1 r
//是否得到焦点 ' {8 g- K- m9 F+ V! p' B if(lpDrawItemStruct->itemState & ODS_FOCUS)' F) D) }$ b3 b
{; N; z9 C: k% y2 d. { {$ l+ C( j
//画虚框 9 o1 n/ b8 l2 H CRect rtFocus = lpDrawItemStruct->rcItem;, k X; z0 G& H: f# x% Z# I, ^7 T: N; `
rtFocus.DeflateRect(3, 3); 7 n" x1 }2 H) E5 W) X8 M1 y dc.DrawFocusRect(&rtFocus); 3 d* P1 m! f/ E- q } ; F4 [* ?; b# [+ ?1 J. k6 l4 j# I8 b7 a8 q
return; / X: s) K( B+ [! h2 M7 E5 _2 ^ }7 c& {0 M* J6 J( l
CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct); . | c9 Z+ H3 m} 1 E# v7 f' r7 Z, [2 u</TEXTAREA> ' f5 J$ l l) }$ ], j+ R<P>别忘了标记Owner draw属性: 4 k0 L* U$ W6 x7 s8 I<P align=center><IMG src="http://vcer.net/upload/2004/03/1046596492605.gif" border=0></P> $ l% E2 B) X: v& W<P align=center> 图9 指定按钮的Owner draw属性</P>6 x& \5 e) C( K
<P>值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton:rawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。 # \2 Q4 z( m! v, @+ z, Q9 p x<P>+ U3 s2 B9 v8 e# F9 F) n
<P>! c/ E7 t4 H- t- D
<P><b>3.3.5 WM_MEASUREITEM</b> ) R$ l) |. [1 } R5 ?* @
<P>/ n E% q8 A$ T: y* H0 E" Y
<P>4 K9 X8 b8 T7 @( @1 |7 m2 `
<P>仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。 3 }6 r9 |; m3 Z% u1 B<P>WM_DRAWITEM的映射函数原型如下: ) B; g% B0 f! [( j- B- K
<P>afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct ); 1 T$ l) ]0 V1 D1 u1 w
<P>nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 - D0 T. J6 r3 A( S6 T$ w# U$ X<P>lpMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下: % k S$ Z7 f K) ^1 G
<P><TEXTAREA readOnly>typedef struct tagMEASUREITEMSTRUCT* S; r! i4 {0 g4 e) X9 L5 ~4 C
{ ; q* D0 Q$ W/ q: N | UINT CtlType; ) q' O' j3 ]6 r. k5 B$ ^/ `: B UINT CtlID;* X9 ^1 d+ k- _1 `
UINT itemID; n' X/ ]- K( r: o+ J UINT itemWidth; 3 o: c3 q k- C) h2 b$ K UINT itemHeight; ( R R4 K; f( z& |1 q+ t% l. d/ D DWORD itemData8 i- s: i* c& }: ~3 ?3 o0 _6 y
} MEASUREITEMSTRUCT; / [8 R c* {4 q; C3 P- k! X6 F8 |</TEXTAREA> 4 p: \" w! S, G( B$ p' T6 f
<P>CtlType指定了控件的类型,其取值如表6所示: , Q' {0 J# X# |0 b: x6 A$ Z( @<P>类型值 含义 3 r% G- u) a2 x# f* `<P>ODT_COMBOBOX 组合框控件 . ]: K( i9 e! u$ q* Q3 U<P>ODT_LISTBOX 列表框控件 - B6 T5 a( A4 l0 F7 V, O
<P>ODT_MENU 菜单项 , x. g) S! F6 X1 a<P>表6 CtlType的类型值与含义</P>, e( b, O$ a3 u* l: t
<P>CtlID 指定自绘控件的ID值,该成员不适用于菜单项 7 }0 m9 ~' V+ A8 Q& x( a7 F
<P>itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。 8 F" {0 P9 ?% I& B# @3 I
<P>itemWidth 指定菜单项的宽度 1 p8 i y$ [, `# x, N5 s<P>itemHeight指定菜单项或者列表框中某项的的高度,最大值为255 5 E+ v$ J) ^1 [/ s<P>itemData ) I) Q; E; H# v<P>对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 8 A# P# B# G0 e% S5 z
<P>对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 9 O3 G5 [4 M/ G% J: P1 m
<P>图示出了OnMeasureItem的效果: ! p( n5 H, j8 R: z' `4 d<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650332513.gif" border=0></P> " ]! ?- \4 Y$ [7 ?$ ?2 h# `. L1 c<P align=center> 图10 利用WM_MEASUREITEM消息美化界面</P>" B8 Y/ r( j: U% Z# V
<P>相应的OnMeasureItem()实现如下: % h9 N3 l" H3 z. y9 w
<P><TEXTAREA readOnly>void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) 1 U: T0 }: S# o$ \
{ 6 P# r0 \8 c+ D, {2 T% @& b, Z if(nIDCtl == IDC_COLOR_PICKER) $ }% N/ @- h ~* ] { v, E" m/ E) r
//设定高度为30: {- F. v3 \$ l5 f
lpMeasureItemStruct->itemHeight = 30;% q2 l8 C; Y9 E0 G# e
return;! z0 [( J9 u1 W
}+ L7 E' M/ P/ R. i4 ?
CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);; F2 v8 w# e& u$ r1 Y
} , d3 R! ?4 k; a( a3 c! x) L</TEXTAREA> + p, g' g3 x2 A1 w3 E
<P>同样别忘了指定列表框的Owner draw属性: / m8 r. [5 d! j/ @ f6 l- t: f4 i- ~<P align=center><IMG src="http://vcer.net/upload/2004/03/1046596451727.gif" border=0></P> F6 ]' P: @3 {' j: [ L<P> / x1 A& J$ p( I K<P align=center>图11 指定下拉框的Owner draw属性 2 Z& i4 m. |- L( ^- A<P align=center> 4 R/ L6 E' w/ K. B+ }
<P><b>3.3.6 NM_CUSTOMDRAW</b> S4 u. J8 l5 v& G' o: g<P>( U F; h. ^" q
<P>' \9 \- v: L1 l5 T/ g
<P>大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。 " S/ n% Q- ]+ d( ^' ~$ }
<P>可以反射NM_CUSTOMDRAW消息,如: % U# `$ |/ I9 E0 j! H
<P>ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw) * E( ^+ c. d+ {, U9 G3 M6 a<P>afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); * T$ R9 R7 ^ s$ [* t6 z
<P>参数: 1 |3 M. J. [/ y" r/ M. i! g4 R
<P>pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下: : {4 f: C8 x1 y+ b; X
<P><TEXTAREA readOnly>typedef struct tagNMHDR - B# o6 T' ^( V2 m0 x{ , `8 u4 h" z1 T' _1 z0 @ HWND hwndFrom; 9 E% |; E' I3 |% o" q+ a4 E UINT idFrom; 2 k/ n( i9 Y" G& c/ p
UINT code; 0 \, F1 v8 [; Y# g. Z
} NMHDR;2 p# U/ c) B) Y7 j" u7 ~8 c3 z7 f
</TEXTAREA> : P2 s- h- F: |# C<P>其中: 5 s6 X6 Q+ I7 ~! x! d Z
<P>hwndFrom 发送方控件的窗口句柄 " K2 k" U9 D6 |. V/ I<P>idFrom 发送方控件的ID 8 @) M/ N3 S& h' y1 O C- ?<P>code 通知代码 ; Z' l! d8 [; |6 f" g, Q
<P>对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下: $ M: p# C/ X# }0 f) U
<P><TEXTAREA readOnly>typedef struct tagNMCUSTOMDRAWINFO 3 i" x; A/ T) s3 c5 n* x; A{0 S' n# p- I3 T; m/ p5 Q9 S
NMHDR hdr; $ X( O: |; m, ?# L DWORD dwDrawStage;9 V& n5 s- R. R, y! H
HDC hdc; * P. t; G1 A! `2 o4 L* S' o5 A RECT rc;: ^4 i& P. V1 z3 l
DWORD dwItemSpec;+ o2 \% F. Z8 g" b% n1 k
UINT uItemState;9 b- S. j( [. t9 T' X" P- \
LPARAM lItemlParam;5 S9 @+ t$ y2 N( ]$ m! E9 R% ?
} NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW; ) r5 A! D; N4 E9 o7 j/ J</TEXTAREA> 2 m' W& b" U# a" \3 w, I9 B( T
<P>hdr NMHDR对象 5 f3 O( g; a' ^5 s3 u+ G' A7 Q<P>dwDrawStage 当前绘制状态,其取值如表7所示:</P>2 f( c& {9 e' D, ~& @. D3 e# ?
<P>类型值 含义 : s$ M9 @8 U+ Y) k9 t! V u<P>CDDS_POSTERASE 擦除循环结束 1 @6 n7 T: O8 R) b- S ]<P>CDDS_POSTPAINT 绘制循环结束 - t% D9 H3 ?% I2 x5 h' t4 H( e
<P>CDDS_PREERASE 准备开始擦除循环 + N2 _5 u- g, r% O
<P>CDDS_PREPAINT 准备开始绘制循环 3 \5 u- l; n& p: j<P>CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 1 c. Z" }; p$ e9 h3 R
<P>CDDS_ITEMPOSTERASE 列表项擦除结束 ' K! G, T: C: n
<P>CDDS_ITEMPOSTPAINT 列表项绘制结束 $ d* ]9 A: p$ T1 ~! f7 C
<P>CDDS_ITEMPREERASE 准备开始列表项擦除 ! _5 v. l; D) M% o' H' Z. k5 ~
<P>CDDS_ITEMPREPAINT 准备开始列表项绘制 ; t+ D$ ]# s V2 P7 l5 f" }<P>CDDS_SUBITEM 指定列表子项</P>6 `8 k% k* a$ e! T
<P>表7 dwDrawStage的类型值与含义</P> % `6 |# z+ e: N' U3 p<P>hdc指定了绘制操作所使用的设备环境。 8 A5 [. f* x$ J# Y' U" i
<P>rc指定了将被绘制的矩形区域。 2 H; B. Z0 P- o J6 @/ D% i& f
<P>dwItemSpec 列表项的索引 9 g/ N" v4 R4 |
<P>uItemState 当前列表项的状态,其取值如表8所示:</P> : u: k* G+ {; t: h, m7 {<P>类型值 含义 ' j# F- |3 k5 d( ?+ N* p. T<P>CDIS_CHECKED 标记状态。 ) M* R. H0 g/ g- X<P>CDIS_DEFAULT 默认状态。 6 F# d; w. `+ g: |/ Q6 o1 b1 [% K: M<P>CDIS_DISABLED 禁止状态。 " ]: Q! ^' Z' `& R
<P>CDIS_FOCUS 焦点状态。 . _ _$ k( h: J* l) a0 J
<P>CDIS_GRAYED 灰化状态。 8 G7 i1 ~: {9 } ~. `" T, G( L
<P>CDIS_SELECTED 选中状态。 7 X; U1 y$ Y* ~. D3 ~<P>CDIS_HOTLIGHT 热点状态。 6 u. q$ g. O# W; s) W
<P>CDIS_INDETERMINATE 不定状态。 $ y' t% A& Z! v3 J) Q<P>CDIS_MARKED 标注状态。</P> , e2 f$ b; n" s; _2 d<P>表8 uItemState的类型值与含义</P># Z" c0 y3 z8 r* U F4 l A+ N
<P>lItemlParam 当前列表项的绑定数据 " f$ |. Q* Z, @* ?! t, [# x( U<P>pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: % q& C7 ~4 y& U/ p- l0 I
<P>当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:</P>. E& H3 X, L# N/ s9 }, T
<P>类型值 含义 % `5 |8 |* T' A) a" D+ w<P>CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。 . A9 h' c: |/ @" M+ }: \
<P>CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。 9 d- b* o: {+ B$ b- R: B4 i$ t) k/ v<P>CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。 ( F/ i* }( e( d! v+ p0 c2 T<P>CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。</P> ) _; t( M0 h, S! X# S. l<P>表9 pResult的类型值与含义(一) % ]; Q- i* }3 n<P>当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:</P> \- f8 d+ v- N4 R8 }8 ]1 @<P>类型值 含义 2 y8 ` x/ H& M* I<P>CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。 ( W* R- y) C% M2 K5 i. c<P>CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。 & @% ~4 ^) m) _3 ^8 d' P5 k2 h<P>CDRF_SKIPDEFAULT 系统不必再绘制该子项。</P> ; q' F: B1 V( b- c) R<P>表10 pResult的类型值与含义(二)</P> ( p2 x) J# J8 `<P>以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子: % {( ?0 A" _6 x9 V) B" h( j
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650317752.gif" border=0></P># O( w4 U- c1 u+ r* _8 s( s' e S/ e
<P>9 e# p' o+ u. E8 t' u$ i/ b
<P align=center>图12 利用NM_CUSTOMDRAW消息美化界面 8 b# x: ~) T+ X/ G# ^<P>对应代码如下: " k3 {: w) E% t<P><TEXTAREA readOnly>void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)0 h, }5 ~/ o( I+ _/ ^/ o
{- E! q, B+ h# K
//类型安全转换1 e; }- C% r+ |9 N( E: h- X
NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR); 7 A, U8 ~/ J& j7 } *pResult = 0; 2 e& J1 K: |! h 7 D. T5 J: @( n) E9 ] //指定列表项绘制前后发送消息$ T' K9 i5 N0 }; N2 Z+ L
if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage) 1 S3 \/ O& |* h { % Y1 S$ O- p" g) b% w8 B" t5 J *pResult = CDRF_NOTIFYITEMDRAW; 9 G. A4 n7 C/ ~2 ?' K( Z+ J+ |3 U+ | }" H: U( y7 Z5 Y- V' P% D" |9 a
else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage) 6 X$ [) J& u8 t6 X8 q {0 ~1 b; `& @2 I4 a
//奇数行 0 T5 A! O9 ^3 l" J if(pLVCD->nmcd.dwItemSpec % 2) 0 R0 Y2 F* s4 G+ K3 X) C pLVCD->clrTextBk = RGB(255, 255, 128); 0 _. S! J1 c. O0 I( A# w //偶数行 7 A! Q! R: i2 s. G P else# R% J: _& U$ U# N* n2 R
pLVCD->clrTextBk = RGB(128, 255, 255); 4 V. z/ I4 r- a3 | D7 E //继续2 C& m4 [$ [9 h4 J
*pResult = CDRF_DODEFAULT; / j0 o" J) Z! ]/ w* \- A3 `# D ` } . u! \& L) F( Y. E. N+ _0 P}( L f' p2 U' F5 R6 n0 N
</TEXTAREA> 3 ?+ A Y% c- s
<P>注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。 ) [, N2 a( A# }, T5 L
<P> 8 v2 w# L1 G. \8 s8 m1 |<P>4 d- _6 G9 r6 j
<P><b>3.4 使用MFC类的虚函数机制</b> - e( O0 R. v' F/ u
<P> . i" o4 K; D- t" y+ }; ?5 C: b* _0 _<P>& b q+ l, F2 i D# S2 f% {: N
<P>修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子: ! h; F1 q e+ S$ D) A# I! G<P><TEXTAREA readOnly>void CView::OnPaint()/ P' ]6 _6 r" \# S; {' D( F! \! d# }0 f
{8 v% W# T) k& ]* Z; V
// standard paint routine# M2 f# C# j' _: G; X. I
CPaintDC dc(this); % C* Y% H3 L% q# G" I/ i d+ @ OnPrepareDC(&dc); ; L" g( p( g# U" y( h! n OnDraw(&dc); % K# z/ m* A+ k}: `2 I% Y, ]# G% T
</TEXTAREA> ; l* t5 t: e! e4 p
<P>这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。 M6 ~6 \* X( M8 C. N<P>以下列出了与界面美化相关的虚函数,参数说明略去: $ W$ Q. k8 s2 K6 s<P>CButton:rawItem ( m: ~* [$ k4 Q. W$ p4 u1 K<P>CCheckListBox:rawItem ) ^. q( s" v. u( }; E
<P>CComboBox:rawItem . t$ j% K2 X* |/ h2 M8 W+ N6 B$ N
<P>CHeaderCtrl:rawItem ; y: ~$ `) i9 N<P>CListBox:rawItem 2 G4 W; U5 P# _8 t9 R2 o+ `9 I' X o
<P>CMenu:rawItem , q2 C2 U5 K9 r1 [) h<P>CStatusBar:rawItem 1 z' {* m# y J; a8 q2 X+ f1 E2 b
<P>CStatusBarCtrl:rawItem , E/ ?; U8 s6 E* @9 J! n<P>CTabCtrl:rawItem</P> 8 a1 E( x. z, E' _<P>virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct ); 1 Z: b7 `0 p0 x: R- Q$ Z<P>Owner draw元素自绘函数 - X% {" _8 ~$ E$ \& [: I
<P>很显然,位图菜单都是通过这个DrawItem画出来的。限于篇幅,在此不再附以例程。 </P></DIV>