数学建模社区-数学中国

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

作者: huashi3483    时间: 2004-9-27 18:32
标题: VC之美化界面篇
<><IMG src="http://vcer.net/images/item.gif" align=top>关键词</P>界面美化 2 }, Q8 K5 T9 @; `
3 z) }; @  Q, e6 k
<><IMG src="http://vcer.net/images/item.gif" align=top>摘要</P>1 N6 }9 o# _9 }; u4 d7 b# v) j" E
<DIV class=vcerParagraph>
, w" P, Q1 S" m' x5 d<>本文专题讨论VC中的界面美化,适用于具有中等VC水平的读者。读者最好具有以下VC基础:
& I6 h9 l6 z, f5 w3 _<>1. 大致了解MFC框架的基本运作原理;
) C' B. ~; @5 `2 \: ~<>2. 熟悉Windows消息机制,熟悉MFC的消息映射和反射机制;
- D- R: i7 h* R5 y* ^<>3. 熟悉OOP理论和技术;
  F% Q; _, I8 L! G0 J0 R<>本文根据笔者多年的开发经验,并结合简单的例子一一展开,希望对读者有所帮助。 3 C! c) f: A  u4 @5 S: @' O

: v9 H: j. J2 J# J  M, z" S8 Q1 m. s</DIV>" J+ C6 I" b% I9 N) w

  t) A1 F* q: t$ ~9 G<><IMG src="http://vcer.net/images/item.gif" align=top>正文</P>
/ L7 F! X" k6 L% Z. {* f<DIV class=vcerParagraph>: p% s2 l5 I* O* y
<>1. 美化界面之开题篇</P>8 _7 C! H+ D, J3 r0 C7 T% e  I( ~
<>相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面: + I# @) \6 Q1 h5 q0 B  K. Z# [& [+ l
<>, W- F  S3 s* y
<>+ H! F" s0 h. q: B' f6 e
< align=center><IMG src="http://vcer.net/upload/2004/03/1046596474810.gif" border=0></P>7 _  ]. o& n, G. ^3 A# m$ ~
< align=center>  & k* Y  d+ G3 _' @& J" N
< align=center>图1 瑞星杀毒软件的精美界面</P>5 k  @. n, M& m/ A3 d9 t6 W8 E
<>程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。 $ \# {" }6 T9 _; E4 @% ]2 N) R/ q
<>“受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。 ' T2 z# b6 B& J  z/ o
<p>
& }- K! Y0 U/ }" q+ _/ b! ^<>2. 美化界面之基础篇</P>' c& c7 k0 S8 B2 s' q* U
<>美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……
2 @& `0 ]) a1 A) R( D1 M<>+ V/ G  H* @; V( V2 j) T
<><b>2.1 Windows下的绘图操作</b> ) ~% i/ H! z3 U3 k. j# V2 r
<>
9 E# V* m9 S; b; m+ l0 H! f<>熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等…… 8 G+ M* ^+ ?/ K, n; D6 H* s; P' O
<>Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。 0 G& w" G: |7 v
<>7 e/ j! g  B7 {" s
<><b>2.1.1 设备环境类</b>
! o" q( _& Q2 v& G+ w; k9 y<>9 T( _4 U$ C5 s/ y) }: J3 l- x
<>Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。 & Q# D7 I) Y1 `* A' h
<>MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括: 0 E! M7 B  z8 Q3 p' g: |
<>Drawing-Attribute Functions:绘图属性操作,如:设置透明模式
$ k* l0 R3 j& \8 b, c: h9 E<P>Mapping Functions:映射操作 5 n0 E, [6 \. q
<P>Coordinate Functions:坐标操作
7 \& C4 L. x9 I4 O& u. I( F9 D3 y<P>Clipping Functions:剪切操作 - s# y8 A; ]/ K4 o
<P>Line-Output Functions:画线操作 0 b/ c1 S- ^1 C6 X6 X1 X* ~  C
<P>Simple Drawing Functions:简单绘图操作,如:绘制矩形框 " \7 O8 V$ ]3 t4 k' q2 l8 Z
<P>Ellipse and Polygon Functions:椭圆/多边形操作   b$ m5 x7 y6 Q$ c+ G5 I. z& n  r
<P>Text Functions:文字输出操作 4 s7 m/ U3 X0 K6 K
<P>Printer Escape Functions:打印操作
9 m! {) |+ R: o) a5 G<P>Scrolling Functions:滚动操作</P>
2 q) D8 ?) q& q7 _<P>*Bitmap Functions:位图操作
/ w$ ]& q  X' @0 x. |" F: J3 v* `<P>*Region Functions:区域操作
7 @8 D" D9 ^! m- @% y7 b<P>*Font Functions:字体操作
( f1 z6 |* t$ ~5 @7 V/ F<P>*Color and Color Palette Functions:颜色/调色板操作</P>
% L- h* H1 e3 ?7 M& m<P>其中,标注*项会用到相应的图形对象类,参见2.1.2内容。 / W0 P' E  q. V/ r1 \/ V! F
<P><b></b>  ' p7 F* L0 m: D+ Q
<P><b>2.1.2 图形对象类</b>
! C: M1 Y4 N& d4 ^8 j. {: V<P>9 P0 J1 o. L3 a3 ^
<P>; n, ?2 e9 R# z, V% J
<P>设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。 ) O% q- H- |/ H7 J5 d- _4 T4 R2 o( `
<P>下面的表格列出了MFC的图形对象类:</P>
8 v# P  I+ s- B' J, Q7 }<P>MFC类 图形对象句柄 图形对象目的 & F/ A# s- Z- A& `4 O
<P>CBitmap HBITMAP 内存中的位图 3 h7 [7 Z" R: ~' Z( t1 D) Y
<P>CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式
  \5 F( p+ g3 j' C2 J: V* W8 K<P>CFont HFONT 字体特性—写文本时所使用的字体 2 X6 G8 d( I% t7 x6 ^
<P>CPalette HPALETTE 调色板颜色 # R9 G- p3 k( B9 e# h( J
<P>CPen HPEN 画笔特性—画轮廓时所使用的线的粗细 ' K- J# n0 f/ B9 k, q, q
<P>CRgn HRGN 区域特性—包括定义它的点 % t6 a" t) g0 t1 q- U, Q( ^
<P>表1 图形对象类和它们封装的句柄</P>; E9 J/ v0 w* V' S; G4 Y5 d
<P>使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:
2 W( K3 g9 ^# j4 Y) ?. r3 `<P>
# \8 o  Z2 ~) F<P align=center><IMG src="http://vcer.net/upload/2004/03/1046651213100.gif" border=0></P>
" B/ D5 E2 t  l7 K0 u$ _3 K/ `<P align=center> 图2 使用CDC绘制出的按钮</P>
. @: J( ?0 d  n. d6 f2 u<P>该画面通过以下代码自行绘制的假按钮: 0 |$ }, y! y$ X1 Q- S* w
<P><TEXTAREA readOnly>BOOL CUi1View:reCreateWindow(CREATESTRUCT&amp; cs)
0 q2 V" b; t& Z4 X/ S{
$ a* T4 p# Y  F' C, O6 L9 d3 v        //设置背景色* [; E8 l/ M- M& D4 A4 e
        //CBrush CUi1View::m_Back
) Z4 n! A1 X) I  k        m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));3 N  f. y' Y! E- N! `
5 l9 F& X; O' O: [0 a; a( f
        cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL);$ f7 H5 J" y" {9 B8 `4 E
        return CView:reCreateWindow(cs);
$ ^# m( ]  K5 i" ?. J; x' d7 o}. |( N) |: o' M2 ]  g! b% q
# x9 B6 R4 \7 }; q5 n
int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
4 B( b' k: A* o3 j5 z) w. _{5 O! [8 _& D( B6 I& Q' R6 h, g2 ]
        if (CView::OnCreate(lpCreateStruct) == -1)% s% f# C) W" j& e/ V
                return -1;
) |4 j' P- H/ ^5 y' Y+ L! M
& \  o+ b, \* f- L: E% ~        //创建字体6 N9 m% T7 j* U+ Q3 V0 V2 {  z
        //CFont CUi1View::m_Font3 J. @  Q" E/ C, Q
        m_Font.CreatePointFont(120, "Impact");
0 N( |% }* J6 G/ ~1 `       
: R: {, K/ j$ g$ M: N, m        return 0;5 ^6 A% x7 K1 c+ L
}* E0 z  I" U1 _
1 }1 y2 G/ b; b) F
void CUi1View::OnDraw(CDC* pDC)
+ P9 H9 A' ^5 W( H3 B! x6 A{
* `% i7 [, a3 g3 N$ E        //绘制按钮框架
8 E/ i3 C6 I# ~! p" |        pDC-&gt;DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);
9 V; L. r; I( |4 W
# n) d" f: D# Z, _% l% ~7 z        //输出文字
9 K5 V  J# a7 Z        pDC-&gt;SetBkMode(TRANSPARENT);  x- B4 S, K; N" P; a7 j/ T( F: Y. w
        pDC-&gt;TextOut(120, 120, "Hello, CFan!");7 y1 h) `1 h1 n6 V4 c2 n( h
}</TEXTAREA></P>4 U) D6 D  R0 v# t( ?4 t4 w8 C
<P>呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。 </P>' S: Y0 J' r4 T% k
<P><b>2.2 Windows的幕后绘图操作</b> </P>6 ~5 U# G5 X, Y2 |9 v# S' _
<P>在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。 4 Z% V! r: ?/ D( s* x
<P>有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。
) X0 n$ S, H( s+ s<P>所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框…… / B- I1 X3 q5 z; g4 B
<P>有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。 1 `7 j9 d1 T# j, f$ V/ }+ N" ^) I  }
<p>
& `( r7 j! `% t  A, [6 z% U<P>3. 美化界面之实现篇</P>
" {. c( Z! Y" M- [+ p! Q; P3 [<P>Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。
! X2 `2 b  T) _$ v0 j<P>
4 j6 E+ y0 W% o2 t1 F) B<P>
3 L  Z4 s. E& t/ f" T+ }& j) ?<P><b>3.1 美化界面的途径</b> " ]% F) ]  S  ?( {1 [  }! R3 {
<P>5 S# t  w/ p% E" W
<P>
/ C8 s; h( |- j7 c8 u3 x<P>如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括: " U& L& u7 ~' e. l) e( L
<P>1. 使用MFC类的既有函数,设定界面属性; + m, ]! |1 ~  {% g) K; U, F
<P>2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;
3 l" D4 ~5 J  w5 P" X" r7 \, w<P>3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为; # g9 S3 ?7 A. j; C' N
<P>一般来说,应用程序可以通过以下两种途径来实现以上的方法:
  Z; o+ [; P+ P8 t) J4 ~<P>1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息; 0 J/ e1 k1 z3 a: |  C% }5 _
<P>2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。 3 \7 W/ N1 \7 V/ P/ j, t  W* N
<P>对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:
6 j. ^1 @3 p0 i# ^<P>① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示: 8 t& Y' _  _4 {' Y& O8 O
<P>% s3 o6 d; F0 O2 Z9 s6 k
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046596487288.gif" border=0></P>
7 x. j# u6 @& H! Q8 O<P align=center> 图3 为按钮指定CXPButton类型</P>
; f- s4 a. Q& S/ ^5 Q# p0 S. q<P>②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法; 9 ^: G. ^' f/ R9 M
<P>以下的章节将综合地使用以上的方法,请读者朋友留心观察。
% }, Y& g: ]" K7 A2 U<P>4 S( V! x) ?$ i* s6 ]) n) Z4 h
<P><b></b>  
) ^& `) ?& e  k4 p; x: I$ Y3 A4 o<P><b>3.2 使用MFC类的既有函数</b> " w2 A. g  J9 ]
<P>* K0 y9 K0 ^/ ?7 Q+ T! O& l
<P>
* V' Z) e2 p( T' ~: F1 P<P>在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。 : z: D4 O9 {# T7 Y, a* j' s' T
<P>CWinApp::SetDialogBkColor
. t* q/ i% H/ b. z3 Y7 }<P>void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) ); $ m" [- y# T7 _5 p$ x" y+ k
<P>指定对话框的背景色和文本颜色。</P>) ?! s' n9 {/ a# o8 V
<P>CListCtrl::SetBkColor
* `# X6 ~3 ?+ h" [; `, i<P>CReBarCtrl::SetBkColor
2 M$ n# p3 r2 g1 h) N<P>CStatusBarCtrl::SetBkColor - E2 T# V) c: D2 s( C$ z
<P>CTreeCtrl::SetBkColor
- I% j2 X& F( K" y<P>COLORREF SetBkColor( COLORREF clr );
2 }4 j, ~  A8 G% V<P>设定背景色。</P>
+ w* z3 S0 T6 [& y0 v# B5 P/ E<P>CListCtrl::SetTextColor
; k  b$ r6 H8 D! b. @<P>CReBarCtrl::SetTextColor 7 t7 k+ x. O, j, J  r4 N
<P>CTreeCtrl::SetTextColor " k/ P/ s# N6 [& H' t# n
<P>COLORREF SetTextColor( COLORREF clr );
' H7 @9 l7 A# j6 P9 ?; y<P>设定文本颜色。</P>
2 n$ }3 [6 e( c4 ^/ F6 d! H/ k<P>CListCtrl::SetBkImage
8 V; `+ K3 @- e+ o<P>BOOL SetBkImage( LVBKIMAGE* plvbkImage );
3 |2 a3 W" B! \8 b5 H<P>BOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0); 8 j! A7 q+ G2 N4 _$ y
<P>BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 ); $ i! A/ U, {) _. H/ e
<P>设定列表控件的背景图片。</P>9 _! h  Y# k% c5 j( z
<P>CComboBoxEx::SetExtendedStyle
3 `" P* j6 y% M; F6 U<P>CListCtrl::SetExtendedStyle ' D3 i6 T% ], S" e- K$ F
<P>CTabCtrl::SetExtendedStyle ! G2 R0 B* Y3 V* d1 j& J0 z; j
<P>CToolBarCtrl::SetExtendedStyle
1 }' d3 @9 J* r<P>DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles ); 8 w7 E3 d4 R. L8 x& x$ X
<P>设置控件的扩展属性,例如:设置列表控件属性带有表格线。 7 c/ c. y- T+ ]/ r
<P>图4是个简单应用MFC类的既有函数来改善Windows界面的例子: 7 q% N' o2 _1 e9 l% q  B
<P>- |1 Q  Q& d$ ~2 I+ B- h
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650314708.gif" border=0></P>
, z/ P( Z2 v* }, \<P>
. X0 t8 \8 F* n" G<P align=center>图4 使用MFC类的既有函数美化界面</P>
5 R3 a* G1 @6 q0 n<P>相关实现代码如下:
; ?2 ]2 E2 ?8 _5 x) @<P><TEXTAREA readOnly>BOOL CUi2App::InitInstance(), M, \4 F3 D+ Z2 B
{
1 j8 V1 D! b2 T7 e2 ?' a        //…* s5 \+ C6 \4 k9 `5 B. ~
        //设置对话框背景色和字体颜色  m5 Y+ a5 i9 i8 I0 H
        SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
$ A! U" k8 C& p- ]8 @7 T0 F5 O        //…
$ p* d  W% T7 G, I5 t  R}
5 j, C2 G8 S) F+ A- I8 ^2 g# n4 e2 k' l9 ]4 _
BOOL CUi2Dlg::OnInitDialog()9 r3 B! L' Z# m8 B( i
{
" q- D2 M6 O+ U; |2 V        //…
% N( `. U- B1 R+ ]6 [( E4 o# n. D4 U        //设置列表控件属性带有表格线
1 v2 ?3 d! Y7 {' R: l) B        DWORD NewStyle = m_List.GetExtendedStyle();9 c. J! s# W& C2 ^" I3 {$ n
    NewStyle |= LVS_EX_GRIDLINES;
: g! b3 C9 o& am_List.SetExtendedStyle(NewStyle);
* ?  r- L* c4 z+ @) d' S, P1 W! N/ y
' j2 x6 L5 ]0 t        //设置列表控件字体颜色为红色) s& r" {" }5 {% _" I
        m_List.SetTextColor(RGB(255, 0, 0));/ M* e6 Z# t/ B1 k

2 X/ r7 d, i6 u. Q/ H; @7 ^        //填充数据
6 {$ ^/ n, s# K% n: y+ o) Z5 j        m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);
; t& O; ]" j$ I; R        m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);
/ d: J# f) p( ]. i+ S
) w! ]& }4 |' V% q        m_List.InsertItem(0, "5854165");
8 p; |( e; k  Y1 n% b3 k1 k$ g        m_List.SetItemText(0, 1, "白乔");
3 Q1 J& {  e. U5 ]+ v/ M( t& \5 i6 t8 L7 d3 |: T
        m_List.InsertItem(1, "6823864");
+ v, v# \! B6 e" L        m_List.SetItemText(1, 1, "Satan");
( O( f$ h' e# `& ~        //…7 Q$ f. k. V/ y1 n
}</TEXTAREA></P>* c& h6 @! x: W% I! }6 M0 R
<P>嗯,这样的界面还算不错吧? </P>4 U( q# I6 U8 g+ b+ N0 z; l
<P><b>3.3 使用Windows的消息机制 </b>
2 A1 [1 `0 l; \! S% S* q! {<P><b></b>  
5 _# V9 {/ n! W  i* k8 I<P>使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息: 3 K) K2 G, J8 i  l7 l- ]
<P>WM_PAINT 9 ]$ }' |" M3 ?% g; H/ I/ w
<P>WM_ERASEBKGND 1 T+ Y( }& V( a, r7 x
<P>WM_CTLCOLOR*
5 W9 `! m. p3 V, H) p/ q<P>WM_DRAWITEM*
( `7 q; f% E8 g) U  I; N<P>WM_MEASUREITEM* ' m/ G* i! M% c0 d) N
<P>NM_CUSTOMDRAW*
' k; `8 L7 O0 C+ n: [3 ]8 ]: ?2 N<P>注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。
$ w: B" o7 D2 Y<P>
9 g( B/ K% w* `<P>; ^3 G; \1 @! h" W% t3 U
<P><b>3.3.1 WM_PAINT </b>0 h1 F. g0 V, g# {! Z+ j6 l
<P><b></b>  
3 F0 Z; b7 l  q6 ]<P>WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。 2 {* h9 I1 o" V4 Y6 e
<P>可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下:
% ]% h- d0 n4 {# V8 Z, Y" \' R3 T  K<P>afx_msg void OnPaint(); 6 G' F1 x6 r, Y1 c4 [
<P>控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: 9 @' D' I! ]- w, J
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650335708.gif" border=0></P>8 [6 g7 @% J' e% ?
<P align=center>图5 利用WM_ PAINT消息美化界面
3 A( Y/ s, e4 T9 t+ u" j<P>实现代码也很简单: , j) \6 m2 U" W1 d1 S
<P><TEXTAREA readOnly>void CLazyStatic::OnPaint()
* v& |' b& N; W- d* |{
& W6 y4 l' U& i  A& `# J% Y* ?+ c        CPaintDC dc(this); // device context for painting2 S" k! e8 {* E" I  C$ ~
       
2 Y2 R( Y  P1 E) {7 F        //什么都不输出,仅仅画一个矩形框$ \. y; p& q" h1 j9 K  X& ^
        CRect rc;
2 M" R* {- v  f5 f0 v4 l        GetClientRect(&amp;rc);
  F3 ]6 A5 V1 ^  B# J        dc.Rectangle(rc);        % o7 Q  X, ^2 \& R, S2 m/ g
}
' G' N# _# G/ w6 z5 c' d</TEXTAREA>   [. c% `* h4 E% V
<P>哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。 3 E" @( z9 l  Z, Y, j3 w
<P>
* `7 o  z$ H4 i7 S7 `# f<P>
4 w, O0 F4 F+ L  I/ I<P><b>3.3.2 WM_ERASEBKGND </b>9 t7 G0 j/ c) k0 {/ B9 }
<P><b></b>  3 a9 X4 L+ i- @8 V' ?( V' @% l" L: E
<P>Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。 " F  o' |# L) j
<P>可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:
% |4 y: v' V% ^" g/ }<P>afx_msg BOOL OnEraseBkgnd( CDC* pDC );
$ c" B5 w! `9 S" w<P>返回值: ! F9 M9 E1 R2 S
<P>指定背景是否已清除,如果为FALSE,系统将自动清除
/ E+ l* f; u9 T0 A( \<P>参数:
. n8 i3 z  I  ]3 D# n1 Q1 m<P>pDC指定了绘制操作所使用的设备环境。
! x/ \" U" J: j0 T<P>图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景:
4 v9 _  f* _: m0 G<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650328908.gif" border=0></P>
7 n8 B% Q: [. s% t" f( G<P>
2 ?. M; n9 e' _+ @& M<P align=center>图6 利用WM_ ERASEBKGND消息美化界面</P>
3 h+ u; R$ A7 C<P>实现代码也很简单:
4 W: v, [1 P" @& ]5 H<P><TEXTAREA readOnly>BOOL CUi4Dlg::OnInitDialog()9 B( j* D" m. K$ x3 k3 ^) A2 A
{
* x. T, u' O- w8 H! `. }//…
* N9 o, h& @0 U8 @% s4 k- g4 l        //加载位图5 D, U: A4 `+ H5 J4 `9 }7 o0 u0 W
        //CBitmap m_Back;
7 _9 X& H( T  d        m_Back.LoadBitmap(IDB_BACK);
2 H3 t6 r; Z9 `. O) s        //…
  p* W4 Z8 M3 q: m1 a5 `- W4 h. ?}$ R' f6 l2 o4 l/ o0 i( D) N

' T3 G; ^" e: e% q7 _3 T9 ?, gBOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC)
* f" q% y& k9 D1 f{
4 v6 g$ E! Z0 G$ |        CDC dc;5 U: m7 G' c; l
        dc.CreateCompatibleDC(pDC);: z. r  H5 Q' T; K7 O
        dc.SelectObject(&amp;m_Back);( r& T; g$ w, o5 v; o" o- a# ]% ]; N

- ~5 Y- g8 j! g2 ^! E' W% ?# @        //获取BITMAP对象
* e" F1 o+ L' K        BITMAP hb;
! ?' W% T% F7 ^        m_Back.GetBitmap(&amp;hb);6 A6 E9 I, `/ V. `+ _

+ ?1 @) f0 w6 B) d        //获取窗口大小1 u8 q: [  ?: M& n2 _$ A3 @
        CRect rt;' @, Y( d% P/ i& U! V# O/ c
        GetClientRect(&amp;rt);
7 l4 d) w" O- }) K3 X        //显示位图1 m# u3 h6 P% ?) L- k) U/ D
        pDC-&gt;StretchBlt(0, 0, rt.Width(), rt.Height(),( ]6 h# q% t8 @- p6 \
                &amp;dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);
' j' C# C! R3 A* n$ @- }6 L$ u
0 u* s2 u- h0 o, K) a" m        return TRUE;) Z) |# Z) z) h( H
}
- R6 T! I- _7 N+ s+ i! T) r) T. Q9 ?
0 d6 A2 q' X- A" E8 j) g; y1 QHBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
! g5 m& `3 n4 {0 X{
/ r9 v! a3 Q, b  I" `) I7 j6 d        //设置透明背景模式
3 S* N& {" Y' `6 Z) o( B        pDC-&gt;SetBkMode(TRANSPARENT);# [& x+ V2 D. I, i9 w4 R# F
        //设置背景刷子为空, b- {) I0 `) L! ^/ W/ i" }* q
        return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);
5 ^7 Y: [, [0 H, Y' c3 f) Z6 @, ]6 w5 s( \}
* g0 H- s" O+ \, p; L" x) a</TEXTAREA>
" o' m# @* }! D7 Q<P>同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。   ]1 u: E% y0 Q$ y: u+ p7 v
<P>5 P. b9 K4 d* y" o# G$ Y3 B9 t
<P>& `7 d6 H0 V( e7 U; R
<P><b>3.3.3 WM_CTLCOLOR </b>- B$ c  T" }$ R8 o/ D8 C- d! M
<P><b></b>  
; K8 u& }/ T6 o<P>在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。
- t) C" x% T! W) f* `<P>WM_CTLCOLOR的映射函数原型如下: 0 t" D( _. ~3 y' E! m! q0 }
<P>afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );</P>
7 Y+ B# y* p+ H4 Z7 c: c<P>返回值:
% o7 K: a, h3 `% N<P>用以指定背景的刷子
; o" E( b' T; v0 t2 [, C<P>参数:   [- `/ h& n3 U
<P>pDC指定了绘制操作所使用的设备环境。 ! R: J: P- b4 U( G) b
<P>pWnd 控件指针
1 K& R1 |2 i# ~3 a<P>nCtlColor 指定控件类型,其取值如表2所示:</P>3 Q$ ^; A0 G) u+ V
<P>类型值 含义 & K4 o7 G6 d0 L& ~% E4 `
<P>CTLCOLOR_BTN 按钮控件
( _, H1 o+ C8 d) B<P>CTLCOLOR_DLG 对话框 7 @/ s2 \" U" ]6 F) i& ]$ s! D
<P>CTLCOLOR_EDIT  编辑控件
$ d6 B2 M4 i+ j/ X* U6 h<P>CTLCOLOR_LISTBOX  列表框 4 G- H, t0 R3 w
<P>CTLCOLOR_MSGBOX  消息框 ; }. I7 g6 c& \  d" _/ `3 q
<P>CTLCOLOR_SCROLLBAR 滚动条
6 s- U% I2 V* q, ?) q7 O/ \* q<P>CTLCOLOR_STATIC 静态控件
! p% [9 X# ]4 l+ G/ C8 U# c$ \<P>表2 nCtlColor的类型值与含义</P>: G, b% I& Q1 W, R9 s; d
<P>作为一个简单的例子,观察以下的代码:
" O, o* j' u: y' _. P' e) D; ~<P><TEXTAREA readOnly>BOOL CUi5Dlg::OnInitDialog()8 |1 P1 A5 p+ D- r* e
{
' I/ ?) O" Y% B1 ^4 b9 q6 r  F: r        //…
* {, n& D( o6 q1 k  k( c1 c5 f        //创建字体/ A, D9 z0 }2 ?
        //CFont CUi1View::m_Font1, CUi1View::m_Font2
/ w* Q7 f- B0 B' m        m_Font1.CreatePointFont(120, "Impact");
$ I/ E: h) i0 }+ L- }$ H- o        m_Font3.CreatePointFont(120, "Arial");
( p5 |* j" v$ D9 k8 Y8 e( \4 r       
( E/ n8 l+ E- ~# o) w- s$ H        return TRUE;  // return TRUE  unless you set the focus to a control
" K4 g3 D6 X: B; W- B}
1 j$ S/ q. P( j2 X1 S$ `) B  V# q. i' J
HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
* u6 x( e+ J$ m9 z  f& d# N" U4 B{
  i& K$ b) x$ J+ v3 T6 |8 I        HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
- H0 H4 n6 ]; J+ @        if(nCtlColor == CTLCOLOR_STATIC)
: u+ L; @$ {8 U& }        {
! Z6 q4 U% N; K) L                //区分静态控件
% I' R: i  J$ ~$ i5 p' w; r( g                switch(pWnd-&gt;GetDlgCtrlID())+ x: E* h! r/ E0 T9 B: {; ], w
                {
) E9 C3 U' ?* s8 h2 t% |8 r% I3 G                        case IDC_STATIC1:# ^+ R8 m2 V& P6 n9 Y" _2 A
                        {+ N# Z0 t1 n0 A* ?/ Q+ B
                                pDC-&gt;SelectObject(&amp;m_Font1);! _# W9 X1 I: v- R
                                pDC-&gt;SetTextColor(RGB(0, 0, 255));, M* E% g" r( N' L. @+ ^1 z6 J- @( h
                                break;- j1 ?# j  B/ \( O# w& O
                        }# c; T, p+ ]6 }' m
                        case IDC_STATIC2:
% w+ S3 `) y: i7 s' Z                        {
# L; K+ T4 O3 r4 i( a  o                                pDC-&gt;SelectObject(&amp;m_Font2);& X+ y. d5 O- Z9 j& B' }4 S
                                pDC-&gt;SetTextColor(RGB(255, 0, 0));
  @1 l# r' q  t8 |! W# Y- r                                break;( }3 C9 A  w0 n: w% r$ q
                        }8 J- ]9 e! i2 g/ S2 C
                }  b% g$ A. {$ j; Z
        }, \: Q5 O3 O1 {7 m/ r- V. c2 W
2 e8 Q/ o% O0 M, x2 n& Z" J0 \8 N
        return hbr;. B" z+ P" v  h" ]4 b& E1 z
}
, ], m) [1 q6 K% @</TEXTAREA> 9 G% Z* k7 I7 Z" {* t) p+ t
<P>生成的界面如下:
! z2 B2 v' X. N8 X<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650321578.gif" border=0></P>
" Y0 Y/ O9 }- C7 d; _<P align=center> 图7 利用WM_CTLCOLOR消息美化界面 </P>
. X, T" m3 l  p( F6 u* d" L& ~<P><b>3.3.4 WM_DRAWITEM </b>3 h& {; Z/ s( t. m8 Z' N& s! w
<P><b></b>  
9 D- \- w) h9 `7 y! a+ {. `<P>OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。
1 T3 Q% z7 a& ^<P>当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。 & R8 h4 K: i: g) ~0 S% ]
<P>WM_DRAWITEM的映射函数原型如下: 4 d' w5 \' g/ ~/ w
<P>afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );</P>7 h4 E' i# @) I" Q9 F
<P>参数:
  R. f) |0 W! Q! X/ |4 {<P>nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0
& [+ v! Y3 i- q) k<P>lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下:
. X& M! Z' \! V: O0 z8 S<P><TEXTAREA readOnly>typedef struct tagDRAWITEMSTRUCT3 L& P! i$ L; V, O; s5 v2 G
{
8 m" Q7 n: `. ?  L) y7 J, W  q    UINT   CtlType;
  c9 G' [" P2 e) G* q0 ~    UINT   CtlID; 0 |: j! V; `9 L+ W" h- D
    UINT   itemID;8 u1 j5 t( \( q) K& J/ k
    UINT   itemAction;
3 D/ ~$ _5 F) J) D$ c    UINT   itemState;
# [6 e' ], [. L) ?    HWND   hwndItem;% X- E  B' i# y4 c6 Q+ u  B8 h+ d/ ^
    HDC    hDC;
9 K, J# s/ K; L4 d7 w8 [5 W2 ]    RECT   rcItem;* |: \" n5 t+ V2 p  V4 {" L
    DWORD  itemData;, d5 m# u, ^% N0 @- p5 l
}DRAWITEMSTRUCT;
. S! S7 O) L$ I6 q  {) d</TEXTAREA>
" t2 j5 p$ Q% Y/ M7 J+ G/ g<P>CtlType指定了控件的类型,其取值如表3所示: 1 A* q2 Z# X9 c9 i1 p
<P>类型值 含义
* k2 V( }; w' R$ z! o/ c1 G<P>ODT_BUTTON 按钮控件 5 D: ^9 g5 u* Y
<P>ODT_COMBOBOX 组合框控件 - G& B9 M& n; |9 N/ ~2 I
<P>ODT_LISTBOX 列表框控件 # H4 y: d+ V* l* S, }( x5 j6 x& c
<P>ODT_LISTVIEW 列表视图
/ P/ M! S; ~! Y$ D3 k<P>ODT_MENU 菜单项 8 `0 z6 q, W( R7 L& ?
<P>ODT_STATIC 静态文本控件 . ?; m# l! F6 v
<P>ODT_TAB Tab控件 ) A% @- q: }& {0 h: M
<P>表3 CtlType的类型值与含义</P>, k4 e/ {; X1 d% A5 [$ J
<P>CtlID 指定自绘控件的ID值,该成员不适用于菜单项 ; A6 A0 b% z! D( ^4 z3 z& H3 F
<P>itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。
- f; X$ c5 Z4 e8 R0 U9 t<P>itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:</P>
) Q2 \7 Z; [) i& Z; \<P>类型值 含义 & m" M1 A  ?' Y% h' t( Y
<P>ODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。 & I6 [( Q* M+ v- v) U5 d2 U8 C
<P>ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。
( k! C5 F, o& r. t, R<P>ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。
# C' E$ @$ {; m1 m<P>表4 itemAction的类型值与含义</P>, Q3 w1 p+ m; w/ H  G
<P>itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:</P>
9 D$ F( z7 o: ], D<P>类型值 含义 + i$ }3 |; A6 \/ e4 S' h+ m( }
<P>ODS_CHECKED 标记状态,仅适用于菜单项。
3 O5 ~8 x9 X- f' U2 I- I2 o<P>ODS_DEFAULT 默认状态。
7 S: {' O2 u; n5 a9 ^! T" s7 I( w<P>ODS_DISABLED 禁止状态。
7 f7 Z1 c% c* Z1 }0 X7 _<P>ODS_FOCUS 焦点状态。 0 N( G$ L% S4 j- i8 r# U
<P>ODS_GRAYED 灰化状态,仅适用于菜单项。 : X" L3 B+ i3 R% p/ T4 {
<P>ODS_SELECTED 选中状态。
% p6 w. d- [3 {3 }<P>ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。 , d: w- g1 w) V3 a4 [
<P>ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 4 V7 f$ U' x9 t4 G/ N0 _3 s, U
<P>ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。 5 X* E: g4 Y* j$ J. s8 B$ C! g  f
<P>ODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。
9 b$ l! t6 R; i: P- {( s<P>ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。
* p4 y) g/ S4 f# n$ H<P>表5 itemState的类型值与含义</P>
' b5 K5 c- B) J) b<P>hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。 7 J" B4 U  O( b7 y/ Y
<P>hDC 指定了绘制操作所使用的设备环境。
0 Z& O$ j1 L# ]<P>rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。
  N# T0 Z9 e1 E2 S+ {6 i<P>itemData ! n3 W) ?% I( |" p/ J% C' [
<P>对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
  A* S3 W/ }8 L* f2 o6 n" ?$ ^. K<P>对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 ' ]  w. m' ?) Q  {: V2 y
<P>如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。
$ V7 g8 u# V  ?( a<P>图5是个相应的例子,它修改了按钮的界面: ( u8 K4 V" }, P7 [& ?2 e
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650324712.gif" border=0></P>
- b4 I& p- ?! n, N7 e* W9 r<P>3 W1 a2 R& k6 ~0 P, E3 C& O5 U
<P align=center>图8 利用WM_DRAWITEM消息美化界面</P>2 ^# z$ x5 Z1 }+ L
<P>实现代码如下: , s; S' h8 {% R& h6 J- p2 L
<P><TEXTAREA readOnly>BOOL CUi6Dlg::OnInitDialog()
+ X# T2 z+ P- x{* d0 s9 b* L9 \; ?9 `& F$ b" x
        //…
7 B( @2 \, L8 A        //创建字体
# B& o6 M( ?/ \, |' {' }0 I. f- D        //CFont CUi1View::m_Font
% z, m% n1 q0 ^! R* G0 f        m_Font.CreatePointFont(120, "Impact");
: P. N* U' z0 R/ f3 c: H        //…
3 }7 q6 ^5 q4 ~4 y4 L- Y}
2 |. X9 P" o% S2 g1 k5 `1 q. ^3 l$ @6 x
void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
' Y( L+ Y3 N' Z/ Z% H$ `9 ~{
8 _3 t( O  m& x( s& e  M        if(nIDCtl == IDC_HELLO_CFAN)
; `$ ^. l* [" u1 l" x        {
6 s! z# [4 v2 N2 {6 r# f                //绘制按钮框架
. x' u: h* g$ G5 J; q6 L, j, K9 c4 {( x/ N. i$ l: y6 Q
                UINT uStyle = DFCS_BUTTONPUSH;* d5 ?3 I+ z+ E' l7 A
                //是否按下去了?
! k8 [! q: j% E; b3 d1 d# H                if (lpDrawItemStruct-&gt;itemState &amp; ODS_SELECTED)
* Q7 g/ c# t/ y& u                        uStyle |= DFCS_PUSHED;# H3 F9 {; o  P* g) v6 {

* L0 F# |8 `# a5 f% w! ]                CDC dc;
  |+ T" x5 R6 e, W' l                dc.Attach(lpDrawItemStruct-&gt;hDC);
; Q* x7 r) k7 g; W                dc.DrawFrameControl(&amp;lpDrawItemStruct-&gt;rcItem, DFC_BUTTON, uStyle);% ?0 A1 ]! l( W# ~8 p
1 p" F5 j2 q+ {$ X) W% Y2 [
                //输出文字
. A6 B( D, R. Z7 P* H                dc.SelectObject(&amp;m_Font);
: f# U5 E! q' {/ j                dc.SetTextColor(RGB(0, 0, 255));) u8 C! k2 w2 Q. l- ?, Z
                dc.SetBkMode(TRANSPARENT);* j& ~6 o3 a  g8 }1 t) W

8 D. a$ e$ K* e4 T                CString sText;+ P5 `7 M1 L/ O( r  B2 p1 z5 h% I. g$ f
                m_HelloCFan.GetWindowText(sText);
, E' \( k; l0 @. ?+ U                dc.TextOut(lpDrawItemStruct-&gt;rcItem.left + 20, lpDrawItemStruct-&gt;rcItem.top + 20, sText);8 w4 P, Q" h2 o9 ^
! ]; e/ `2 Y, Y& X! X
                //是否得到焦点: q, }. q  ]  x1 J1 t
                if(lpDrawItemStruct-&gt;itemState &amp; ODS_FOCUS)' q* u# x  T8 S+ H
                {
( H+ d7 x6 D+ s                        //画虚框/ p6 C: A' M2 D  _2 W
                        CRect rtFocus = lpDrawItemStruct-&gt;rcItem;
1 O4 r3 i" D- z( }4 M7 {                        rtFocus.DeflateRect(3, 3);
% P9 M1 b% t. c. L1 P/ s+ _                        dc.DrawFocusRect(&amp;rtFocus);
0 w! O. }8 ^: }& i* m/ ~7 E                }
% I, k$ r/ z7 L% H+ c% I' E* ?# u( i0 E/ J! j! l
                return;
( b$ g* b& n; }3 W. k        }
( W6 @* I5 ]! z        CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);. Y. B) J9 r  b+ Q" I: h6 G
}
! Z7 F( k- N/ a1 Z! W* D; `</TEXTAREA> 0 r% Y8 B! V  B. e! h1 r! c- r
<P>别忘了标记Owner draw属性:
1 h# Q$ N. Q* E8 @( v9 F7 u<P align=center><IMG src="http://vcer.net/upload/2004/03/1046596492605.gif" border=0></P>
' }9 y2 a3 G' x. q9 h3 N- Y<P align=center> 图9 指定按钮的Owner draw属性</P>) d4 e6 k+ |; O/ a* X$ L
<P>值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton:rawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。
/ {* ?' Q7 H" p5 r$ @<P>1 R. _3 N; j2 P3 H3 y2 W3 w
<P>& |- E0 I/ e9 I# ?
<P><b>3.3.5 WM_MEASUREITEM</b>
. L$ J3 v% j/ b<P>
  z$ }% ~, ^# h; ]$ t<P>+ t; r8 d! ?  {: O$ ~
<P>仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
$ R( d* d4 G0 |7 L: E( r<P>WM_DRAWITEM的映射函数原型如下:
; V; W' G  ]! h3 ?<P>afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct ); 6 @. h/ h4 S& q* v! ?! U; F0 `: ~
<P>nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 & r0 q- }  ?, t2 Z" F
<P>lpMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下:
! P2 W% ^3 E) M* E! W6 b. l<P><TEXTAREA readOnly>typedef struct tagMEASUREITEMSTRUCT" G' D; P1 I0 f5 a
{
  D! @# b3 q8 f: t4 ]  k: D* k) q    UINT   CtlType;
; b# k, Y3 s7 v2 h    UINT   CtlID;
5 U0 @7 _/ }3 ^, j) {6 V& _    UINT   itemID;3 U. u/ K" H6 z/ C" v* r9 d
    UINT   itemWidth;$ m$ S: h& ~+ G( v/ o, {9 Z
    UINT   itemHeight;2 b+ b- x7 h) C0 G/ M
    DWORD  itemData& u* u/ H! c  E2 h% f9 @+ ?
} MEASUREITEMSTRUCT;' Q* s5 M) W# |& Z/ c; o
</TEXTAREA>
5 |0 M; c2 Q+ o4 S0 F4 n# p<P>CtlType指定了控件的类型,其取值如表6所示:
% I5 _9 L: F% Y3 ~3 `<P>类型值 含义
+ R) r, T2 s; U8 J2 `<P>ODT_COMBOBOX 组合框控件
( k' h9 @- D! [9 V<P>ODT_LISTBOX 列表框控件 2 p3 y9 P* @4 L$ k
<P>ODT_MENU 菜单项
6 ^7 c; L5 p5 p* B<P>表6 CtlType的类型值与含义</P>
/ ~5 Y" V: g5 a3 Q9 Y<P>CtlID 指定自绘控件的ID值,该成员不适用于菜单项 + Q* {9 ^% a. ^
<P>itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。
* q" E! _8 G" b( Y<P>itemWidth 指定菜单项的宽度 ( p! c( W9 \1 N" m( W
<P>itemHeight指定菜单项或者列表框中某项的的高度,最大值为255 2 z* d9 X" k; P; \; [7 N  C* e
<P>itemData
% W; E4 s' a( T<P>对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
: D& w7 n  Q; I2 ]<P>对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 ; E9 {, P* A$ R
<P>图示出了OnMeasureItem的效果: 6 s* o. m$ F: P# u- g6 V6 f- f, j9 L
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650332513.gif" border=0></P>
. @) N8 m+ ^1 g( Z: ?0 x' l' s<P align=center> 图10 利用WM_MEASUREITEM消息美化界面</P>) ^* o5 o+ a4 b2 q
<P>相应的OnMeasureItem()实现如下: 0 F6 k7 ~- K- ?  x4 v, A- O+ q
<P><TEXTAREA readOnly>void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) & i( X0 d) d2 a" n/ Q' H0 o
{- W+ S6 V6 f8 K' x' [9 v
        if(nIDCtl == IDC_COLOR_PICKER). E7 L" E- q# Z8 c
        {
# H$ a/ n+ y, k! E                //设定高度为30
$ F/ }7 o6 O% e- R+ o0 j3 l1 Q                lpMeasureItemStruct-&gt;itemHeight = 30;
' n# o# a/ s! ~                return;# V# g0 B6 W- g2 Z: q
        }6 r" x& z9 K* R0 X& Y
        CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
( V/ A) m( g& X}
0 Z5 z$ i6 y2 u7 I9 ^0 U</TEXTAREA>
7 q! Z0 q# g. e( S" `<P>同样别忘了指定列表框的Owner draw属性: # o* W% O4 c# N2 k" h- C
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046596451727.gif" border=0></P># a7 s' o& F% v7 v% A
<P>1 j! v7 I1 y8 k5 j. v8 G
<P align=center>图11 指定下拉框的Owner draw属性 * p0 S( Z: Y$ J& T/ j7 J
<P align=center>  
7 `# w: h- j' `0 U<P><b>3.3.6 NM_CUSTOMDRAW</b>
" P; @8 f; b" ^<P>! w: V1 d5 v: C+ d- p7 u* {
<P>2 w# m1 O% ~5 U8 C6 S  I- _0 H- ?8 `
<P>大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。
( f( U6 w' J$ [; {<P>可以反射NM_CUSTOMDRAW消息,如: * L9 y- |1 ~; d/ S. k" w
<P>ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw) : L7 n1 ?/ n* |5 k4 Q5 V
<P>afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); . }7 d! T, R: R
<P>参数: 6 {, E  i3 ~" [6 f6 R9 u6 R& `( m
<P>pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下: ' }) q; b" o  [  m* j* |; ?
<P><TEXTAREA readOnly>typedef struct tagNMHDR/ i* O8 v" P0 a" F& `% j
{ : [( K: ]( |$ d7 ~# u
    HWND hwndFrom;
& p: Q8 a: x3 O/ u    UINT idFrom;   r0 V+ ^8 O6 q2 C% o$ T* y
    UINT code; : r" i3 ]7 Q% w5 \2 N# F- u  g$ l! {
} NMHDR;9 v5 S- q3 X- d$ M' c
</TEXTAREA> + i0 r" k7 N  F6 q; s
<P>其中: 1 q5 m) B0 j% Q0 F
<P>hwndFrom 发送方控件的窗口句柄 3 A+ C7 N/ o, I3 U! t  g3 z5 E3 v5 y
<P>idFrom 发送方控件的ID ' D4 Z0 O$ d: r" |4 S
<P>code 通知代码 # B2 ]0 O* y* A" I4 G
<P>对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下: 2 _, A" j8 \1 P
<P><TEXTAREA readOnly>typedef struct tagNMCUSTOMDRAWINFO; V1 @0 w2 e8 O* U9 Z5 T
{5 l( {/ F2 l+ K7 e
    NMHDR  hdr;0 H: h" h! P" _0 V
    DWORD  dwDrawStage;% m6 O4 N3 {* W* s" O, G
    HDC    hdc;
/ S; d* i2 P9 Q' U3 D# o) E    RECT   rc;6 S6 b0 W/ U& _. T! H/ F# n+ _
    DWORD  dwItemSpec;
0 `: E& ?/ y4 k+ x7 `& C0 J    UINT   uItemState;
& A' `6 i3 c5 i( {7 r    LPARAM lItemlParam;
/ T- C! y+ W) ]$ \6 [1 _% F} NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
# i  [0 K. o( X- M; s$ r</TEXTAREA>
8 e5 D; m! @, s; ^% t% P6 Q: W/ ^<P>hdr NMHDR对象 6 i8 J# l; e2 X& h2 B) K# ~
<P>dwDrawStage 当前绘制状态,其取值如表7所示:</P>
! W$ _0 ~( ]5 q0 l1 @! T1 m<P>类型值 含义
7 \) _; U3 Y5 e/ G5 m3 o; B1 |<P>CDDS_POSTERASE 擦除循环结束   ?8 E* p; D- F; }* U
<P>CDDS_POSTPAINT 绘制循环结束
- h  b. L( f* ^<P>CDDS_PREERASE 准备开始擦除循环
/ D# ~  T+ K8 j9 U: M<P>CDDS_PREPAINT 准备开始绘制循环 1 n5 D: k6 i) N# [( f" D6 `  {5 U7 V
<P>CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 ! x0 L0 j$ u+ L
<P>CDDS_ITEMPOSTERASE 列表项擦除结束 - i% U- O! R6 K0 S$ k( w
<P>CDDS_ITEMPOSTPAINT 列表项绘制结束 3 x5 S: ?( e7 @! V+ e
<P>CDDS_ITEMPREERASE 准备开始列表项擦除
$ @- h. E. V8 f0 d$ c( y) q' {/ N<P>CDDS_ITEMPREPAINT 准备开始列表项绘制
; u2 a  ?" a0 |  O# l( V% q<P>CDDS_SUBITEM 指定列表子项</P>
6 G' G8 h: N4 R' R<P>表7 dwDrawStage的类型值与含义</P>6 }( b8 Q% K4 m& ^' K: u
<P>hdc指定了绘制操作所使用的设备环境。
, `6 d/ P7 w6 Y+ [* J<P>rc指定了将被绘制的矩形区域。
+ Y7 p) w% @7 c4 T$ J( R9 b<P>dwItemSpec 列表项的索引 7 x% ]: e) J9 g5 |8 k$ D
<P>uItemState 当前列表项的状态,其取值如表8所示:</P>
7 i" T4 N' M4 p; _: D<P>类型值 含义
2 L& a2 v; e; @, v$ B<P>CDIS_CHECKED 标记状态。 + c. u4 J1 ~7 C' O
<P>CDIS_DEFAULT 默认状态。
& X+ @0 ]# r+ b  n& g<P>CDIS_DISABLED 禁止状态。
4 Y) ^* t8 ~. N8 a<P>CDIS_FOCUS 焦点状态。
+ q, c% I' ]+ `% I5 M<P>CDIS_GRAYED 灰化状态。
# P- m7 a. J6 }/ o<P>CDIS_SELECTED 选中状态。 5 d% h; [8 ]* Z$ Y$ C
<P>CDIS_HOTLIGHT 热点状态。
8 k+ k# L8 |: {4 m* I<P>CDIS_INDETERMINATE 不定状态。 $ [, @3 N# m% s+ V/ V
<P>CDIS_MARKED 标注状态。</P>* t$ _" s2 I, L
<P>表8 uItemState的类型值与含义</P>6 J, ~: q+ X# W
<P>lItemlParam 当前列表项的绑定数据 # Q: M# m3 R! [* H- ?
<P>pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: 3 n% B4 G/ W, t7 ~( `- ]. c6 B
<P>当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:</P>
% x. Z9 j* m* v2 \<P>类型值 含义 ; J8 v/ Y" {/ ]9 ^' c8 h6 [
<P>CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。
( X4 n) _4 w& u0 K4 {9 L<P>CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。 - v/ s7 v* b  p) M/ S9 i, ^9 s: a
<P>CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。
% u0 t' K$ S& J<P>CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。</P>2 K. m, O0 r8 O' |
<P>表9 pResult的类型值与含义(一) ) l6 V# m8 q' O8 h
<P>当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:</P>
7 T- z( c% J9 g<P>类型值 含义 9 d" l8 P! l7 z* P1 y' m
<P>CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。
" v% s; o# ~5 M+ [, p<P>CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
, Z& [  P$ Z6 R; a9 H0 |8 e' Y5 A<P>CDRF_SKIPDEFAULT 系统不必再绘制该子项。</P>5 e* [8 B3 \- @
<P>表10 pResult的类型值与含义(二)</P>+ n, P/ F( b) r8 _/ ]0 i
<P>以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子:
% D; P2 D. i) D  n* @<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650317752.gif" border=0></P>! C# T/ I0 E% V5 m% b7 d
<P>
; b# f" A; Y. P' _- N7 n<P align=center>图12 利用NM_CUSTOMDRAW消息美化界面 * j2 U: P# u% f2 m1 I' O/ c
<P>对应代码如下:   U5 _0 p0 S1 O3 }" r, \8 U
<P><TEXTAREA readOnly>void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)7 N9 O& e& l! J7 d
{
8 f; j: ?( \- o+ m& G. J        //类型安全转换0 p1 I8 v- r8 E( r$ l" N* w% T
        NMLVCUSTOMDRAW* pLVCD = reinterpret_cast&lt;NMLVCUSTOMDRAW*&gt;(pNMHDR);( V4 s1 t7 A0 X3 C" y2 ?4 B2 v; N
        *pResult = 0;8 Z; S$ r! H; M1 F( q; |5 E. z
       
- W9 C9 x0 x9 e: Z5 C/ N5 M        //指定列表项绘制前后发送消息+ f0 W7 R! m$ Z, o5 \
        if(CDDS_PREPAINT == pLVCD-&gt;nmcd.dwDrawStage)4 R# a9 x1 h6 x) @! x# G* _0 @
        {* y: `+ y# W/ c' T% c
                *pResult = CDRF_NOTIFYITEMDRAW;. ^$ E5 X6 b) C$ d/ d
        }
4 [  X1 I: v0 j) U        else if(CDDS_ITEMPREPAINT == pLVCD-&gt;nmcd.dwDrawStage)
: J% Q) g2 U# F! v; J% Y/ @. F8 ?+ u        {
) S" o5 o/ \- Y9 C' q! W                //奇数行
; r2 u. Y0 D( a) L7 J& n8 _) F                if(pLVCD-&gt;nmcd.dwItemSpec % 2)
. m* f) z2 \$ o6 Z                        pLVCD-&gt;clrTextBk = RGB(255, 255, 128);
' ~7 L# Y/ Y3 f$ l                //偶数行
; d& ]9 s8 P, N# D" `* ~, V% g                else
! B- H5 k4 e/ g* {+ ~. w. z6 P                        pLVCD-&gt;clrTextBk = RGB(128, 255, 255);
  C7 c2 Z9 c. v5 S' z7 X                //继续) s: r; K- e* P: l' t
                *pResult = CDRF_DODEFAULT;) t: _2 C2 B$ h3 s# P
        }
( ?$ |$ g6 t& B}
2 ~$ v4 w" I  D) w( b) A. u</TEXTAREA>
! X2 ]3 h' F+ k. j6 a<P>注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。 " W. s: w' @1 _8 I' O
<P>
6 [. h+ v0 ^9 [3 O. }8 g+ i<P>
' |( r5 s( J* V7 ?<P><b>3.4 使用MFC类的虚函数机制</b>
. j6 |2 k/ K' |<P>
/ J) ?0 z# `1 D" u* Q% E! X! L<P>
- |8 u8 M5 i6 X<P>修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子: / _7 L. N1 P. z+ S9 a, f$ ]
<P><TEXTAREA readOnly>void CView::OnPaint()
. e- K: G7 f) u1 |! f7 g{( n* B% W# S5 v2 w& S# U9 D  S9 B
        // standard paint routine* w! U) e9 L, A) G" k: h$ c$ _
        CPaintDC dc(this);! b% ~# t1 y# p! k. L3 H6 a; `  e6 M
        OnPrepareDC(&amp;dc);
2 X  \# I$ D3 f" Q+ s8 ?  R/ M        OnDraw(&amp;dc);
2 [" ?6 M, `- a, o9 x}
5 P  E& z5 \2 x9 t; f</TEXTAREA> 5 t$ G* K) m* `+ C( G" y' N% z: ?
<P>这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。 - P* D  `" l  w- v: D
<P>以下列出了与界面美化相关的虚函数,参数说明略去:
7 t/ r: @  b6 A" n# N; c<P>CButton:rawItem
3 d* d4 Q( k0 a  o<P>CCheckListBox:rawItem - I. P, y6 F( ]  [! l7 B
<P>CComboBox:rawItem 0 f, Y  _7 Y- b+ r
<P>CHeaderCtrl:rawItem 0 G5 \# a6 a& p0 i2 R1 Q1 X
<P>CListBox:rawItem
# V) l* O) z' {4 w" X$ z<P>CMenu:rawItem
) G2 f  W) o$ C<P>CStatusBar:rawItem
& {- f6 E4 ^3 F" a7 I  T<P>CStatusBarCtrl:rawItem 7 F8 S0 P5 c$ y3 e/ r
<P>CTabCtrl:rawItem</P>5 g2 \% ?% Y7 }" J
<P>virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct ); - G+ q' c1 r- I
<P>Owner draw元素自绘函数
9 K) o( r' }6 Z% Y' }<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
很好的东西。值得深入学习
" k. B* h0 ^2 |3 o" Y




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