数学建模社区-数学中国

标题: VC之美化界面篇 [打印本页]

作者: huashi3483    时间: 2004-9-27 18:32
标题: VC之美化界面篇
<><IMG src="http://vcer.net/images/item.gif" align=top>关键词</P>界面美化 , |# x( f3 E. Y2 S

( P$ B0 _! T. Q& _% E$ ]<><IMG src="http://vcer.net/images/item.gif" align=top>摘要</P>( w2 E) H6 |5 S1 ~. N' Y8 r; C+ V
<DIV class=vcerParagraph>
# Z4 k! w" N; T4 n# k: X  D<>本文专题讨论VC中的界面美化,适用于具有中等VC水平的读者。读者最好具有以下VC基础: $ F1 {- K5 [* H; {7 |; S
<>1. 大致了解MFC框架的基本运作原理; 2 a) G; t4 U8 _4 I. W; n) }3 q
<>2. 熟悉Windows消息机制,熟悉MFC的消息映射和反射机制; + v) X: N. \' m
<>3. 熟悉OOP理论和技术; 5 F' o  v1 S( N) s- v' \
<>本文根据笔者多年的开发经验,并结合简单的例子一一展开,希望对读者有所帮助。
; R( Q( K! K& l# D% {1 g' I! m! O% g
5 K: \" ?1 b! ]1 |</DIV>/ q1 }% I5 E/ c1 f/ c

3 r* ~% u( n& ?7 \* u<><IMG src="http://vcer.net/images/item.gif" align=top>正文</P>
+ O+ n8 x+ U$ {3 @  A, L# O<DIV class=vcerParagraph>: [( `5 H; h3 {) x5 }
<>1. 美化界面之开题篇</P>4 ]- {3 v) j3 G2 g' k$ J
<>相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
- ?/ }4 j/ H7 h/ P' q6 D; `<>
2 T# ?  z1 F4 N* E+ M<>
' I3 L# K& V" K  C& t! \% l< align=center><IMG src="http://vcer.net/upload/2004/03/1046596474810.gif" border=0></P>: N2 a% c: I" o
< align=center>  7 @; w2 @! ~$ ]  S) d( v! B
< align=center>图1 瑞星杀毒软件的精美界面</P>$ D2 W' a+ x: [0 P+ ~
<>程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。 & x0 ]0 N% ?& e) q" n% I! }  P' A
<>“受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。 . s- g. O8 e4 h5 U7 }
<p>
# x9 _' [' R' K5 ~<>2. 美化界面之基础篇</P>2 q1 \" e1 p) P0 s3 n
<>美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……
0 f* O0 Q0 X- p$ s6 p$ f<>
, Y( G; T( X/ v: }8 J<><b>2.1 Windows下的绘图操作</b> ; x# a# ]; L# |7 z
<>) F+ A3 h' O" u) M" D% B
<>熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……
5 o4 ~% W! r" [+ l4 o# ~' x<>Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。
& ]6 K5 {" A: M% |: y6 Y1 U" Q3 F<>, l# ~0 F8 h6 W) ]0 o# Q4 r
<><b>2.1.1 设备环境类</b>
1 r) z' [: ]8 ?: C8 ]<>
: \, W& J" t4 ~; X<>Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。
0 l/ F2 I6 P0 `0 M<>MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:
" T1 n  {2 m0 t) [; W2 B8 @! s<>Drawing-Attribute Functions:绘图属性操作,如:设置透明模式 + r# X( j( d7 l$ f
<P>Mapping Functions:映射操作
4 T# Y' A* O; Y* h! f5 @<P>Coordinate Functions:坐标操作 - J# u+ r0 u8 z5 Y1 a
<P>Clipping Functions:剪切操作 8 d: r+ ]& X& D0 y7 {% x! h: H
<P>Line-Output Functions:画线操作 8 }# d% q. f; P) N' T; S
<P>Simple Drawing Functions:简单绘图操作,如:绘制矩形框 9 u5 `; ]6 B# F4 k( b
<P>Ellipse and Polygon Functions:椭圆/多边形操作 " z7 w: Z) e  a+ M
<P>Text Functions:文字输出操作
% q. B+ K4 m% l" b: E! A<P>Printer Escape Functions:打印操作 3 ?+ |, F! K- p* G, p
<P>Scrolling Functions:滚动操作</P>
2 L) x% u# y! g8 ]7 s! |& z4 h<P>*Bitmap Functions:位图操作
$ v: V3 a" K9 L; ^  l* i/ z8 F2 i<P>*Region Functions:区域操作 . }" o2 e% w# D+ _
<P>*Font Functions:字体操作
6 k' E9 I) o9 T: U( t<P>*Color and Color Palette Functions:颜色/调色板操作</P>2 u: f4 f$ a/ n3 F0 n  Q/ U3 ]/ s
<P>其中,标注*项会用到相应的图形对象类,参见2.1.2内容。
; _3 _+ g  U* @<P><b></b>  
% I1 e4 a: h$ J; J<P><b>2.1.2 图形对象类</b>
) l' m$ ^/ ^" {<P>
. u! }; N. T, {9 T<P>
1 j" S  r0 n- W5 ^" |& s7 f# q<P>设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。
6 B8 ^0 s' i. }9 w+ N# @<P>下面的表格列出了MFC的图形对象类:</P>
6 ]  ~9 E, d, b4 P<P>MFC类 图形对象句柄 图形对象目的 1 R: Z* D0 q3 j  z2 K: r4 D
<P>CBitmap HBITMAP 内存中的位图
( p' `/ o! q8 Z" |; M9 h<P>CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式
. s' J# Y, F/ @$ ~& |<P>CFont HFONT 字体特性—写文本时所使用的字体 ; r) z. U! X* m* h' U* B) T5 W9 r
<P>CPalette HPALETTE 调色板颜色
5 B( E# o, S0 [8 }1 H; @# X<P>CPen HPEN 画笔特性—画轮廓时所使用的线的粗细 % j1 y& M$ C" U  z9 ]- [
<P>CRgn HRGN 区域特性—包括定义它的点 0 K' Z3 @6 \, P9 ]5 X5 x  {1 P5 s
<P>表1 图形对象类和它们封装的句柄</P>
1 m0 ~* n5 ^9 Z7 V/ o9 ~# i! L<P>使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:
% R& C2 k. }% ~1 z( Q- u/ q. N<P>
9 w9 A( F  Y4 H' x/ X. F+ a<P align=center><IMG src="http://vcer.net/upload/2004/03/1046651213100.gif" border=0></P>$ q# z2 m( `+ V
<P align=center> 图2 使用CDC绘制出的按钮</P>
- s8 ~, a3 b$ Z% E9 J$ h1 s<P>该画面通过以下代码自行绘制的假按钮: & c# t% {' K5 @3 p% `+ Y
<P><TEXTAREA readOnly>BOOL CUi1View:reCreateWindow(CREATESTRUCT&amp; cs)
# K+ _7 i, t! \4 z: y{" ]  P* P0 p' t- ~
        //设置背景色
  H7 P8 R! g1 C) W; Z9 c$ l$ h$ o        //CBrush CUi1View::m_Back+ l& Y) G* x) {4 o$ G# ?' z  B
        m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));
& M1 Z5 L  w4 u4 ~; j. n# F
: b5 x9 m  O" q        cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL);7 h" [2 r+ _1 l5 n' K5 c
        return CView:reCreateWindow(cs);3 a5 C" J- ?3 e) C7 c3 A
}
: k# @' i# b: ^
  l7 _2 }+ Q- M% S7 ^' Vint CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
7 B! S) T1 ?2 P, J4 m{
/ s6 l% k, @* X2 t- G( u        if (CView::OnCreate(lpCreateStruct) == -1)
8 D( L' I, a3 `                return -1;5 O2 ~" d6 k9 ]( C+ P$ R

% i  f4 H9 F4 h) Q$ y7 k! u        //创建字体
4 R- y8 b! h- {0 D1 y$ H        //CFont CUi1View::m_Font
1 f& O8 y. C/ a: J" q, i' g        m_Font.CreatePointFont(120, "Impact");
! r( B2 w) |3 H6 ]       
9 X1 F5 \5 w- W$ ~        return 0;
3 x7 |- V) H: V+ D% B2 J+ }# k}- H1 s. ]# R9 e, i6 A) W- Y) r

& H$ S% W; B8 Rvoid CUi1View::OnDraw(CDC* pDC)
, g& A% q6 F/ [& z{
0 m5 f. x8 o/ f8 c        //绘制按钮框架
( H9 c" M& I3 d+ S  M3 k/ W# f        pDC-&gt;DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);+ ~( q3 x2 }0 A! y6 f$ D3 |% m& S( r
5 D, |1 O/ i% r+ l* U
        //输出文字2 p5 K; y) f$ y9 c) M1 l
        pDC-&gt;SetBkMode(TRANSPARENT);
2 Q1 e6 V8 r+ C3 [3 q% H( c        pDC-&gt;TextOut(120, 120, "Hello, CFan!");
, p2 C& j# G0 C- h}</TEXTAREA></P>6 S- o$ T/ Q, b* |* t6 A- D8 o
<P>呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。 </P>
, r  e4 |/ _7 F, A- ~# C/ o<P><b>2.2 Windows的幕后绘图操作</b> </P>6 a% O7 f4 ], u; M0 X
<P>在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。
" g0 h5 P/ n6 {  T<P>有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。
5 B& I- @3 o# d' P. F<P>所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……
- t0 V' d: \  S4 {) j0 v; x<P>有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。
8 f! Y" w: n' @8 z& D" i<p>
1 r' _' y* A# ^3 Z# C* i3 \<P>3. 美化界面之实现篇</P>+ A* c8 z: g( i: @3 ^, r
<P>Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。
$ |7 o: @. E  Z  d( @1 [<P>
" {! L3 f" X0 q. m1 |6 _: s! ]<P>
6 N1 F) Y: a6 a( Q* O# w<P><b>3.1 美化界面的途径</b> % b% J6 x4 ?. ?
<P>5 z* D8 p. n5 q
<P>5 R; Y" M0 r6 @9 C
<P>如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括: 1 ]) p( ]7 a$ }
<P>1. 使用MFC类的既有函数,设定界面属性;
- V7 f) _+ }. k<P>2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;
2 `6 h3 Y/ h1 d+ e7 K<P>3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;
7 U/ H3 C7 @7 V8 z; Z4 s<P>一般来说,应用程序可以通过以下两种途径来实现以上的方法: ! I: k' v' i; R' I) ]' D# H
<P>1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;
/ A6 G; C! ^( ?<P>2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。
4 e1 q1 G, w9 w<P>对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种: 4 F1 j8 B& _. U+ _' S: b
<P>① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示:
. H3 M5 i/ a1 S- v0 B+ C<P>2 k/ G) t4 R+ k+ f
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046596487288.gif" border=0></P>0 w3 ^4 ]* t: V6 ^6 a" z+ G, r6 k! \
<P align=center> 图3 为按钮指定CXPButton类型</P>
5 u9 d7 U* ]" `+ n" O0 `<P>②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;
  f3 C- r4 v- C5 E$ t3 `<P>以下的章节将综合地使用以上的方法,请读者朋友留心观察。
( p0 D% M0 R1 K( h: `# @1 r<P>
) F  K" B3 p& q- f<P><b></b>  
) M# Y7 i# ]1 m; }) K<P><b>3.2 使用MFC类的既有函数</b> 6 ~) N  P) y8 M3 _# g
<P>
1 z1 i: H0 m' d% w3 r: E7 C<P>7 A" o! x; V( v% K8 f
<P>在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。
) ^9 ]1 z0 }( N9 x/ j) T6 \5 s<P>CWinApp::SetDialogBkColor
- R% d* S3 e! E1 m<P>void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );
8 L  N4 y/ [8 D<P>指定对话框的背景色和文本颜色。</P>
! ^5 t& K$ D$ K1 m<P>CListCtrl::SetBkColor , t5 ~# K! {" D4 `  h1 U6 A* B
<P>CReBarCtrl::SetBkColor 2 n6 W  f3 S: r# ^) d9 u
<P>CStatusBarCtrl::SetBkColor - Z! n3 m; P9 T. n3 ~/ b
<P>CTreeCtrl::SetBkColor 1 ]& _8 ]! E$ s8 [3 C
<P>COLORREF SetBkColor( COLORREF clr ); & |" V/ t/ H6 o. Y. L6 S
<P>设定背景色。</P>. u3 R; e. n# D
<P>CListCtrl::SetTextColor ( w' B, X- C& U; R$ ~" P
<P>CReBarCtrl::SetTextColor
" a% o- x  K: [8 J5 f# g% m7 ^4 k, `<P>CTreeCtrl::SetTextColor 4 _! n% \1 t  l8 C, @
<P>COLORREF SetTextColor( COLORREF clr );
7 r* H. r7 A  Z! g<P>设定文本颜色。</P>
. [$ h) A+ c/ r- _6 M<P>CListCtrl::SetBkImage
. E& d7 B/ o  o% F# h<P>BOOL SetBkImage( LVBKIMAGE* plvbkImage ); " Z# G# J! ?* Y$ c
<P>BOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0); # t+ }9 {  g* l* c9 L0 x
<P>BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );
: a' Z* K2 n2 E9 W2 Y) U<P>设定列表控件的背景图片。</P>: A& {6 Q, |! D
<P>CComboBoxEx::SetExtendedStyle
! o: S" K$ k! a& ~/ D1 A" T$ q<P>CListCtrl::SetExtendedStyle
% x. d9 U" n' s9 N& }( N<P>CTabCtrl::SetExtendedStyle - X$ ^  k- d( M1 C
<P>CToolBarCtrl::SetExtendedStyle
% a: f1 r' ]$ R5 h  E4 [; N<P>DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles ); 0 c) g. k! Y: e1 G
<P>设置控件的扩展属性,例如:设置列表控件属性带有表格线。 5 G5 ^: O( k4 q7 l$ [4 U
<P>图4是个简单应用MFC类的既有函数来改善Windows界面的例子: # E7 {) w/ C2 p& S2 z8 d
<P>
% _2 o: H2 x4 \& [<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650314708.gif" border=0></P>
# d9 J0 V) o& V/ a7 m& H<P>0 K! @+ ~0 \: s6 z+ K/ V3 ?+ {
<P align=center>图4 使用MFC类的既有函数美化界面</P>
* I9 `) ]/ c) [0 R<P>相关实现代码如下: ! V5 N' j8 C- H! c
<P><TEXTAREA readOnly>BOOL CUi2App::InitInstance()
) r9 Z& c. a5 \0 d/ \. z- o{
& ?0 L% N9 Q) p! z        //…
& j8 r/ L) n8 |        //设置对话框背景色和字体颜色, t+ J3 J, H, e' ~  P# ]) Y; C
        SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255)); 3 @2 H) I6 C% [# D  [
        //…2 b+ r- G; }! i' u( J, y4 }; N
}
7 z8 g. `$ a6 l+ W4 V  N1 y. ]- y) t
4 v2 r: V7 d! zBOOL CUi2Dlg::OnInitDialog()
; \8 a1 O. r4 ~; E3 r! d{2 M, C( j  ~' n
        //…; x+ G# ^) A8 X* D2 o
        //设置列表控件属性带有表格线* d% L6 H; H/ j  N
        DWORD NewStyle = m_List.GetExtendedStyle();
3 J( f$ p3 g7 X9 W2 _    NewStyle |= LVS_EX_GRIDLINES;- s* t! l* ]* {, Y, u
m_List.SetExtendedStyle(NewStyle);0 s. h# [6 h; W( u8 _- L1 D: D
- N+ ?& Z" H  d
        //设置列表控件字体颜色为红色& T/ O4 N# ?! s! P& {$ y
        m_List.SetTextColor(RGB(255, 0, 0));
- s1 I  O& a0 I3 C( }
+ X7 e/ H7 l0 x3 h        //填充数据
2 O2 Z+ l4 |! A8 }        m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);
* X8 `( R/ L! b& }        m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);
& J. ]" y& H& r: x' O& K
  Z+ S7 A! p! l        m_List.InsertItem(0, "5854165");1 d, y7 v2 q& B% j) x7 O" y
        m_List.SetItemText(0, 1, "白乔");
6 h+ v1 U2 ~2 _$ b) Z: [) E
9 n1 A9 U5 I0 J0 h% W4 n) g- g% }        m_List.InsertItem(1, "6823864");( Q! K3 k; I4 F
        m_List.SetItemText(1, 1, "Satan");# f) a, c* {: z) p6 c) O
        //…3 c+ _* s. W% ]: P1 _
}</TEXTAREA></P>
( r2 w& m: f6 A<P>嗯,这样的界面还算不错吧? </P>5 Y8 H4 T! I9 z4 x3 [( T1 x9 W
<P><b>3.3 使用Windows的消息机制 </b>
& L9 O5 W* k3 `1 z<P><b></b>  3 n6 C) u) I, X* j) M" J1 f" z
<P>使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息:
; [6 x4 u5 Y1 l2 c<P>WM_PAINT
8 l6 h( W0 k. l6 U5 K1 ?& a1 K<P>WM_ERASEBKGND ; K/ E1 d5 T, p- [
<P>WM_CTLCOLOR* 7 \6 S) w* @* u: G8 f
<P>WM_DRAWITEM*
( I1 j  z( G% n9 f9 B5 L  X! ~4 c<P>WM_MEASUREITEM*
+ ~0 h- n' C- v  L1 R7 Y<P>NM_CUSTOMDRAW*
. P8 n  ~$ d5 W2 T, ^/ |0 B<P>注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。
! K8 J* c0 _/ ]- e<P>
  p) d# f$ F$ A5 J. q" ^9 {! h3 n<P>
; {' @8 n- L" |: Y<P><b>3.3.1 WM_PAINT </b>
! K' c; |8 `! D. s+ g<P><b></b>  & P* w8 L, U  ~1 ~: U$ I+ G. F' F0 [" n
<P>WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。
6 I+ d+ S! ~0 n4 i) S  j! l<P>可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下: ' R9 f9 L8 h0 s4 G1 R
<P>afx_msg void OnPaint();
9 [  |/ z; ?% @: ^6 i; i& S<P>控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示:
/ V0 m5 C7 M5 v<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650335708.gif" border=0></P>
/ P( j" K' u6 R" d  {  Q% c<P align=center>图5 利用WM_ PAINT消息美化界面
7 o3 u2 r. d4 k7 q( u1 a. T, T1 w( {% Q<P>实现代码也很简单:
) a7 H" o7 L2 c+ e% \<P><TEXTAREA readOnly>void CLazyStatic::OnPaint() , b/ a" Y) u% J2 Y
{4 s7 B3 r/ ~; d) z* l. j* R( R
        CPaintDC dc(this); // device context for painting
0 U9 b! Z' I% e8 r9 E. U" {8 r. @% t        : o/ i' G+ o$ V( w0 o$ s: P
        //什么都不输出,仅仅画一个矩形框
9 e7 y5 ~- \) s* M% g' b3 ?        CRect rc;6 X. ]1 J5 J/ s, I: [& n8 _
        GetClientRect(&amp;rc);
' c1 g9 h3 X! X        dc.Rectangle(rc);        ; K# K9 r. E/ b" s' M+ c; b3 t
}, m+ M$ E; i& j& x; T8 i4 i( W
</TEXTAREA>
0 c: ?9 J9 ]6 Q<P>哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。 ' k; t& m: v, W
<P>
% k. {5 A' W4 E2 p  n& j<P>
8 C% ~, B5 e, ^& h2 M; M: ?<P><b>3.3.2 WM_ERASEBKGND </b>6 q* C  y! \6 @: L% _$ S8 o4 r
<P><b></b>  
$ {9 `+ s3 Z" g4 T$ E! U. i6 s; \# L: b<P>Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。
) Y( j1 N! V, r' R2 h, n<P>可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:
# Y3 \; F& ~% W<P>afx_msg BOOL OnEraseBkgnd( CDC* pDC );
  C: x6 q" }. Z! [9 |/ r4 v<P>返回值:   n$ H9 Y& Z+ p! G( Y
<P>指定背景是否已清除,如果为FALSE,系统将自动清除 ; ^4 b& R) M3 [$ w2 S
<P>参数:
" ~8 f# j) d8 q  e( c: {<P>pDC指定了绘制操作所使用的设备环境。 9 X& E& e/ j# k; V5 w- t% Q& C0 n
<P>图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景: 6 O5 s; P- {* h: m7 b1 Z
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650328908.gif" border=0></P>
# V- X% ?- p8 t% U<P>
. j  E. x. N: v: D5 S1 N) S6 J<P align=center>图6 利用WM_ ERASEBKGND消息美化界面</P>2 G/ ^( i- r" Y6 w
<P>实现代码也很简单:
; g& k2 O% |2 Y4 N4 B1 g2 E<P><TEXTAREA readOnly>BOOL CUi4Dlg::OnInitDialog()
0 I! t9 p+ V. l+ j- V{
$ t. q6 V# ~5 N//…
3 _# u9 Q( B8 Q3 n/ p        //加载位图
; m4 o8 R% P4 \+ Q. C4 Q        //CBitmap m_Back;
6 e( h2 d1 g' W" m+ n  J        m_Back.LoadBitmap(IDB_BACK);# k2 z! ~3 _& N, C
        //…3 W$ S; y- ]& s# Z. e% N
}
2 d& `. f5 l: T9 F9 b+ e9 B& q
* S) a( Q' Z6 N% CBOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC)
, ?! E  a7 ?1 n" s& D+ c4 D: h$ I{
5 K3 e0 B! s1 r2 B. }" U        CDC dc;! t/ {# d$ m$ q3 I' z! X3 w
        dc.CreateCompatibleDC(pDC);7 v" Z: s" o& h" G% u) A
        dc.SelectObject(&amp;m_Back);
$ O& l; G/ Z3 @5 Q3 g  g- B6 S, P
        //获取BITMAP对象% Q6 g* D; S+ \9 W/ C; I8 o
        BITMAP hb;
. a7 n* \6 N/ _/ \        m_Back.GetBitmap(&amp;hb);
; n, N& @3 W' T; Q$ l1 ]
( `3 Q9 D# [) {* O6 Y; c        //获取窗口大小
; W! ~% J8 m! L8 |3 w5 G( {        CRect rt;
5 e6 M4 W/ ^+ G        GetClientRect(&amp;rt);8 a( J/ i% \" t
        //显示位图% I# A) R* s: X4 |6 u8 v; V) l$ r* d
        pDC-&gt;StretchBlt(0, 0, rt.Width(), rt.Height(),% W/ w% N: B0 @1 E7 X
                &amp;dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);4 t8 Z3 S( @( a+ d$ i1 N

2 Q. l" W$ C; j% R. v        return TRUE;1 q: U: c' N8 o$ m( p- z/ V
}  P4 f5 @, M2 Y$ p3 _; n* b4 a
; e: g; s1 W. v$ T4 j/ d6 y
HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
3 j7 w0 m! `- D! {{
9 u/ k$ i% |0 Z( H" `- T- W' W2 E        //设置透明背景模式
+ v/ b( C9 a3 c8 m  s        pDC-&gt;SetBkMode(TRANSPARENT);6 @1 i2 \$ {3 o
        //设置背景刷子为空
' a7 {6 [) _+ P' U8 F1 Q6 i: O3 [" A5 }: {        return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);. ]4 g7 O6 E5 b/ W* g2 S* [
}
5 t, {: s4 I7 H  S</TEXTAREA> / }& r6 }9 b5 A* w+ f) o
<P>同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。
) u8 K/ S* B! y2 D7 Z+ @<P>
- ?" y5 q# w- k$ I7 b8 f<P>
9 j' @8 c: {( |3 l<P><b>3.3.3 WM_CTLCOLOR </b>
) q) H! a( s- b" G# w<P><b></b>  
0 C+ D" m6 m; W+ [* L: q! G<P>在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。
/ f, h1 r, _6 c* o<P>WM_CTLCOLOR的映射函数原型如下: & R; k$ ?  Z2 G0 J9 @2 q# C0 h
<P>afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );</P>4 q7 `# I1 g$ \8 f
<P>返回值: 8 E7 S7 T% O1 L
<P>用以指定背景的刷子
# d& Q  p( U6 I3 b( p. Q<P>参数: 7 g6 @, J) i) W4 o0 I& y) v
<P>pDC指定了绘制操作所使用的设备环境。 / F' z2 H2 W& {
<P>pWnd 控件指针
. b1 @- B% F* f2 E- ]8 O<P>nCtlColor 指定控件类型,其取值如表2所示:</P>
6 m7 K- N/ A6 R1 y/ W<P>类型值 含义 6 V" l& t2 X/ u2 n. X
<P>CTLCOLOR_BTN 按钮控件
0 M- Q: g* R- S( N& g<P>CTLCOLOR_DLG 对话框
$ m9 z- k9 Y: N/ a$ _<P>CTLCOLOR_EDIT  编辑控件
) Y, A- R8 A: |) v; N0 s+ C<P>CTLCOLOR_LISTBOX  列表框 # w8 s2 ~" p* `! Z& G/ P
<P>CTLCOLOR_MSGBOX  消息框
8 n5 m3 \9 w* ?7 [<P>CTLCOLOR_SCROLLBAR 滚动条
5 p4 v: D5 n- O# n. [<P>CTLCOLOR_STATIC 静态控件 ( Y0 ]2 M. G% M& D
<P>表2 nCtlColor的类型值与含义</P>/ O9 H2 E; _9 m8 d) v
<P>作为一个简单的例子,观察以下的代码: 6 F/ a, D; W6 |3 T+ O
<P><TEXTAREA readOnly>BOOL CUi5Dlg::OnInitDialog(), }7 z1 H5 p8 O7 u/ V
{
7 x4 I3 n* ]3 b9 l+ [1 b        //…
- G$ L! v# E1 H2 L; U: n        //创建字体! r. X+ k. Q0 ]- F+ G: ^7 z) g
        //CFont CUi1View::m_Font1, CUi1View::m_Font28 M$ D6 F/ V8 t/ Z$ e
        m_Font1.CreatePointFont(120, "Impact");0 l3 i% Q- U4 Y: S; I
        m_Font3.CreatePointFont(120, "Arial");, v: P1 i6 C, O3 n' n# N' o
       
) I' H' ~. f* y0 Z  U  A        return TRUE;  // return TRUE  unless you set the focus to a control ) @# L6 G( u/ Y+ L, o2 _+ W9 X* b# E
}
: o8 T% e' p( E
6 Q% |4 i: p$ `3 t5 i: R, eHBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 4 A% L, x$ h# W3 O- x) v7 D& b
{
2 V, F# `2 k# o        HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
, m: O2 u5 y( [* N8 w        if(nCtlColor == CTLCOLOR_STATIC)" a) `2 |, ]. S' Y& Z: ]
        {; \4 f2 a) g+ j  x9 w
                //区分静态控件
& H! i1 F0 ~: I; I# ?0 Q1 @6 }$ ?                switch(pWnd-&gt;GetDlgCtrlID())
5 n1 G' r# c( e9 P                {$ N$ P; {/ v4 f- p3 e
                        case IDC_STATIC1:
) z/ v( m5 ^4 V6 p' k( v! y                        {
9 g" F; T. S& I                                pDC-&gt;SelectObject(&amp;m_Font1);$ ?) U/ J4 ^1 E4 Z" P" C! f
                                pDC-&gt;SetTextColor(RGB(0, 0, 255));; B+ ?1 e9 M" d' w3 @
                                break;7 H+ U+ O4 H+ u7 O' g
                        }
- l; |4 O/ f4 [/ e1 D. F                        case IDC_STATIC2:
: I6 x0 h6 u* i  i7 I                        {: M6 D. v( i8 x" I( {( p- v* T  G  D
                                pDC-&gt;SelectObject(&amp;m_Font2);# I3 [- R/ Q: d6 }; N+ F9 u% `
                                pDC-&gt;SetTextColor(RGB(255, 0, 0));$ ?; E& H& E3 E
                                break;/ X/ `% K( F3 f! f6 [/ l. O
                        }5 q4 V1 ^, f. D6 ~
                }1 a  m9 g* ~( F- h1 h- e7 V
        }5 A& ]+ i' N" f: g9 K
$ K1 X5 n$ u  s; A4 q: s8 _
        return hbr;" r; m, ~1 q0 M/ m, e" j! i) ]
}
+ u6 \" K. q$ F- v</TEXTAREA> - C9 V4 s7 g  c
<P>生成的界面如下: 8 o5 e. }0 A4 Z8 u7 s$ _1 s
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650321578.gif" border=0></P>& G: |4 i$ S. N& u4 p: A
<P align=center> 图7 利用WM_CTLCOLOR消息美化界面 </P>7 K( T% Z& J5 j. ?6 l: X, w
<P><b>3.3.4 WM_DRAWITEM </b>
0 C! }$ P8 @+ s! s: l<P><b></b>  / w1 |) ?0 w. [5 l$ ^0 V
<P>OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。 . z  \7 l- t" _2 T0 E# _; S
<P>当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。
7 u& y% S: [( p: x) h+ M# {7 J<P>WM_DRAWITEM的映射函数原型如下:
$ A* m: }, W- p& g" j<P>afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );</P>
' X9 R) j* t9 a" ~7 |+ {+ p# U9 b<P>参数:
) w( u0 d! Q* W<P>nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 ) M9 a4 O( M( ~
<P>lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下:
& w5 P$ J- k2 O9 n& }( N7 R! A9 W<P><TEXTAREA readOnly>typedef struct tagDRAWITEMSTRUCT4 @) `# k6 W& f8 f
{( R/ `8 _5 Q5 |+ i  S& T4 r* F9 m
    UINT   CtlType; # e' J4 H0 Q6 w8 p# X
    UINT   CtlID; : a' v0 n4 i9 T9 {5 u
    UINT   itemID;
4 ?; S) d4 o  `* `* w* |8 A% p    UINT   itemAction;
6 m# d8 j" v) v- k5 s! C* a5 ?; M    UINT   itemState;* h( C" K/ h5 T: ~# u
    HWND   hwndItem;
! K3 I3 U0 e7 R( @$ r4 ?    HDC    hDC;
! O5 Y' {; O) l/ G/ H" t    RECT   rcItem;; R3 W! f. Z4 E7 K: z
    DWORD  itemData;
8 x  s& R! v2 D! s2 h' c0 D4 z2 @8 w}DRAWITEMSTRUCT;
) G9 J$ ?- n' _9 W/ i  t</TEXTAREA> ! _# h1 C7 o7 |) `/ A; m9 n# |$ n
<P>CtlType指定了控件的类型,其取值如表3所示:
9 @; H9 s  B- {<P>类型值 含义
6 @+ K  k* I6 w, q. i# z<P>ODT_BUTTON 按钮控件
$ x; i7 @+ i3 {$ ~; ?4 e$ w<P>ODT_COMBOBOX 组合框控件 6 b6 i; t+ O2 J6 C
<P>ODT_LISTBOX 列表框控件 * F# M% a  c- u
<P>ODT_LISTVIEW 列表视图
- D4 @; F1 f2 E<P>ODT_MENU 菜单项
6 b# Y0 q1 f" z# L<P>ODT_STATIC 静态文本控件
+ L( s% j1 q# H<P>ODT_TAB Tab控件 7 W) G, F2 |, B/ u. {- z; c. m+ I
<P>表3 CtlType的类型值与含义</P>
' z; S7 t  Y! H/ d: i<P>CtlID 指定自绘控件的ID值,该成员不适用于菜单项
' s2 w; c* h" F; E# h<P>itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。 / t& Y7 B: O% A, |# W/ w! p: \
<P>itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:</P>& V; b- K* {: ?7 r0 ~' X5 p1 L5 P% k
<P>类型值 含义 7 d9 V- u+ @4 ^; \" r1 x) ]
<P>ODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。
6 a% [2 u+ |3 z2 L! |<P>ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。 # W% Q2 W: |: I7 }. J: v8 J! J
<P>ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。
9 h" _9 G3 }+ i<P>表4 itemAction的类型值与含义</P>
1 q0 H! D7 X0 p<P>itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:</P>
( U* C: N4 U7 Z3 u; h$ G3 U& Z<P>类型值 含义 0 o9 c: o0 a5 m) k9 d- \
<P>ODS_CHECKED 标记状态,仅适用于菜单项。 $ L0 H$ [3 |8 K
<P>ODS_DEFAULT 默认状态。 - B0 M4 u/ R3 w/ r7 O# o
<P>ODS_DISABLED 禁止状态。 ( o) V6 ]- p0 V- O
<P>ODS_FOCUS 焦点状态。 8 L8 y8 Y2 S# B9 E5 m2 m' t
<P>ODS_GRAYED 灰化状态,仅适用于菜单项。 " m* X; u$ f  |2 ?( y$ p
<P>ODS_SELECTED 选中状态。
; |7 y! j# b% J) ]<P>ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。 2 O# B! P! m$ i9 N; l  b
<P>ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 $ ^4 S8 s- C1 Y6 q! G: p, n+ C
<P>ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。
1 K: a, v3 f0 Z) w( s; D<P>ODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。
0 D8 S0 o+ I# k- C& @<P>ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。 , x  J+ S& P; \: b3 r, A
<P>表5 itemState的类型值与含义</P>" T1 Y! c+ L4 j
<P>hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。   @0 v6 V9 {, D8 L; z
<P>hDC 指定了绘制操作所使用的设备环境。 " X7 v/ W: L0 d  S2 T
<P>rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 8 ?$ g4 g* h; p# w9 S; m
<P>itemData
9 @% X; V% _2 M' i9 L<P>对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
0 u3 c  z5 ~1 d- U  D<P>对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 - ^" d7 q; O/ J; }
<P>如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。 6 ^" V  _, [" A# }/ N; J9 S, J
<P>图5是个相应的例子,它修改了按钮的界面:
2 X& L, f+ Z7 e1 k, @<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650324712.gif" border=0></P>
0 i) R; a) a; t3 d3 x<P>/ Y6 _, X1 V" M$ u: T& J# o
<P align=center>图8 利用WM_DRAWITEM消息美化界面</P>( K& }0 D/ f/ R; S+ c/ X3 w5 S# O# X1 {
<P>实现代码如下:
) {. e4 n! o3 N; {5 m# d<P><TEXTAREA readOnly>BOOL CUi6Dlg::OnInitDialog()
4 f$ R: U8 e2 {: x) Y. \{
* i* z: w8 o+ c        //…
* N7 `1 M( [' k' y/ @        //创建字体/ [* n, r* F2 m& C* V
        //CFont CUi1View::m_Font: |! i) b7 k; v! z* V/ C2 f
        m_Font.CreatePointFont(120, "Impact");
8 ~/ a" d1 m3 T& D+ B8 q( S        //…
* `. G3 `1 M% d. M% p/ a: h* l$ J}
% z- q5 Q2 q) T; M* O) H) `( w
% L" f" B8 _0 m8 J5 g7 Wvoid CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) 7 c7 e( c7 d% d" K5 d/ P% i
{8 N) G: [( Z: B
        if(nIDCtl == IDC_HELLO_CFAN)
! }  r. p/ J4 M$ Q+ Y% L        {' D* R$ ?2 B4 {) Q' ~; o
                //绘制按钮框架1 }2 S$ k( R& H5 t
; R' Y; |9 C6 y, O5 D
                UINT uStyle = DFCS_BUTTONPUSH;
% F5 t4 o0 r  f2 K3 F2 w                //是否按下去了?) P1 Q. @8 g( y: R. k
                if (lpDrawItemStruct-&gt;itemState &amp; ODS_SELECTED)/ q: G2 m, C4 N' S1 ^) Q: b
                        uStyle |= DFCS_PUSHED;; x7 s" ~- P# [) y& ?

/ L) L$ J' H/ l) B                CDC dc;3 t+ h& s1 ?* P
                dc.Attach(lpDrawItemStruct-&gt;hDC);
3 [  N; l3 q1 D+ A8 _                dc.DrawFrameControl(&amp;lpDrawItemStruct-&gt;rcItem, DFC_BUTTON, uStyle);; |; d# \) N8 l6 s
# e5 Z  s6 u, h/ p! d7 y
                //输出文字
* w- i: v" p; j9 }$ j* ^( w                dc.SelectObject(&amp;m_Font);0 L/ S0 I: W! z$ V3 c, l6 {+ Y
                dc.SetTextColor(RGB(0, 0, 255));% {! ]& Y7 J/ f: P8 ^
                dc.SetBkMode(TRANSPARENT);
" f  T$ d5 @  T: l$ r% ^) Y9 m8 K
                CString sText;
! _- ]- F" R/ a; G: J/ p5 s3 p+ Q                m_HelloCFan.GetWindowText(sText);" N) [6 t- a+ l6 L
                dc.TextOut(lpDrawItemStruct-&gt;rcItem.left + 20, lpDrawItemStruct-&gt;rcItem.top + 20, sText);' _" j) A6 _0 Y" V  b

) O! N4 R/ o0 ?2 v  w4 P1 L, N                //是否得到焦点+ L: l/ S! h4 K/ p) N& V' f
                if(lpDrawItemStruct-&gt;itemState &amp; ODS_FOCUS)
( a: q5 D9 p2 W* G                {
6 X" \1 K5 a* r, V$ [- e/ l                        //画虚框
1 C- Y: v9 t$ t( B* t5 |9 m) n                        CRect rtFocus = lpDrawItemStruct-&gt;rcItem;+ P) S5 W$ A0 ^% t$ f2 E, z
                        rtFocus.DeflateRect(3, 3);5 s. Y" R& k9 ^4 _6 Z  D& J
                        dc.DrawFocusRect(&amp;rtFocus);
) Q2 q) w2 T- y$ |& `% M                }1 N1 f7 b, D# s- X$ j% G9 g
9 W3 z0 U6 m6 P/ G0 t& }
                return;4 C, g$ z, k3 ^$ Y5 N* R8 N
        }) q, ?% Z8 [8 Z6 W4 I8 |! \3 z
        CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);9 B  k/ G, n# ?2 X2 ?( x
}" h9 u# t. `: `6 _0 d" _' [
</TEXTAREA> 7 l3 A$ s) M1 Q; S; X- d2 S2 {: n4 c9 j
<P>别忘了标记Owner draw属性:
, A9 W, [- c. `4 u# ]% d4 y<P align=center><IMG src="http://vcer.net/upload/2004/03/1046596492605.gif" border=0></P>
0 N* B0 ^* A8 p<P align=center> 图9 指定按钮的Owner draw属性</P>
+ i3 C0 P! v* p' w. w* P<P>值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton:rawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。
" Q/ ~. f0 l, C<P>, u6 s, g5 c; n9 K+ [; a9 a3 {% j
<P># u: \2 ?2 ~2 g* j
<P><b>3.3.5 WM_MEASUREITEM</b>
- l; S! U/ n  P1 M5 U: u: n, }<P>
' B' j7 t# m  X7 v<P>
) x& E' @5 ?8 [8 O# L<P>仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
  \: [; l* B# i( `6 h/ H<P>WM_DRAWITEM的映射函数原型如下:
7 E! A* O0 p' V- k# O<P>afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
4 ^# {- O2 |0 x% E5 T* f$ L" p<P>nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0
: _* t3 ~' J, g% q8 a. H<P>lpMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下: & @' F0 y# s' V) U7 W
<P><TEXTAREA readOnly>typedef struct tagMEASUREITEMSTRUCT
. K3 L9 r' Z* G/ g3 c{
3 a  s# n8 R1 B) \    UINT   CtlType;6 n$ x& k+ O+ \0 j
    UINT   CtlID;
7 e2 ]* l3 {9 \) T" {3 O6 k    UINT   itemID;
  E% |+ t! e. V7 u- X0 x/ m    UINT   itemWidth;9 ]0 ?9 C0 T8 n  b) U" r
    UINT   itemHeight;
& G8 H8 Z; h; Y: S& e$ e    DWORD  itemData$ R( [( i' [0 ^
} MEASUREITEMSTRUCT;) q7 m" M- `' ]- r  y
</TEXTAREA> 1 n4 E: J! w' k$ ^+ n
<P>CtlType指定了控件的类型,其取值如表6所示: 1 [3 K( [# _" i8 m! J8 E" k: S/ }
<P>类型值 含义 2 X4 x( u1 N2 W9 [3 p* n  i7 j
<P>ODT_COMBOBOX 组合框控件
' j) z6 n( E4 a9 r/ S& @3 w<P>ODT_LISTBOX 列表框控件
3 {' a! q* b& A0 g) v<P>ODT_MENU 菜单项 ! }. C8 B3 I3 a, U% f8 }7 O; q
<P>表6 CtlType的类型值与含义</P>
$ {/ ^1 g& y. Q& m3 l' Q<P>CtlID 指定自绘控件的ID值,该成员不适用于菜单项 4 M+ G% @# c$ r6 j
<P>itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。
2 S. g. ?7 B/ R, d( v4 \<P>itemWidth 指定菜单项的宽度 - s0 V$ |* o' G) @4 d
<P>itemHeight指定菜单项或者列表框中某项的的高度,最大值为255 + W0 c, u, h# l" {  o$ C/ X& }5 k
<P>itemData , b/ L1 T* C; c4 ]6 T
<P>对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
& g2 w$ [) P3 \" H3 M<P>对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
/ B5 u% Z9 N2 W  w7 x<P>图示出了OnMeasureItem的效果:
5 g$ @: q" K" w4 _<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650332513.gif" border=0></P>( S9 Q8 Q) l" o" t2 d" t4 M
<P align=center> 图10 利用WM_MEASUREITEM消息美化界面</P>, X, p5 J& _* l8 Y
<P>相应的OnMeasureItem()实现如下:
3 I6 n2 D4 h  _8 t<P><TEXTAREA readOnly>void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
6 Z. R. {; x+ V  P+ `8 V0 j, h{9 ~# ]( E; J8 b
        if(nIDCtl == IDC_COLOR_PICKER)6 ?: T' J' }+ E! s1 A- n
        {
) V. J; o: j  h( Q                //设定高度为30$ n3 l7 u5 F# ]% H7 H
                lpMeasureItemStruct-&gt;itemHeight = 30;
& w9 E6 G8 l& _3 z8 W' {                return;" ]3 Q+ L$ n/ x! w% n, Q- F! d4 k
        }
  o5 y% d$ ]' y  {        CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
% u' T% p3 V. N6 @# t3 R! ^}
- A1 E5 I( v  e* Q/ Q/ I</TEXTAREA>
" t5 x9 t: S0 o. s9 I7 I<P>同样别忘了指定列表框的Owner draw属性:
0 C$ M( c' z3 [. n, l7 C<P align=center><IMG src="http://vcer.net/upload/2004/03/1046596451727.gif" border=0></P>
6 B1 d& Z$ j- O2 X  W+ a<P>0 q& {, `3 u; B& M# u: t
<P align=center>图11 指定下拉框的Owner draw属性
$ }2 E; e5 ]+ d1 q<P align=center>  
4 A& u$ j2 F; s  N: K; O9 l0 S$ k<P><b>3.3.6 NM_CUSTOMDRAW</b>
2 o# |$ d: W6 h4 r# I<P>
  @" W  p- i5 \" c  G' e2 {<P>- A/ ^/ @; B4 S* ~& C4 k* M$ S5 K
<P>大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。 3 q2 R& ?* i+ Z* k; y; y
<P>可以反射NM_CUSTOMDRAW消息,如: - F# X/ s7 a2 N7 ]) {7 p5 s- H, E
<P>ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw) 5 G8 I5 x, n) h* q# s; i: P  s# V
<P>afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); 4 `! y. d4 q3 Y* c
<P>参数:
8 L8 D" W8 c) _<P>pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下: 0 Q9 T$ l2 i. M/ G! K
<P><TEXTAREA readOnly>typedef struct tagNMHDR1 c$ @8 z. L; \
{
$ a, ^" m; N: s+ A/ X    HWND hwndFrom; 8 [2 d" v' I; k+ |  t" J
    UINT idFrom; # `3 O9 z, C, a6 K, |- @  K% p
    UINT code;
4 Z2 U. F) B8 a3 l5 h} NMHDR;3 _  f: g7 z, L4 u
</TEXTAREA> : Z( b/ h. u" C4 d
<P>其中: . Y$ x, B' j4 G0 Z* r: q* ^
<P>hwndFrom 发送方控件的窗口句柄 $ k: w' I: n, x
<P>idFrom 发送方控件的ID 0 X  `; J* C! u, M% C
<P>code 通知代码 / p0 _0 w1 c- ^5 b
<P>对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下:
) w* J1 R* y# [: e' J: K<P><TEXTAREA readOnly>typedef struct tagNMCUSTOMDRAWINFO
6 k/ |6 @2 h- U) M" ~{
: y9 U- r3 }1 z0 T8 v    NMHDR  hdr;
( C3 ]; V8 d( L/ M: q# Z& _    DWORD  dwDrawStage;1 Y/ L& }% V* h' T
    HDC    hdc;
. {* m1 |$ X" ~! O/ C    RECT   rc;
. N" ]! T' c' w- {5 m+ s0 {; A3 x# ^    DWORD  dwItemSpec;* l% o, g; h' }! b
    UINT   uItemState;6 }! D6 \% y% S5 A4 M
    LPARAM lItemlParam;
( s1 l* g: L5 A; O} NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;1 w7 v/ q4 W" Q+ T9 L
</TEXTAREA> ) l% K1 L$ J) x. q- j
<P>hdr NMHDR对象 $ R" N. z, E2 m+ g; c
<P>dwDrawStage 当前绘制状态,其取值如表7所示:</P>
9 B# ~/ O/ _* T" n<P>类型值 含义 + N2 e7 a5 ]9 o8 B7 W# R' n
<P>CDDS_POSTERASE 擦除循环结束 ( j1 b1 A" S3 K7 ]) q1 D! y( d
<P>CDDS_POSTPAINT 绘制循环结束 4 {$ R% Z3 Q" g- h* y% d* k5 M9 H
<P>CDDS_PREERASE 准备开始擦除循环
; l; t# H4 Z5 _/ @<P>CDDS_PREPAINT 准备开始绘制循环 $ O3 J0 S9 g( J- {0 q  y
<P>CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效
/ O$ w4 ?) \4 }. z' E; n& w4 a<P>CDDS_ITEMPOSTERASE 列表项擦除结束
# T7 ?' {, l. Y<P>CDDS_ITEMPOSTPAINT 列表项绘制结束
  g) o# ^7 v" \& z8 G' u) m! w<P>CDDS_ITEMPREERASE 准备开始列表项擦除
! r1 b& O" n$ w. a<P>CDDS_ITEMPREPAINT 准备开始列表项绘制
# c& x* x& ]+ w0 x* m' Y$ y' Q  v! m<P>CDDS_SUBITEM 指定列表子项</P>
- O8 }8 o6 ?+ ^6 X<P>表7 dwDrawStage的类型值与含义</P>
+ n. v" j. K# Y/ u<P>hdc指定了绘制操作所使用的设备环境。 # N& R) j! u3 |# J
<P>rc指定了将被绘制的矩形区域。 % ^5 \6 }0 V6 z, p
<P>dwItemSpec 列表项的索引   t" p8 B8 Y. k) z9 G9 g  _0 J3 G6 L
<P>uItemState 当前列表项的状态,其取值如表8所示:</P>
0 ?2 S, z' k" L0 V<P>类型值 含义 8 Z8 e0 y$ m) x2 \" B" H6 Z
<P>CDIS_CHECKED 标记状态。 ; i8 u8 B" C) v8 Z
<P>CDIS_DEFAULT 默认状态。
7 O1 A- r+ N+ w: k' i<P>CDIS_DISABLED 禁止状态。
8 i0 ]+ M  s3 |3 o; p6 r7 R<P>CDIS_FOCUS 焦点状态。 * u! Z* {# w& k1 x& o6 R" v) k
<P>CDIS_GRAYED 灰化状态。
8 U0 I' r5 ?5 O<P>CDIS_SELECTED 选中状态。
6 H7 S% ~+ P/ [! O) k<P>CDIS_HOTLIGHT 热点状态。 6 K& Y- u0 Z7 \$ g  u" i$ `3 D
<P>CDIS_INDETERMINATE 不定状态。 / ]' y! \+ |1 ], n
<P>CDIS_MARKED 标注状态。</P>. {0 n9 r2 T* i+ {7 F  o
<P>表8 uItemState的类型值与含义</P>$ I8 D' \1 S% a
<P>lItemlParam 当前列表项的绑定数据 4 A, C( }7 Z1 W
<P>pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: " N* [4 w9 y7 a) b6 a7 w; ^- c
<P>当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:</P>
6 z! N; [; I. c: c  |% r7 C5 ~<P>类型值 含义 1 Z5 d; ]5 [' j. W: e8 Z; X
<P>CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。 3 V' r; |, D& [* u& H
<P>CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。
% d- ]7 ]" F) W( v4 O$ E! Z9 O: S3 C<P>CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。 $ c; k; E: _& y% E; m
<P>CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。</P>. v5 a( i% d  ^+ m2 \1 g
<P>表9 pResult的类型值与含义(一) : M3 w. n5 U& f: j1 b! p2 Q
<P>当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:</P>+ E4 a0 c& u6 I, ]6 D9 t3 J* {
<P>类型值 含义
5 c% P) g+ u4 N: w4 a2 I" b<P>CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。
/ @5 A2 S/ I* f0 ~1 |) @+ e<P>CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
, g, ]2 Q- m" c<P>CDRF_SKIPDEFAULT 系统不必再绘制该子项。</P>8 b# \( P) s8 e! ~0 Z4 }
<P>表10 pResult的类型值与含义(二)</P>
+ b( D4 m& X( I8 G" @<P>以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子:   p+ {7 K8 ~+ ^8 T' C/ d
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650317752.gif" border=0></P>
+ T2 t2 G# q4 U: S$ k1 w9 F<P>9 C* o9 |3 J% i
<P align=center>图12 利用NM_CUSTOMDRAW消息美化界面
1 S' q6 O0 t5 B1 }& u8 u3 M/ y! w9 a- C<P>对应代码如下: - b9 u  d* K1 A3 Z) x% q6 J
<P><TEXTAREA readOnly>void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
0 C, H( g  Q1 W8 d/ x{+ H% d3 G( w- R9 \
        //类型安全转换2 p( ]2 T  F0 u% W5 k' ~  ~* {
        NMLVCUSTOMDRAW* pLVCD = reinterpret_cast&lt;NMLVCUSTOMDRAW*&gt;(pNMHDR);2 F9 [- V6 I* ?! C
        *pResult = 0;
, b* R/ t" n- F        " |- |( m; O3 j) H: a- y/ E" m
        //指定列表项绘制前后发送消息$ E4 o4 Q5 R" Y7 h
        if(CDDS_PREPAINT == pLVCD-&gt;nmcd.dwDrawStage): M) f: n% M/ X3 [, U2 t; K
        {! }- H. u( M5 i5 d, P  w
                *pResult = CDRF_NOTIFYITEMDRAW;
6 u( m4 p6 G9 h! {: o- N        }) u3 E" [$ p* X
        else if(CDDS_ITEMPREPAINT == pLVCD-&gt;nmcd.dwDrawStage)& x- S3 U  W" F! L1 H3 r
        {( e4 ?* R; S% e
                //奇数行
: t$ V5 s; J# O' n' x' n                if(pLVCD-&gt;nmcd.dwItemSpec % 2)
6 j$ ~# A% M5 W& N1 A                        pLVCD-&gt;clrTextBk = RGB(255, 255, 128);  \4 J) M2 D* X/ O, e+ t3 \% I# L6 j
                //偶数行7 p) r- K/ l7 p) C' q8 _
                else
, R6 y9 A- V  @! A& A. K: U0 y                        pLVCD-&gt;clrTextBk = RGB(128, 255, 255);
: F; n( ?( ~- {9 P3 G; i! v6 s                //继续
/ J# v: D+ l* q7 }                *pResult = CDRF_DODEFAULT;
! V0 o+ f% |& a! p+ }4 O2 q        }, w( q4 [. t) e6 ]# o. m3 y
}
, H" {# P# r' u( M</TEXTAREA> / D8 c- f: d# |
<P>注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。
! P" D; |% k/ u/ t' q# ~<P>5 y+ m/ l$ B- K- n) C. w$ u2 \
<P>
, R$ I$ U1 `% [; }% ^<P><b>3.4 使用MFC类的虚函数机制</b>
/ d! F5 P3 S& X" W$ S; l<P>2 Q3 M' ?8 R: ]7 }
<P>( I$ E; M" j+ @4 u
<P>修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子: $ d7 a. e5 \" y. Y6 w
<P><TEXTAREA readOnly>void CView::OnPaint(); v2 i# X. z! |0 X! V4 v
{' f& H% M4 G' h7 B4 X3 P% L
        // standard paint routine
: i, G9 b3 m% o) s. [. E        CPaintDC dc(this);& W" u  G" n+ q; S
        OnPrepareDC(&amp;dc);
* T$ T7 ~- N& P) `2 }$ k1 Y        OnDraw(&amp;dc);
: y' U/ z, k2 j8 k8 K  ^; l" e}
1 u" {$ U& Z2 y  v8 u' Z  M2 A: e</TEXTAREA> - P; |+ t% d% M  i8 P& n/ ^
<P>这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。
$ ~9 C6 h$ a  p* V) G: y) k  j2 \<P>以下列出了与界面美化相关的虚函数,参数说明略去: / [9 ^' u" A. E
<P>CButton:rawItem
+ X! a7 i! i; ^) Y1 W  D3 t<P>CCheckListBox:rawItem 1 k9 F$ I6 H0 m, |9 q
<P>CComboBox:rawItem * x$ R9 p# @) p
<P>CHeaderCtrl:rawItem   Z5 D& s% ]+ ?9 i- r4 Y+ _
<P>CListBox:rawItem
7 a# f  P, d6 U2 _: S7 v0 Y/ a, \<P>CMenu:rawItem
. J' W( [/ S3 K% ~+ i3 Y# ?" c5 B' \<P>CStatusBar:rawItem $ C" n; t- L, L. b
<P>CStatusBarCtrl:rawItem & t+ Z  P% z% ]  X7 \, N* h
<P>CTabCtrl:rawItem</P>
- R& J  E) }1 ~% U8 |- r<P>virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct ); 7 V  z" h$ z- I7 a# A6 O& _
<P>Owner draw元素自绘函数 ' J( I7 R: [; u; X4 i) {7 r. F
<P>很显然,位图菜单都是通过这个DrawItem画出来的。限于篇幅,在此不再附以例程。 </P></DIV>
作者: xShandow    时间: 2004-10-8 16:22
不错。。。。
作者: sherryer    时间: 2010-3-3 20:33
顶顶顶顶顶顶。。。。。。。。。
作者: shuiqingchan    时间: 2010-5-13 19:58
很好的东西。值得深入学习啊!!!!!!!!!!!!!!!!!!!!!!!!!!
作者: shuiqingchan    时间: 2010-5-13 19:59
路还长的呢!但我需要继续走下去。。。。。。。。。。。。。。。。。。。。。
作者: shencs1983    时间: 2010-8-18 12:33
很好的东西。值得深入学习
6 {1 |2 u6 x- t: `$ ]! q( m




欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5