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