2 ^) o; l/ W2 I) Gvoid CUi1View::OnDraw(CDC* pDC)2 }5 |- p& G# b3 E: z+ H
{ 3 h: H z& N1 W8 Z* j" k //绘制按钮框架 " w" i* l- E! \; m5 i pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH); $ Q) v9 c% ]/ i8 c' u+ s! A9 z: E1 C H4 x/ h
//输出文字' V8 e- {' I M" U, Q
pDC->SetBkMode(TRANSPARENT);" F9 h6 ~2 M- C: a( r& ~
pDC->TextOut(120, 120, "Hello, CFan!"); 3 {% V- e4 S! x}</TEXTAREA></P> 9 B5 c* n/ n( S) i9 I8 f<P>呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。 </P> ' L- i& F4 S5 z, Y; H<P><b>2.2 Windows的幕后绘图操作</b> </P> 1 g, g9 z9 C, T! @% T \<P>在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。 ' t9 N$ A/ R) i! [& k% f
<P>有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。 - P! V! h2 N% [" H# j- _ _8 H6 I0 o5 Y
<P>所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框…… % G9 p! u& f2 |1 t4 m- D
<P>有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。 & Z3 `6 Y! j4 p9 ]4 U* S
<p>0 ^: Q8 Y ~" p* ]( P1 G$ ]
<P>3. 美化界面之实现篇</P>/ [ w% b. q& R$ `5 j0 e# P
<P>Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。 & r H, M" m: q" }<P> 7 T) g& }' Q7 j. B7 q/ Z5 c. [<P>( d( c. x: d! e9 i6 m0 f
<P><b>3.1 美化界面的途径</b> 5 ]# n$ r$ J8 f: b4 J
<P> 1 i8 N, w& ^- r2 c+ E8 X/ R<P>& q8 W* f! M V
<P>如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括: + J! c- X- C+ E+ s0 m- k
<P>1. 使用MFC类的既有函数,设定界面属性; - `1 }, R4 }( i# I9 k- r
<P>2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色; : [: D0 U6 y3 X# z/ v<P>3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为; ! `1 W) y5 E' f3 c9 v) j<P>一般来说,应用程序可以通过以下两种途径来实现以上的方法: & a$ u! S0 Z" w<P>1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息; 4 r- c# e' W3 w
<P>2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。 : p8 d$ T4 k# B/ K+ S* ]
<P>对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种: # x8 W& V2 r/ g q" z/ s; T<P>① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示: " g: w/ G: I$ e
<P>" | |% e5 n7 x8 n' ?( E6 f
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046596487288.gif" border=0></P> 6 e; F' J: Z/ g0 K8 t9 H2 e8 l<P align=center> 图3 为按钮指定CXPButton类型</P> - l; b* p5 U2 Q L6 y<P>②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法; 1 k- |. d) I n' _( ^1 o<P>以下的章节将综合地使用以上的方法,请读者朋友留心观察。 + Z( F$ x$ N2 {6 x; l8 \/ ]9 O
<P> _* B. x8 V; D7 y8 Z) r4 F
<P><b></b> ) l& R0 E( @# n- M+ r<P><b>3.2 使用MFC类的既有函数</b> ! e0 s) U: a: e: c5 o<P> y! \, z& d% V2 e9 ^& Z
<P>2 c" z5 a$ S/ O0 b7 I
<P>在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。 8 L9 K% ]0 }( Q% m, b7 t# ?8 f% T<P>CWinApp::SetDialogBkColor 7 P2 E8 u1 u+ e: f _1 ^/ T J2 T; z8 ^1 i$ x
<P>void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) ); ' }0 ~% R4 j# U2 R% p8 `& d<P>指定对话框的背景色和文本颜色。</P>5 j: W; {' f. k! h/ b1 _$ u! E
<P>CListCtrl::SetBkColor 7 b" `* ]0 Q3 [8 ^
<P>CReBarCtrl::SetBkColor 0 a6 D6 \! x# S7 e4 d<P>CStatusBarCtrl::SetBkColor ) V, {9 t8 I2 D/ `! G+ i
<P>CTreeCtrl::SetBkColor 7 a" P+ S y5 E+ X% q<P>COLORREF SetBkColor( COLORREF clr ); , l8 T: S$ h" g: h
<P>设定背景色。</P> 9 F) h4 s/ h! m' Z<P>CListCtrl::SetTextColor 5 @2 x9 \( }- h
<P>CReBarCtrl::SetTextColor + `5 M5 m1 i. Q- K, g6 e
<P>CTreeCtrl::SetTextColor # ^+ b6 T) a `: e
<P>COLORREF SetTextColor( COLORREF clr ); 2 w/ d$ t6 s* G G1 {4 L
<P>设定文本颜色。</P>5 c5 J9 p' B8 d6 Z" y5 K
<P>CListCtrl::SetBkImage $ ]* s* o; }3 T u
<P>BOOL SetBkImage( LVBKIMAGE* plvbkImage ); / }& d+ y" q8 p R, k<P>BOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0); % _1 |$ b2 y; U m5 _/ ^<P>BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 ); ; a. U \, W! K8 c3 A1 N7 t<P>设定列表控件的背景图片。</P>1 X1 V a4 U! v7 ?
<P>CComboBoxEx::SetExtendedStyle 8 B \9 X& A* b1 V
<P>CListCtrl::SetExtendedStyle ) b& c5 _5 l# u* H8 V
<P>CTabCtrl::SetExtendedStyle ! a' ?5 J# e r" N
<P>CToolBarCtrl::SetExtendedStyle 5 u* F3 [) Z1 U
<P>DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles ); 6 J' C7 D* g0 X8 C+ @
<P>设置控件的扩展属性,例如:设置列表控件属性带有表格线。 4 J8 D$ d( t8 R<P>图4是个简单应用MFC类的既有函数来改善Windows界面的例子: , s# P) z; d. _ T, @<P>4 ~3 o. E/ P' ?$ K" ~5 r
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650314708.gif" border=0></P> ( u: S5 O# {4 r2 e, A& d<P> ?* F2 l0 o: {2 ^3 m3 H: ?9 N0 l<P align=center>图4 使用MFC类的既有函数美化界面</P>2 m) g. N& l! Y9 A
<P>相关实现代码如下: " G9 V1 H" M9 Z( u: K
<P><TEXTAREA readOnly>BOOL CUi2App::InitInstance()# D3 z+ B( V8 a, r6 N8 u* z2 E/ {* H
{/ N4 k& \ G. V* E& _ G2 y
//… " Q! h3 D" r. M3 _0 W1 l //设置对话框背景色和字体颜色 * X" p: I: G+ s SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255)); % b3 V1 X2 h6 v6 e O2 K' s/ D //…# y9 D2 ]4 A. ]8 Q% z' K
} # i5 O! J4 [( L" \! P* q1 q5 s9 g / {/ I" B m2 `8 a7 U/ C7 _BOOL CUi2Dlg::OnInitDialog()# N% B& Y, `3 Y8 L
{5 ^2 p! h `0 P E" Q5 u5 y* O
//…2 {6 N. h# `/ \& t9 A" ]7 ^
//设置列表控件属性带有表格线7 R0 \' B8 g3 t8 J
DWORD NewStyle = m_List.GetExtendedStyle();- ]) i9 p: @" x1 s% n7 ]
NewStyle |= LVS_EX_GRIDLINES; : U% y# v! X/ V: d; im_List.SetExtendedStyle(NewStyle); 4 t2 J3 I5 k$ X! m) M" b# q* @6 e6 n: f! t+ f, d* I) l
//设置列表控件字体颜色为红色 1 N4 u/ S, i, s1 f. i O m_List.SetTextColor(RGB(255, 0, 0)); D( Q C3 h" S" i/ T h 4 T( ]( d8 v! \' V9 Z //填充数据 $ H* I) ]" ]. \& M$ b. j! A m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);, @6 t% \" n0 g! o
m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);7 Q+ ^" z: a5 D. v% }8 V
) [7 i8 V# [/ y; i& [6 L
m_List.InsertItem(0, "5854165");5 g7 R( U# q) t5 M2 b) W
m_List.SetItemText(0, 1, "白乔");/ O$ D: f5 r( Y) r; |
" S& n% Y8 N% d$ Z/ q1 g
m_List.InsertItem(1, "6823864"); " h9 a( ?& u: n6 u# l m_List.SetItemText(1, 1, "Satan"); 6 X6 ]/ |2 C9 T+ m6 V' r5 D4 [- _ //… & T* l3 H/ v: T& T7 J}</TEXTAREA></P>2 b a& {/ b; E* C" q, s F. \( v5 _
<P>嗯,这样的界面还算不错吧? </P>" H% ~) h/ F5 j
<P><b>3.3 使用Windows的消息机制 </b>, B5 ]0 ^, V* z) O. L
<P><b></b> 7 F3 K. B: e$ m% ]8 a. k
<P>使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息: ! W0 [7 `/ H l/ m* q0 U/ m<P>WM_PAINT ! j0 A9 `! _7 ? o<P>WM_ERASEBKGND ( S1 S: z0 S0 d* x3 O
<P>WM_CTLCOLOR* 8 ~- o! q N. Q! O<P>WM_DRAWITEM* ( [. e+ s# A6 A% w; M
<P>WM_MEASUREITEM* " `8 Y, G4 U; ?; X2 W
<P>NM_CUSTOMDRAW* 1 N0 T7 B9 p4 u3 A0 ] C$ b<P>注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。 2 X" f3 D) |. P7 n
<P> 3 @! K& }$ n3 |<P> 8 P+ q! |7 D& i& [3 {- b. Q+ s8 O6 U<P><b>3.3.1 WM_PAINT </b>8 L& ~" @6 z# h4 \
<P><b></b> " N! k6 S0 b* T4 Z8 e<P>WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。 : T& f7 l2 }0 s; A7 y d/ P1 M: O6 H8 |
<P>可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下: 9 R$ q3 v8 m! B6 r( n& u<P>afx_msg void OnPaint(); . r* Q- j5 t3 Z. h8 \9 d4 k<P>控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: ! Y" i) |% C/ l* W0 \<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650335708.gif" border=0></P> 4 N& L" m! u1 }" y8 P- Y$ g<P align=center>图5 利用WM_ PAINT消息美化界面 4 d* v1 T/ `( K" v: Q3 D6 Y
<P>实现代码也很简单: 7 h/ n* h% J* m7 `8 c8 {
<P><TEXTAREA readOnly>void CLazyStatic::OnPaint() 1 _ k( @$ {) C0 Y{; S. |9 ?) A' a" s `
CPaintDC dc(this); // device context for painting " o* O. F" b4 P. O0 R: D $ F4 F- X, f/ ?* b+ X: q
//什么都不输出,仅仅画一个矩形框 % P4 ~, {4 D# \6 |: z; h: w2 O CRect rc; * p. a: j7 a- P! B* V3 ` GetClientRect(&rc);/ s" P a" Z8 k0 C
dc.Rectangle(rc); ( u$ X2 w3 T5 B+ |7 Q5 {, t} & d% c: [5 S( H% [6 z2 Z5 y</TEXTAREA> & Z2 H% v2 Z1 h9 `. M. \7 d7 \
<P>哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。 6 e2 L/ K4 X3 [2 r; ?
<P>- s! N7 ?) m, J
<P>' W; V$ {9 M7 k) E4 U2 _' W
<P><b>3.3.2 WM_ERASEBKGND </b> * M) P3 B# U6 A' o) |* M7 t- j<P><b></b> ' y2 N( E3 C) _5 {& |
<P>Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。 0 j/ V) B7 a2 F5 I; A: ^/ B- o& e2 [<P>可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下: 3 Z( s, @; d, [- X6 @
<P>afx_msg BOOL OnEraseBkgnd( CDC* pDC ); 8 S6 H2 b) T9 C/ ^<P>返回值: + s# t O9 E- _/ N, W4 x' C& ^: h+ e
<P>指定背景是否已清除,如果为FALSE,系统将自动清除 ) c! Z9 ?3 ]1 H<P>参数: 2 Y4 X* O4 v- L/ V1 S% x& Y v; t% d# L0 O<P>pDC指定了绘制操作所使用的设备环境。 2 e5 s6 ?' Z, J9 T: r<P>图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景: 5 L; K% q) Z. P* o0 P* o' {, d
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650328908.gif" border=0></P> ( O! `4 |/ b+ n<P> 5 f& V# Z# `& U<P align=center>图6 利用WM_ ERASEBKGND消息美化界面</P>! U6 m8 c4 ]( Y
<P>实现代码也很简单: 4 E+ h: Z% ^3 q3 V<P><TEXTAREA readOnly>BOOL CUi4Dlg::OnInitDialog() & J* u( I5 t# N3 }6 l# P, q& i3 m2 U{ 8 T2 c3 e7 E* O% x9 e' c//…% |! L3 Q0 A1 ~7 A4 [$ l5 Z7 U; `2 F
//加载位图 + X# {( l5 D# H ?3 H+ F8 I //CBitmap m_Back;! |/ e0 w% i$ ^4 R6 f$ J7 q- ?
m_Back.LoadBitmap(IDB_BACK); ; e2 O s# b, L4 d _0 J //… 2 s$ l9 C, L+ [! |} 6 e1 o& E( F( D; i4 ]) x$ g) G9 d* N% o6 i9 k
BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC) + z" }6 `3 Z( a$ s# p% Z. Z v+ Z
{! C& X) F& J2 G. ]+ Q' a4 K
CDC dc; 0 V8 \' B# L1 t. \2 J1 i* ~ dc.CreateCompatibleDC(pDC);0 j4 b: b3 b) K& U& G
dc.SelectObject(&m_Back);2 @( I: ]$ X9 J: i
+ D5 w2 Z# w$ W( @ S+ d \! E- H. Q //获取BITMAP对象2 p* Q3 `; q& _ I# Z# P3 P
BITMAP hb; 3 t {5 [+ E4 v5 X8 z m_Back.GetBitmap(&hb);8 r, }, N$ h' X5 R
! S/ c9 G0 d+ W9 U+ Y //获取窗口大小 1 [! m; p! P+ [2 T8 _8 b7 `0 ~7 I7 a CRect rt; ' w1 c _# _, k6 S& l1 K- ] GetClientRect(&rt);2 e4 R u2 H- B+ V
//显示位图 . O3 b% m' N% A- R3 `+ o/ ~ pDC->StretchBlt(0, 0, rt.Width(), rt.Height(), $ p5 a' d3 ^- U( q: \( n &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY); # a( T- h+ f% m5 t* J: ^9 o 4 J3 S; L0 `; O1 v% b& x return TRUE; * p. z( ]; x$ ^9 W0 u} ! o% W$ }( f t + a2 N/ e% z) x+ f; AHBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) " \% {, V: N( w$ {
{ 7 W$ U( s, r7 i //设置透明背景模式4 O, Y$ d0 z% B2 u( x3 A8 s+ t' G6 j
pDC->SetBkMode(TRANSPARENT);) b# K# e7 g2 L/ ]# ~
//设置背景刷子为空2 z- _' k9 |3 \8 ?
return (HBRUSH)::GetStockObject(HOLLOW_BRUSH); 8 X' r3 P4 f8 L" u} v, Q+ K A" O" j- K$ C1 E. c</TEXTAREA> + P$ M; {7 s0 q5 G# i: _$ v<P>同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。 7 }- J, Q+ Y1 ~# q5 S2 h) p( j<P>7 ]9 j: Y( i7 e7 U
<P>- A$ S7 F& }, w$ Q R* e
<P><b>3.3.3 WM_CTLCOLOR </b>3 Q" e% i: A0 L& {
<P><b></b> + U/ a9 k* A! s
<P>在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。 / |# f( w4 m. C& z1 G# k<P>WM_CTLCOLOR的映射函数原型如下: 8 o, [3 Q4 T+ u4 c7 |5 t" E
<P>afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );</P> + I2 g1 M5 o" y1 Q3 k<P>返回值: 5 S M0 \. R# k! c
<P>用以指定背景的刷子 5 \5 a2 N* B6 C) Z+ U<P>参数: $ Z+ o5 H" ~: l$ j- r; ^% q
<P>pDC指定了绘制操作所使用的设备环境。 ! a: U3 Q$ D+ z2 n7 k. \<P>pWnd 控件指针 + a! Y3 q; A4 F+ O
<P>nCtlColor 指定控件类型,其取值如表2所示:</P> * b% [* @) _$ B<P>类型值 含义 ; M1 o q, ^7 m<P>CTLCOLOR_BTN 按钮控件 : I2 m4 F7 `6 {$ ~6 C<P>CTLCOLOR_DLG 对话框 % ^( J, K9 Q, [: u8 W( W3 ?
<P>CTLCOLOR_EDIT 编辑控件 4 Z* N( T% e j5 q) U4 [
<P>CTLCOLOR_LISTBOX 列表框 # y1 O) {) W) y1 l6 l7 _
<P>CTLCOLOR_MSGBOX 消息框 - d/ a! y9 [% C0 Q6 X
<P>CTLCOLOR_SCROLLBAR 滚动条 # C' k* c- T9 B6 n4 Q' j* C: A! {<P>CTLCOLOR_STATIC 静态控件 9 b# ^% g5 V9 G
<P>表2 nCtlColor的类型值与含义</P>' ~$ D* t( Z' P7 {
<P>作为一个简单的例子,观察以下的代码: 3 l/ R' j4 b6 c0 z- D- F$ D9 s
<P><TEXTAREA readOnly>BOOL CUi5Dlg::OnInitDialog()+ b4 [- i9 {) s. k
{ - w9 {' x% ?) c, `+ b //…& m- L. {5 \6 h0 O# J
//创建字体5 m* R% J/ |5 @+ a! X0 @
//CFont CUi1View::m_Font1, CUi1View::m_Font2 . D; T* c8 z a0 ?& b m_Font1.CreatePointFont(120, "Impact"); N Y4 [% I! X$ ^! l; B. d
m_Font3.CreatePointFont(120, "Arial"); Y p+ v3 v' Y ~7 w( o 6 X; G2 s$ a% x) s" R3 Z return TRUE; // return TRUE unless you set the focus to a control & c4 d# g+ @ ^} 8 J$ c$ U: R+ O0 ~. N5 p. a1 l: p9 k2 h. ^4 g7 s, e
HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) # N3 P9 a. w% G2 o& o7 }+ g$ ^5 S
{' A( T7 @$ ~( A/ ~' D9 _7 ^9 n% g9 d
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); ( [+ z8 A5 j, K0 b+ E% P8 ^( z if(nCtlColor == CTLCOLOR_STATIC) . C, ]$ G8 w" u9 {" G { , W2 S V/ y6 o+ g! b //区分静态控件2 \5 I1 k B; g; W# ~6 U7 F+ W
switch(pWnd->GetDlgCtrlID()) 0 G: u: G4 a/ ?# c! [/ N {3 i( u! g7 H. [2 Y* C4 _5 B2 a
case IDC_STATIC1:! n+ P, G5 o8 Y
{+ }5 V7 j/ o; }$ ^) X3 e3 ^
pDC->SelectObject(&m_Font1); 7 H! V8 q$ P7 \, ]# d/ Q) b pDC->SetTextColor(RGB(0, 0, 255)); " q3 @' G/ n- Z6 ?7 D# u! v) L, v break;4 k* _) B m$ F \
}1 x3 e3 f0 n' s1 U M+ j
case IDC_STATIC2:/ c( x; d/ i9 e) m6 ^, `
{4 z) o3 \8 I' c9 ~# o0 u2 F0 Z
pDC->SelectObject(&m_Font2);! \! u* g/ [3 q0 p7 v2 s4 S+ ^; J
pDC->SetTextColor(RGB(255, 0, 0));2 D4 o! e- X9 F7 S2 D1 y H
break;! {% g" ~' z" V I
}' d1 i0 G: K# z5 k5 P
} " e& M- B: e) A5 [ } 0 P& D, E8 J) K$ I0 s: S ; C) p* d+ B8 ? return hbr; 9 v) _2 D; b9 g- A. F$ g# H}; K+ l2 v! {: Y4 F& i$ T% w. p( w1 x% \
</TEXTAREA> $ ~1 E% f' \! g) P2 u
<P>生成的界面如下: % j% j* i. p) k0 ]4 g# A7 r2 q<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650321578.gif" border=0></P> ; c" E# t/ h# y0 Z/ D% A<P align=center> 图7 利用WM_CTLCOLOR消息美化界面 </P> - ]# r1 j5 [, m1 O) u3 A) `8 d<P><b>3.3.4 WM_DRAWITEM </b>. C) c/ \% ^7 h
<P><b></b> % m& e9 G- `0 ^5 y* ]
<P>OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。 ) w( Y) q5 R' ]6 [/ O; Q
<P>当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。 : l* v8 G2 |1 U+ l9 F/ d1 W4 M<P>WM_DRAWITEM的映射函数原型如下: ) t% e( j7 D7 T- }2 N<P>afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );</P>* t4 i3 O+ A b& D: K3 o3 M
<P>参数: 4 G3 A2 V1 Z# b a7 b( p; L4 V<P>nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 4 F: e6 w2 C9 L6 f0 f
<P>lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下: ! H" \( l" k2 A" X+ {2 i! z) ~<P><TEXTAREA readOnly>typedef struct tagDRAWITEMSTRUCT% N0 i( b' Q3 B: p
{" T' X7 V; N6 {( O! Z, _9 ~+ ~
UINT CtlType; 2 p+ x1 H* \) s6 x8 \/ c1 T, m% C UINT CtlID; 6 f* V+ J- a$ H$ J. k UINT itemID;0 X2 y+ \8 g& ~' c" ?( L
UINT itemAction; % v3 m8 ]& v1 J+ g UINT itemState;+ c/ ^7 I* ^9 U4 c: C/ Y! f0 I
HWND hwndItem; + c9 t L" _$ W) C- ] HDC hDC;" J, c0 c! _, T5 m+ s6 k( J
RECT rcItem;& _/ q2 q% _9 `2 W# M3 H
DWORD itemData; ! F- Z O# ~1 {$ D3 L7 t# f}DRAWITEMSTRUCT; e/ s. Z \+ K' ]. a7 A- i1 ^</TEXTAREA> 4 D5 d- s6 Z b7 N8 G<P>CtlType指定了控件的类型,其取值如表3所示: $ P C, i, v; m1 l: Q
<P>类型值 含义 - o# [8 b4 f% x V2 y* G$ k, Q C; K<P>ODT_BUTTON 按钮控件 * F) t& l' w5 c! v& _/ V7 E( |<P>ODT_COMBOBOX 组合框控件 8 g& w6 x/ x4 s1 v9 t6 [! @( F
<P>ODT_LISTBOX 列表框控件 4 p2 V0 y& G+ p5 o- H
<P>ODT_LISTVIEW 列表视图 % Y6 G; {0 Q0 i! |" D* I<P>ODT_MENU 菜单项 \1 l7 B9 @9 z' E" m<P>ODT_STATIC 静态文本控件 5 G2 R6 ~7 t% d5 p( d' ^* F" l
<P>ODT_TAB Tab控件 ! n4 L: L( A' B; {1 P' i; c
<P>表3 CtlType的类型值与含义</P> $ m% H% e* B N' L<P>CtlID 指定自绘控件的ID值,该成员不适用于菜单项 6 x% d$ _& j# n) t! M
<P>itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。 ; s8 `, }% d! m# I
<P>itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:</P> 0 @1 ?. H$ n* e c( u<P>类型值 含义 , v9 ~0 W9 v% k/ r" t4 p0 |<P>ODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。 , t8 k7 V/ S N9 B4 `<P>ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。 3 o* t9 ^1 P8 q* X. k2 g2 D<P>ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 8 a& M4 @3 }% \, h9 M5 F
<P>表4 itemAction的类型值与含义</P> ( O! Q3 h: ?% l6 R- x<P>itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:</P>+ z6 |9 K! S* R
<P>类型值 含义 # Y2 K. F- H2 S7 r
<P>ODS_CHECKED 标记状态,仅适用于菜单项。 7 b k1 l0 D' h7 s& c5 g% V<P>ODS_DEFAULT 默认状态。 % a( D: V3 Z* K3 O6 d7 s1 o- d<P>ODS_DISABLED 禁止状态。 - L6 y. K4 T9 h: S* a& n
<P>ODS_FOCUS 焦点状态。 / n* B; c, t$ P
<P>ODS_GRAYED 灰化状态,仅适用于菜单项。 : Z+ R% ~8 h) r% p+ d( c
<P>ODS_SELECTED 选中状态。 4 e$ z/ ~+ j/ f; m
<P>ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。 6 ^& q S1 [' H5 s; m q
<P>ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 , M% s E6 ]4 {% t7 t0 N4 t3 Q; ~0 P1 k<P>ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。 , N4 c, a! X9 n" x
<P>ODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。 ' \( x8 v, M; [& d5 X) m<P>ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。 , i* ?, W r ~7 u- I
<P>表5 itemState的类型值与含义</P> 7 @5 Q, q; x, l; Q<P>hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。 2 D! g) O* A2 r# c5 @5 R: M<P>hDC 指定了绘制操作所使用的设备环境。 9 s, e- u) D& i- l1 ]# _- v<P>rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 # a2 n$ k( [9 D# G4 G) |
<P>itemData 1 I F% d! X: c7 r
<P>对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 & N& q1 ~9 H8 O4 R V# I) P- m( x
<P>对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 + n- w8 Y: w; M0 [2 @
<P>如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。 # x8 R, |) t! T* A
<P>图5是个相应的例子,它修改了按钮的界面: ) B. |5 D/ m) r<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650324712.gif" border=0></P> 9 D Y% F$ T% v7 h6 U7 ~<P>4 _) G: V9 M( W+ ^6 t
<P align=center>图8 利用WM_DRAWITEM消息美化界面</P>: R# ?& Y9 w! U9 b- f1 Q8 ~, _
<P>实现代码如下: 0 g2 j$ q3 J' S- M" t% Q
<P><TEXTAREA readOnly>BOOL CUi6Dlg::OnInitDialog() 3 ?0 G# d% N- j8 d{ ' u( e! a* [1 V% f( t5 h1 e! O //… : p5 h+ A/ E4 W) X2 j+ s //创建字体: S" X- t1 W* w
//CFont CUi1View::m_Font% u( r+ G: @4 t r* i, d4 W. j
m_Font.CreatePointFont(120, "Impact"); ' w! X( U4 W( Q4 ` //… 6 D) X2 b$ O! E M: M}3 K" {# I, g; p
6 T+ w: d V( E* ^& `
void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) 1 p" C' T+ R0 _/ D# P' `. X, Y4 m, U5 t
{. M. T. u8 s( P. J2 e! V# @/ G/ N
if(nIDCtl == IDC_HELLO_CFAN)3 A( \4 G% s! s0 B ?* E/ _
{) q$ s" \" D& ]7 D/ N, k3 ^) M/ V
//绘制按钮框架$ D5 S( _' [1 z5 f }
1 E5 @+ u4 U* X- x' ` UINT uStyle = DFCS_BUTTONPUSH; 2 q7 U# @: a' |; S //是否按下去了? 2 @! ~4 n/ G. C0 }" `- h if (lpDrawItemStruct->itemState & ODS_SELECTED)- B. c. C5 g A4 n+ f, G
uStyle |= DFCS_PUSHED;( s# |7 H h# w
$ S2 C0 i* y2 q. v3 g o CDC dc; 4 p, q! N6 B z# N; U9 W! Z dc.Attach(lpDrawItemStruct->hDC);. E; B. r! ?1 f' s6 K
dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle); : H7 o7 a. z, p# L0 {' W8 j. C0 ?9 w B! M
//输出文字 - T. B% ?2 ?- |6 ~6 k* x3 ? dc.SelectObject(&m_Font); 7 F Q( M( E9 w p/ \ dc.SetTextColor(RGB(0, 0, 255)); ( A r; N, X: p! F8 N1 @( ]9 P dc.SetBkMode(TRANSPARENT); 4 f! R+ y7 m8 E 6 `4 Q( t" y& B E8 ]% v CString sText;& A0 K" W" U/ J) N) I6 c
m_HelloCFan.GetWindowText(sText);; Q5 M: a8 I3 [$ L3 j, T
dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText); M6 a: K' B: r. Z0 _8 T: `8 ]/ d" a# f ^
//是否得到焦点 " w5 t' l( g! U( o7 ?* ] if(lpDrawItemStruct->itemState & ODS_FOCUS) / M; ]. r( Z2 @3 G/ F {. D$ O: F- ]6 ~2 ~; ^& A0 J4 N
//画虚框 ( Q" \. Q) b4 y, j CRect rtFocus = lpDrawItemStruct->rcItem;& s% U5 T* T4 s6 P! x+ _5 _, e
rtFocus.DeflateRect(3, 3); * E' `2 H0 o' T# ^1 @: X8 b dc.DrawFocusRect(&rtFocus); & b. a- ]+ W( N: b. ~ }9 s/ `% [9 \4 ^9 O
9 {( p. D/ S1 w( J! G return;* W6 y5 M8 x$ P a! `( m& |
}( K* J; T. G/ @9 `* V- q
CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct); - \& b. u2 U2 M0 N C2 \! Y& y}% b' D% W t" }7 a
</TEXTAREA> 5 l' ? Q4 y' F' H |<P>别忘了标记Owner draw属性: ( ?( P# m* R1 H<P align=center><IMG src="http://vcer.net/upload/2004/03/1046596492605.gif" border=0></P> 7 X* A: A( \" L, h* D, O4 {<P align=center> 图9 指定按钮的Owner draw属性</P>; [2 Y+ e2 Y, i! n+ g
<P>值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton:rawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。 9 s+ l. v3 _& j& ?# |' v
<P>- j2 H$ Q% ]4 Y6 ?+ E* t6 Z' c: ]
<P>0 z/ B% x" \* |/ a7 w9 G) R
<P><b>3.3.5 WM_MEASUREITEM</b> 9 S$ o, d& Y& N* |+ S3 n! R/ \" `
<P>' R# r5 y4 f. l0 I/ b, F* H
<P> ) X, v% y* \* F! Q( b4 s( A<P>仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。 9 c1 w/ ?) e2 k+ L
<P>WM_DRAWITEM的映射函数原型如下: , A" Z5 F9 s o" Z$ }3 p0 e6 O6 N, _; T<P>afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct ); # y( L- Z# P) T7 d6 e& j* `
<P>nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 : S }4 x+ k" x6 Z9 i<P>lpMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下: " V* D4 X) h* {* r
<P><TEXTAREA readOnly>typedef struct tagMEASUREITEMSTRUCT+ q9 D1 S) n6 O! f0 F. C
{ 9 O/ u' H1 T+ ]1 y6 U UINT CtlType;# m; O6 Q. k' J* f u; L- J
UINT CtlID; p( M5 i9 d% e- g- @
UINT itemID;6 N& b1 c9 |0 U0 h$ B
UINT itemWidth; ' r$ N& K" p5 P8 K UINT itemHeight; 9 U) Z% D6 X; y) g; V0 @. W; { DWORD itemData, M7 k$ C! z& [6 b! g5 c) c
} MEASUREITEMSTRUCT;+ \: h: F' L1 v4 [' y, G$ J
</TEXTAREA> 8 S3 w- a. i* T/ C9 J) V. f! O
<P>CtlType指定了控件的类型,其取值如表6所示: * L- B( L( G" g# J' j- A4 z
<P>类型值 含义 ' }3 J5 x& Q6 r/ Z<P>ODT_COMBOBOX 组合框控件 9 M% O" j2 p; j( s<P>ODT_LISTBOX 列表框控件 " O8 S3 @( @6 `% J" z- h" u
<P>ODT_MENU 菜单项 4 k9 a4 R% P4 H<P>表6 CtlType的类型值与含义</P> 3 z; q' c( K" t; i# ~. d: T<P>CtlID 指定自绘控件的ID值,该成员不适用于菜单项 % K. H' t V2 q$ ^ s- p0 @: o( Y<P>itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。 & S ^% P! ~( b6 T2 E<P>itemWidth 指定菜单项的宽度 - x8 n0 m- z$ A8 w. R<P>itemHeight指定菜单项或者列表框中某项的的高度,最大值为255 # P3 j$ v# ?# T+ F6 s5 w/ z' p* V<P>itemData 7 m0 k, u# ?' d. j2 V<P>对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 # T% v1 e. l$ e! K t
<P>对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 " r* G$ ]: d9 H- F
<P>图示出了OnMeasureItem的效果: / P! k: Y! r7 j* l<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650332513.gif" border=0></P> " C; G( J ~ R, T<P align=center> 图10 利用WM_MEASUREITEM消息美化界面</P> ( H4 e, z3 F/ `$ P) ^! n- [<P>相应的OnMeasureItem()实现如下: 8 ?! j) E+ `9 h
<P><TEXTAREA readOnly>void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) 1 }8 j! ]% f+ Q" x1 c) L{ # A' H, t1 z0 r4 f if(nIDCtl == IDC_COLOR_PICKER), }5 I) E' {+ ~2 v' P0 ^9 U
{8 a7 G4 j# p/ T$ s$ D; S
//设定高度为30 ; K/ Z* {6 b% t# t0 @3 g8 y0 K8 x lpMeasureItemStruct->itemHeight = 30;, e4 g" V, w0 B
return; 8 r1 \3 C3 U8 X }* Q ~9 h* w: @- s7 d" j
CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct); 6 o+ |. }4 V. C: f0 D" j6 } F N} ^8 l( `: ^8 W" z/ d</TEXTAREA> & {- n5 a5 A8 W- N% ]+ t<P>同样别忘了指定列表框的Owner draw属性: - ~4 g. q. Z& v" x, w<P align=center><IMG src="http://vcer.net/upload/2004/03/1046596451727.gif" border=0></P> V* n! g8 |1 H, l9 v, {- ?% p- I# y
<P> " j$ a( [5 I% L8 |. l# B) `<P align=center>图11 指定下拉框的Owner draw属性 - R+ r# T, j3 G: X8 z<P align=center> % h* s! H, D) d7 T
<P><b>3.3.6 NM_CUSTOMDRAW</b> - e: E h9 g% ?) o7 W; O<P>8 v6 w+ w* o0 g' |" ~2 H' [
<P>* b. `/ R; p8 K& B" E6 Q
<P>大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。 0 f$ I8 j- Y: U" t" U
<P>可以反射NM_CUSTOMDRAW消息,如: 0 F& f' Z; X2 l9 o<P>ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw) % e0 t' [. g5 i<P>afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); 8 M1 t5 b. Z3 N1 ?1 ^# G1 a0 f7 m
<P>参数: 5 [' N0 B$ x1 d. |. E( v* `1 W! B
<P>pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下: & P$ t0 c) p$ r<P><TEXTAREA readOnly>typedef struct tagNMHDR 3 M2 u0 J( S9 R1 G, \2 `{ 0 h' ~" X) i' I( j8 W( E HWND hwndFrom; 8 \6 _6 J9 S; {3 \
UINT idFrom; . ?- F6 Y4 \+ W; i a
UINT code; . O1 z8 L/ p1 C} NMHDR; 7 H6 N4 F, h/ N( \4 G* S</TEXTAREA> - S, c' C* b6 d0 X) f<P>其中: 9 M6 Z7 \5 D' E9 w. j# I1 E
<P>hwndFrom 发送方控件的窗口句柄 0 w; ]1 x* m" F# }% Z: Y) v
<P>idFrom 发送方控件的ID / A! G7 n" ?' z1 J. W, N<P>code 通知代码 0 Q+ y3 h' d; o<P>对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下: 7 H7 a' g5 Q2 q% {! s H) m<P><TEXTAREA readOnly>typedef struct tagNMCUSTOMDRAWINFO+ f* m- p- N# P$ u
{- W( @& r& x9 O' S2 ?, l+ `! S; U& l
NMHDR hdr;7 i6 a* v2 p7 d
DWORD dwDrawStage; 0 V1 b1 F( \% u# f- d* U4 e# R HDC hdc;* g5 d$ w4 `% j! G& w
RECT rc; : t% ~" K! L* z6 H DWORD dwItemSpec;7 F. s5 n' x8 Q: N( D2 e( o2 v
UINT uItemState; * m% L5 a& c; V8 A7 L/ x LPARAM lItemlParam;' V. P+ H. R7 q4 {5 [7 R/ b7 u' S- o
} NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;1 b4 b; F% p. k( J6 u* K& ]! Q5 `
</TEXTAREA> 2 `* j8 c! }4 ^, y8 M; b/ j4 t<P>hdr NMHDR对象 9 t. `* z7 ]. a" e7 M; F! ^$ p; `0 z<P>dwDrawStage 当前绘制状态,其取值如表7所示:</P> / X; ]! r7 G1 m9 j, t<P>类型值 含义 , l3 m! n f+ D' _: H" o; c V% |$ Z
<P>CDDS_POSTERASE 擦除循环结束 ' O3 D* G: Y9 ~( m* f
<P>CDDS_POSTPAINT 绘制循环结束 2 r: n! m" `+ S n' V! u5 ~
<P>CDDS_PREERASE 准备开始擦除循环 2 A' \5 I) n" R, e$ \; T<P>CDDS_PREPAINT 准备开始绘制循环 6 n4 ?; s$ J6 D: ]! U% v<P>CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 * r' j2 B) ?# c0 ~) D7 c<P>CDDS_ITEMPOSTERASE 列表项擦除结束 . T# l3 M+ U' }' v5 X<P>CDDS_ITEMPOSTPAINT 列表项绘制结束 6 f4 [( J, c. N: R8 Q6 T
<P>CDDS_ITEMPREERASE 准备开始列表项擦除 ! x) M, {3 |4 J$ H& s: c
<P>CDDS_ITEMPREPAINT 准备开始列表项绘制 1 E6 r; k0 E9 Y: v; @: M
<P>CDDS_SUBITEM 指定列表子项</P>! E% K2 ^ P3 w& q& t0 Z* i
<P>表7 dwDrawStage的类型值与含义</P> + T Q K7 d) H( D* Y1 U<P>hdc指定了绘制操作所使用的设备环境。 ( r7 b1 z$ L$ i+ x<P>rc指定了将被绘制的矩形区域。 * F; l, e/ ^9 u4 q
<P>dwItemSpec 列表项的索引 , y; z! S1 C2 A9 `/ a) U
<P>uItemState 当前列表项的状态,其取值如表8所示:</P>& |6 s Z/ C* z& _
<P>类型值 含义 7 l4 \8 \/ ^- t<P>CDIS_CHECKED 标记状态。 ' I! \) ?) `+ r+ M9 S2 |<P>CDIS_DEFAULT 默认状态。 # D. u- F& C! _2 }: y: s2 U# d0 W<P>CDIS_DISABLED 禁止状态。 2 T) X# K' J" [6 d3 e<P>CDIS_FOCUS 焦点状态。 # {6 V0 Y3 i$ H2 X+ ?# f% N" u
<P>CDIS_GRAYED 灰化状态。 9 X2 y1 B2 [, Y
<P>CDIS_SELECTED 选中状态。 - W- f/ P/ V3 a. c4 ^( g
<P>CDIS_HOTLIGHT 热点状态。 , w4 ?( N5 c. z' n) o; B<P>CDIS_INDETERMINATE 不定状态。 3 f: k- a2 F$ D c$ k<P>CDIS_MARKED 标注状态。</P> 8 K* `. b6 T4 E, [<P>表8 uItemState的类型值与含义</P> 9 a$ c% v; O, R$ G# [! l<P>lItemlParam 当前列表项的绑定数据 3 |+ j4 G L1 f# I0 y6 t! ]<P>pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: $ l7 j1 y1 J9 b5 \# F<P>当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:</P> % W" U; }) E- e3 l d J<P>类型值 含义 6 ~# q! M. z9 @' _ w% D! s- Z<P>CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。 5 r' ^' j8 w4 x# H6 D3 T' ~! ?2 y
<P>CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。 / R0 g: E+ f; K* W7 B- w
<P>CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。 1 X6 M$ o( y3 s: i1 ^! a<P>CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。</P> 6 ]' ]3 H: V0 `0 H+ F) F# P1 _6 ~( h& H<P>表9 pResult的类型值与含义(一) / J- \& b( e' [& Z
<P>当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:</P>! ^1 H3 S2 W$ D! r8 M% u
<P>类型值 含义 & q2 D$ r/ K6 v F- ?! A<P>CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。 ) H% p/ G2 w' g1 [0 d<P>CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。 3 f+ Z9 o6 ^1 G; j
<P>CDRF_SKIPDEFAULT 系统不必再绘制该子项。</P>; L6 f- P/ E" Q' c0 T% e1 g
<P>表10 pResult的类型值与含义(二)</P>* x7 C& {& X7 M) S( Y% x& E5 k
<P>以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子: ! ]+ j" k% v% j9 C$ U( Y<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650317752.gif" border=0></P> $ H. N7 \2 H4 {# w. Z<P> 1 w' a. r- b' k6 U7 l: ~$ o4 @<P align=center>图12 利用NM_CUSTOMDRAW消息美化界面 3 ~( W8 i/ Y% Q+ z+ S; \+ k<P>对应代码如下: " K4 \; E! a8 t$ {; h: @<P><TEXTAREA readOnly>void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult) " @/ E: x q& Y& Z( X" L2 i{ * i1 L- Y7 ~" [' E$ p% x% Y2 L //类型安全转换* a, T/ q0 H8 i$ A$ p1 E" O
NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);: p. A: M& ]7 b; d! S0 @
*pResult = 0; 9 M! S$ y) @+ G( E. U5 q " Z- p3 Z3 d* S //指定列表项绘制前后发送消息8 Q& U; \- a& W4 N3 g' c
if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage) 2 h' `' z1 Y: h {0 T) F0 C: }1 H8 G b7 p
*pResult = CDRF_NOTIFYITEMDRAW;2 n2 A) u2 F* V7 V7 @" T9 ^, V3 c
}' f& Z9 t) R, _$ ~' y' Z4 P
else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage) 8 P1 R, F7 Z: L/ k/ B/ v( ^+ y { & c4 F7 o3 f$ q7 m }9 D5 n# a) g //奇数行 5 y9 @2 [: L" Y if(pLVCD->nmcd.dwItemSpec % 2)% L! V1 v, y, e
pLVCD->clrTextBk = RGB(255, 255, 128); , m/ i7 R/ H' W T% f+ V# }+ ~6 a9 I //偶数行 : t. s) b L( {7 T* l+ V6 J. w else " q% h, n0 c) Q3 [+ X1 j pLVCD->clrTextBk = RGB(128, 255, 255);' f% u; F' T3 A, M3 ]. P
//继续 " h9 @% \; U& \' y+ u *pResult = CDRF_DODEFAULT;; h4 w, s, ]$ r
}# o% T0 B2 J" _
} , v* O4 ]4 ]1 K1 c7 v</TEXTAREA> . W' F. J8 D6 \' x6 e<P>注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。 + {8 w) ~. i; [/ V& r6 w8 k5 m
<P>" z/ L2 x) J3 \5 j8 X( |% }
<P>. |. i5 Y: k) c$ Z6 o5 {
<P><b>3.4 使用MFC类的虚函数机制</b> 7 w, I5 j! x" y& e: o
<P>- w5 O- ^9 I9 M
<P>5 q; z; A, e; u3 b# n+ o& a
<P>修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子: . k6 S% s# [7 c/ A7 u
<P><TEXTAREA readOnly>void CView::OnPaint()5 L5 } q% m. H; u& N' N' b, L1 d5 Z3 j% k
{+ w9 W1 ?/ r, L3 Q. x
// standard paint routine: A5 Q5 F! K4 Z: q- I i' c3 M" H
CPaintDC dc(this);6 T* C0 ^. \. `! z3 N
OnPrepareDC(&dc);9 W8 v3 X; r# r7 d3 G2 F! L
OnDraw(&dc); * r' i9 s- N. q) I( f2 }8 l$ M# d" `}& s) w/ @, }! z G* T
</TEXTAREA> - s5 L7 t( O7 Y2 S$ u/ B<P>这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。 $ x& M" V5 A, a1 B0 y* @( j<P>以下列出了与界面美化相关的虚函数,参数说明略去: 6 m2 n7 X) n) w( g" E; U<P>CButton:rawItem 7 s8 @0 R' h$ P* {% u7 O<P>CCheckListBox:rawItem ) P+ f, j$ n% o; B9 _% {. P# t6 R
<P>CComboBox:rawItem & B! r9 c; x# s" g0 S. U2 s4 r+ G<P>CHeaderCtrl:rawItem : H% K Y7 S3 S2 N$ X
<P>CListBox:rawItem b% u; U" l' _. S<P>CMenu:rawItem ( c/ _) V" V1 Z" J( f
<P>CStatusBar:rawItem 4 J( o% [# r, z' M2 S<P>CStatusBarCtrl:rawItem ; K) i0 b! |! }% p) H# a4 e<P>CTabCtrl:rawItem</P> - l% k4 O1 F$ Q) t6 F; I<P>virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct ); + u" ]0 g5 Z9 H8 ?8 u" R<P>Owner draw元素自绘函数 6 K0 {; F+ M2 M B# q
<P>很显然,位图菜单都是通过这个DrawItem画出来的。限于篇幅,在此不再附以例程。 </P></DIV>