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