数学建模社区-数学中国

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

作者: huashi3483    时间: 2004-9-27 18:32
标题: VC之美化界面篇
<><IMG src="http://vcer.net/images/item.gif" align=top>关键词</P>界面美化
& n" f. i- d5 _% d0 m& ?, B, }, t5 F, I4 ]) y+ b: \5 F) D1 r0 d* h8 b
<><IMG src="http://vcer.net/images/item.gif" align=top>摘要</P>/ ^( c- m1 l: d/ _4 Q
<DIV class=vcerParagraph>
7 [' K9 f+ {7 F- J2 M% Y<>本文专题讨论VC中的界面美化,适用于具有中等VC水平的读者。读者最好具有以下VC基础:
- l4 g; F" @6 G2 k<>1. 大致了解MFC框架的基本运作原理;
- M3 v( o. U) c9 v5 F. D<>2. 熟悉Windows消息机制,熟悉MFC的消息映射和反射机制; ' V; A, x9 k3 o  [7 j
<>3. 熟悉OOP理论和技术; 8 b/ k  k3 g6 U+ W, [3 Q# S; K
<>本文根据笔者多年的开发经验,并结合简单的例子一一展开,希望对读者有所帮助。 + e/ G$ y2 S# m5 v; t
5 n" E& h) T/ @9 _: N* _* ]
</DIV>) o& b3 b' S3 p% M3 y# Z
/ c, q' R$ s; ^4 ^3 n/ g! }
<><IMG src="http://vcer.net/images/item.gif" align=top>正文</P>, _8 ?7 y% b( _# h* |
<DIV class=vcerParagraph>. J9 E0 x8 Z3 _1 k
<>1. 美化界面之开题篇</P>
$ L; U$ c3 [9 a<>相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
& O8 [7 m* _! V' {, @# ]<>
( X4 y% Z- i3 `3 X, V& w<>( ~6 T7 Q, E# I8 V6 m5 `6 r1 i
< align=center><IMG src="http://vcer.net/upload/2004/03/1046596474810.gif" border=0></P>
; W' e# c! z2 Y4 [< align=center>  7 D/ X8 n& u/ y; ~0 A
< align=center>图1 瑞星杀毒软件的精美界面</P>; W# m) Q; G+ i# j* F
<>程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。
4 D% L. Z2 Q" F+ M$ W6 M<>“受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。 / F) Z9 O6 _; Z9 i
<p>4 `: V) d) Y; w# u5 ~
<>2. 美化界面之基础篇</P>
5 d+ [7 U; E; W" T<>美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免…… $ [. Y! J& f1 e; ^" u9 \9 O# X
<>+ b2 J# q) v" S8 C
<><b>2.1 Windows下的绘图操作</b> " K* a& a5 w. \
<>
7 h& A- |' c+ u. P<>熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等…… , t. u8 `$ F9 ]# U# w( x
<>Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。 . J. P& H6 U+ A6 N2 m9 y0 J& @
<>9 N4 X% m2 \- ~* O0 ~. [! V, r# u
<><b>2.1.1 设备环境类</b>
: W: V* L; h6 ]% Y( ^<>0 d+ X# L# i3 w# H1 G
<>Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。 0 m% O! V! q; T8 U
<>MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括: , p& Y' S# v- q6 y" a* ~# [
<>Drawing-Attribute Functions:绘图属性操作,如:设置透明模式 6 G; B9 L- p9 j+ J" {* I6 ]( Y
<P>Mapping Functions:映射操作 / W2 P3 D% f1 c
<P>Coordinate Functions:坐标操作 $ Z0 D: T# @' y- t# E1 W
<P>Clipping Functions:剪切操作 $ Q! I8 H/ [! t8 u8 B" ~7 `7 W7 b
<P>Line-Output Functions:画线操作
6 o" [* R/ M- _! w6 @<P>Simple Drawing Functions:简单绘图操作,如:绘制矩形框 ! Z" x' _: A. B' V6 l; F
<P>Ellipse and Polygon Functions:椭圆/多边形操作 7 p% F9 G8 V% O
<P>Text Functions:文字输出操作
2 L$ @" W7 y, A% D' s- G<P>Printer Escape Functions:打印操作 - k* t7 J! Z' e/ J. F, q
<P>Scrolling Functions:滚动操作</P>
; E( z' `+ O# z' g1 k9 a' i<P>*Bitmap Functions:位图操作
$ N1 C5 t, R$ Z* |<P>*Region Functions:区域操作
6 d: j! d* ?+ t5 X<P>*Font Functions:字体操作 , m- i2 L1 o2 R' @8 X9 J& e
<P>*Color and Color Palette Functions:颜色/调色板操作</P>! F- K0 {+ _5 L; {
<P>其中,标注*项会用到相应的图形对象类,参见2.1.2内容。
/ g# v& c1 O; @8 V/ t/ G1 @<P><b></b>  
) \; w" d1 V' G( O, u$ g0 H<P><b>2.1.2 图形对象类</b>
8 R/ a& @$ y# C* J* l<P>8 f4 `- d4 B8 ~% B
<P># u* [0 w* w( U8 e7 L6 M2 v
<P>设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。 5 w, O  F% V/ C2 v0 d1 y; V, p
<P>下面的表格列出了MFC的图形对象类:</P>
: ?5 p% t% u, D3 w: Y2 S0 G<P>MFC类 图形对象句柄 图形对象目的 2 W8 a9 a0 A, B& D: Z  G
<P>CBitmap HBITMAP 内存中的位图 ! I; j' K  [! r
<P>CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式 9 w# R* J# n# a: Z
<P>CFont HFONT 字体特性—写文本时所使用的字体 # m: U5 g" G: a5 {$ t6 z9 M  M# z7 h
<P>CPalette HPALETTE 调色板颜色
& F; I, q- l6 Z/ {3 \<P>CPen HPEN 画笔特性—画轮廓时所使用的线的粗细 6 l1 g0 M; Y0 e' C; _4 Z
<P>CRgn HRGN 区域特性—包括定义它的点 * F5 |  f+ R6 D5 n
<P>表1 图形对象类和它们封装的句柄</P>
: I9 G% `7 q- u, C. V<P>使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:
$ {+ [; E* _; u/ w& F: p<P>
+ S# m/ G" f" ?# }) p8 A<P align=center><IMG src="http://vcer.net/upload/2004/03/1046651213100.gif" border=0></P>
% q0 B% q, }* {, z<P align=center> 图2 使用CDC绘制出的按钮</P>( n1 K$ e9 g* t
<P>该画面通过以下代码自行绘制的假按钮: + K: `' U9 U9 k/ I4 W9 x  z
<P><TEXTAREA readOnly>BOOL CUi1View:reCreateWindow(CREATESTRUCT&amp; cs)) L" F; g8 V  \. h  {  C( i
{/ M! |: M5 Z2 d+ o$ g$ O
        //设置背景色
6 C% M! t$ ]) D        //CBrush CUi1View::m_Back
* D$ D5 P% p) O4 s, G; v: f8 X        m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));
* N" g3 b- o6 s# i' y5 {8 y3 v
6 @. x6 g+ a4 }1 @0 j- C# q        cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL);
1 J5 ~; t* W) F$ \6 P        return CView:reCreateWindow(cs);
" o* N3 I0 G6 w' S% Q1 h; ?3 y1 }}
" m- T) P  V/ F. J
& k+ R; D4 l& C; {: Lint CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
$ u  J) [$ n0 C0 S3 p# `{
: T5 Q# C; i( U        if (CView::OnCreate(lpCreateStruct) == -1)
" m+ j/ B5 K3 j0 O8 {                return -1;
5 ^& C' q. [  i4 ~  P  m2 {4 }# n! T9 a
        //创建字体
; e3 D; ?5 S( ^0 L. w2 z% ~        //CFont CUi1View::m_Font
0 |8 ~7 }( L7 q        m_Font.CreatePointFont(120, "Impact");* _6 L4 m% o, a
        * I6 \7 S" `& |# l% m; D
        return 0;
# ?+ Z4 g& s' |9 H% Z3 m9 \}5 e! V# R6 _: o5 q
, K; X4 w8 v( S' t6 ^
void CUi1View::OnDraw(CDC* pDC)
  |' p4 c, s. b: V5 g7 K9 }& N7 ~{
, D7 U" V8 P2 E( C; S        //绘制按钮框架# b3 Z' c8 R: {0 Q
        pDC-&gt;DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);
6 z+ K5 @+ h/ R9 W9 ?' d( Z4 U7 S8 H1 ]: c4 F; F, y% P& p6 R
        //输出文字6 P2 m) i& G6 p( {) t. j, N
        pDC-&gt;SetBkMode(TRANSPARENT);3 u1 M+ V2 m$ V% Z/ B
        pDC-&gt;TextOut(120, 120, "Hello, CFan!");
* n+ d" |' y2 r9 R% a7 u& X4 L8 @6 p}</TEXTAREA></P># [7 U5 l4 W8 M4 E- y- P
<P>呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。 </P>
! I) `( @. y# }6 ]' Y<P><b>2.2 Windows的幕后绘图操作</b> </P>; F3 L! u$ a" `/ F: {, O: U" s
<P>在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。
5 G! I4 T5 d5 J6 k7 J" m9 N% z<P>有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。   `  \* ^8 _/ A7 D9 P& A$ F
<P>所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框…… $ [8 k" X: @7 D! j1 c- c
<P>有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。
# x/ u+ V, V* A<p>! R1 K1 I4 _  _6 M9 Z
<P>3. 美化界面之实现篇</P>! b* H; ?/ k5 N4 g8 K% \' O
<P>Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。 $ `" A* p3 Q/ ^) d
<P>
5 Y0 D* I& b; `<P>
- n& ^, _* Y2 Q' @+ g8 ?2 |<P><b>3.1 美化界面的途径</b> & B$ y# Q# q( ^  h- x  A
<P>; |$ {- L( x+ [  X
<P>
7 G: c, F% p  A7 Y6 A, m8 ?<P>如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:
6 f% Z2 ~2 Z- T<P>1. 使用MFC类的既有函数,设定界面属性;
) U; v. @. e% g1 U. h<P>2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色; 3 y2 w* }2 t% z3 J1 L+ [' b: }
<P>3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为; ! i  T1 ]" ]. J/ Z" @/ F
<P>一般来说,应用程序可以通过以下两种途径来实现以上的方法: 3 {& J9 \& g% c0 K9 I3 Y
<P>1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;
5 N) \! }% c3 s9 E; \5 ~<P>2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。
; n# }* {+ N% J<P>对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:
  J5 D* J7 R' t7 w. b# |<P>① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示:
2 I8 Q$ k% H' f5 J- q' @<P>% P( E: u8 Y0 P+ M9 q1 }$ Q, t% \
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046596487288.gif" border=0></P>1 x* h2 p( Z) A6 T
<P align=center> 图3 为按钮指定CXPButton类型</P>8 O* J4 I, z9 U- S: M- f
<P>②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法; * W2 ~& _# I: p) l' q
<P>以下的章节将综合地使用以上的方法,请读者朋友留心观察。 % y- O3 m, O% U1 F# T: Y2 `
<P>+ _2 b% ]' R8 |* }( w1 u: O1 c
<P><b></b>  
0 Z) H5 w/ Z% Q$ ]: ]+ i  G<P><b>3.2 使用MFC类的既有函数</b> . W0 o4 T  L' f; m1 R
<P>: z# f0 {1 u& L8 Z1 f9 x
<P>0 R% W- P5 r3 Z
<P>在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。 2 L' q$ E' I$ M
<P>CWinApp::SetDialogBkColor
9 S% i3 R* Z* |+ R# c$ N<P>void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );
2 p: M3 U# M2 X<P>指定对话框的背景色和文本颜色。</P>
3 ^. K, F" B$ L7 L" X<P>CListCtrl::SetBkColor
$ m3 j6 r1 [+ J  K1 a<P>CReBarCtrl::SetBkColor 2 O; C' |7 J7 a# D$ m& P0 i3 b
<P>CStatusBarCtrl::SetBkColor : V5 U$ e, Q% D9 G0 e1 M6 ~
<P>CTreeCtrl::SetBkColor - D: e: v3 \( d  Y
<P>COLORREF SetBkColor( COLORREF clr );
& ^2 R1 y! i. L5 n0 l# x, Q) F<P>设定背景色。</P>9 w- m& D# ~+ D8 F% K$ D3 N. G
<P>CListCtrl::SetTextColor 1 o- B( X! D9 a% Y1 }. O
<P>CReBarCtrl::SetTextColor
7 `* ~* i3 f: [) B, @$ A<P>CTreeCtrl::SetTextColor
9 P5 Z8 m! O; \7 ?8 E1 d3 Z  x& S<P>COLORREF SetTextColor( COLORREF clr );
: s! ]. a8 ~! v# o3 W4 R<P>设定文本颜色。</P>
5 a& {7 h- o4 f# r0 _" N<P>CListCtrl::SetBkImage ; q! T9 m4 y' j  i( c0 n
<P>BOOL SetBkImage( LVBKIMAGE* plvbkImage );
% A; y6 J4 x3 [6 `<P>BOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);
  J0 l& u1 |3 W; Q) _( w- S<P>BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );
5 o1 R$ i9 d: }/ ]<P>设定列表控件的背景图片。</P>8 y  k, D, U  ]- t& A4 N
<P>CComboBoxEx::SetExtendedStyle 0 S$ q  c+ y4 `, [) z
<P>CListCtrl::SetExtendedStyle
) r0 S( |: T3 h. t<P>CTabCtrl::SetExtendedStyle
! h* ^3 ~. E5 X5 i0 _/ ~/ Z# f<P>CToolBarCtrl::SetExtendedStyle 0 g9 o9 u3 R' r2 a- L' x
<P>DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles );
) k: N$ o  \3 @7 H$ P  o  c; x6 q<P>设置控件的扩展属性,例如:设置列表控件属性带有表格线。
; e+ H: ~3 M; d& |<P>图4是个简单应用MFC类的既有函数来改善Windows界面的例子: , P; G) r  Q- r6 H. E2 l
<P>- l) B; S' U9 l1 I8 M4 `* q
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650314708.gif" border=0></P>: a: R5 Z0 z0 w* n
<P>
' {; C- {& O: D<P align=center>图4 使用MFC类的既有函数美化界面</P>
5 p4 c- b$ K  e, L; s1 [; p<P>相关实现代码如下: + ?" N& h8 \' h$ f  K: r% {
<P><TEXTAREA readOnly>BOOL CUi2App::InitInstance()% l+ R) N/ h6 L) ?' P# k
{
! G; H6 S( j9 X! S' K) b        //…# C9 g% U5 ~! p3 k% q; B4 u4 o
        //设置对话框背景色和字体颜色* p; m1 V# s7 c' v" N. S& V, t9 _8 W
        SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
% O; Q* j' U, o" W6 P        //…8 [0 S  C9 C& w/ F
}9 O0 Y: |# g) g+ c5 o
4 v2 L0 J2 w) _) b6 \
BOOL CUi2Dlg::OnInitDialog()
3 s9 \; C6 i7 [1 \) ?& U, N* B{8 A4 a5 p: H& g& t. M  C7 w
        //…
$ {" x' h' d4 W, d) Q  h  K        //设置列表控件属性带有表格线: ], _2 _, Q1 d; y
        DWORD NewStyle = m_List.GetExtendedStyle();
8 l' l$ l3 [( s9 C    NewStyle |= LVS_EX_GRIDLINES;5 [; t% D0 E0 t' _; \+ y( [- A
m_List.SetExtendedStyle(NewStyle);
- l; J( T6 J+ S1 F2 L- A& M3 c: r
( j" b. n, H, f% g' Q7 v) l/ j        //设置列表控件字体颜色为红色
  `8 W2 L, z" @5 o  f: P3 ^        m_List.SetTextColor(RGB(255, 0, 0));, g! e1 q; L, u9 n3 e0 X' B

, C& h; x0 j! d  r& A4 y        //填充数据
4 P- U+ H6 H  z3 u        m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);! J5 x' K7 k2 m( A5 @# ]+ e% E  q3 i- Z% o
        m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);
7 S# D2 e( w9 }6 g* L# W8 L
) x! f3 }& ]0 e8 G0 d. @        m_List.InsertItem(0, "5854165");
, B, Q) O& N' p0 U  [        m_List.SetItemText(0, 1, "白乔");) P9 R2 n: i4 \4 [& |5 `$ u, m" C: j
$ l7 E5 L* H7 i$ b/ w4 T* }
        m_List.InsertItem(1, "6823864");9 U$ E7 x# E; ^5 `- [+ t
        m_List.SetItemText(1, 1, "Satan");* w1 k" I! z+ X5 }3 ]; u
        //…
2 {7 k: k9 b3 b* l1 a, T}</TEXTAREA></P>$ p7 m( K* e$ Y" H
<P>嗯,这样的界面还算不错吧? </P>
4 l+ _# W/ A( I; c1 P<P><b>3.3 使用Windows的消息机制 </b>
: R/ `7 g( p1 m: u$ @, G5 E& S<P><b></b>  ( c7 f. x* \4 ~# E( u2 }( Y; N
<P>使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息: 4 b7 r6 e4 [  I- o
<P>WM_PAINT 1 {# {/ s/ r9 w$ u
<P>WM_ERASEBKGND
, J* S- p  w# o1 @- ^5 m/ {4 ~5 j<P>WM_CTLCOLOR*
& |) k% p) o$ q; M2 N<P>WM_DRAWITEM* ( T5 }2 J' @; T3 w
<P>WM_MEASUREITEM* 3 Q' d* {9 s5 X, t) ]; l
<P>NM_CUSTOMDRAW*
4 p# |# U% i+ T& w" g2 _; |<P>注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。
. _+ B4 Y# C0 S& D/ w<P>
1 R+ R2 I0 ?7 F8 V: i8 i<P>
. _0 h: c+ H0 _) d  z- s  O7 C  k<P><b>3.3.1 WM_PAINT </b>
# @" q# y3 M2 H0 X1 V9 o<P><b></b>  
0 \7 X0 a- u# C7 H( O& p2 u7 {, g<P>WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。
0 _4 V5 Z9 f4 `' ^<P>可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下:
% G/ W' o: R0 @1 r<P>afx_msg void OnPaint(); 1 x, y, Q5 T) h+ A$ t. I& c% e9 o5 ?' Z  m
<P>控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: , z& J& X, _8 b) e) B$ Z; ]& h" l  u
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650335708.gif" border=0></P>
( ^: e: n1 @6 S<P align=center>图5 利用WM_ PAINT消息美化界面
/ j! c6 |$ n5 _! E  F: e<P>实现代码也很简单: 5 l" f# c) F; `1 v
<P><TEXTAREA readOnly>void CLazyStatic::OnPaint() & K, W! q6 B1 x( b6 H
{
: r( W# ^, d0 B* }/ O0 a        CPaintDC dc(this); // device context for painting1 u6 Z; Q, [+ W7 w! s7 x7 [
       
7 l3 a9 z% _  r/ d        //什么都不输出,仅仅画一个矩形框6 B  r8 u* p% _0 y  X
        CRect rc;7 H1 r1 {* `" D
        GetClientRect(&amp;rc);
# Y$ K; a" N% z3 ?' j        dc.Rectangle(rc);        : I6 z. @( F; @0 e
}
6 X/ E; C. I# L% ^+ a</TEXTAREA> * `! d& g) V) M% Q
<P>哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。
. r6 F) p& W( A: z6 B1 s5 y6 J& R<P>2 Q' h8 |/ @! l/ j
<P>
9 |; r/ a) i% W) M5 X  ]<P><b>3.3.2 WM_ERASEBKGND </b>
! Z0 I* E5 h) T- V& m% K<P><b></b>  
- w' a6 X+ Z; n  [0 F<P>Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。 ; D' A  a  Q" ?6 s( D; r0 o& [9 W' h
<P>可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下: * P3 ~& N4 T) p9 x
<P>afx_msg BOOL OnEraseBkgnd( CDC* pDC );
5 ?1 _0 s5 S! F+ J- i* v7 a0 y<P>返回值:
% ^$ b6 A' c9 s" U<P>指定背景是否已清除,如果为FALSE,系统将自动清除
4 `; j! O, n* }<P>参数: ' B2 m( G' o% J* l8 M$ L
<P>pDC指定了绘制操作所使用的设备环境。 5 r5 c2 ]- G  S
<P>图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景:
( B, I) k6 l/ Q<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650328908.gif" border=0></P>
. N$ j5 P3 }% D  B2 d* ], V<P>  z9 h8 O3 e: t* g2 E/ M
<P align=center>图6 利用WM_ ERASEBKGND消息美化界面</P>
) V7 B5 m- |$ s1 [<P>实现代码也很简单: % ]( }8 w: c/ @
<P><TEXTAREA readOnly>BOOL CUi4Dlg::OnInitDialog()
  \- V' ~. I  D0 t{4 q2 a6 Y) |" V2 X' @6 B/ S: H% A
//…
1 j2 C; _6 x$ w0 ?( B        //加载位图
& e& \3 N; T( o        //CBitmap m_Back;& N$ o: a. }; `. R4 P4 A
        m_Back.LoadBitmap(IDB_BACK);
1 _! ^* t% y8 P& d& s( F: n* r        //…
* L0 e; F) v: V8 K0 h* _. _: i' S}8 o* e' Q6 s/ m- N& H

6 G$ m/ n3 }5 Q6 lBOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC) + o. w1 e! f7 c) t: k
{
5 p: [2 B3 ]9 K  {' M7 D        CDC dc;- t- B8 C  ?* `
        dc.CreateCompatibleDC(pDC);
8 c* Y$ d9 R/ r. s        dc.SelectObject(&amp;m_Back);+ U! A: M" u8 z5 P0 I
& J, B& z# M! X$ n9 ^8 E; u5 X
        //获取BITMAP对象6 v& \' ?( _2 U# h' @8 l+ |) I' f
        BITMAP hb;0 \& s  |6 s$ E6 b8 ]" _. J% d# H
        m_Back.GetBitmap(&amp;hb);" M# T% J( j: `! e) c1 f0 u( B
* O" J8 p# i6 y
        //获取窗口大小
! f; h  v) W# Q; T7 N5 M$ P0 z3 X1 [        CRect rt;
2 \' k! c& a, u# w9 \1 o        GetClientRect(&amp;rt);
4 `" K0 T5 m  L7 r3 h/ u        //显示位图4 W6 x* Z$ F5 c9 _1 x0 {+ g9 `
        pDC-&gt;StretchBlt(0, 0, rt.Width(), rt.Height(),
3 J! Z0 H. k+ q                &amp;dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);
: ~, t; {; R! o( t% e- P
3 h, n- S1 H" D- v8 E& q        return TRUE;
+ F/ A. N- A' _3 W}
8 L5 t& N' h5 `: D$ j
) ~( b& i/ `2 m  S7 h6 u. K) @! kHBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
+ e' b. a) J2 I5 O  q' p{
5 a7 d: a( F2 j! P- y; O        //设置透明背景模式4 [" Z7 d( Y* I2 o
        pDC-&gt;SetBkMode(TRANSPARENT);/ ?/ F. Q/ E- R; T# |1 x$ Z$ q
        //设置背景刷子为空
7 v) }, f8 I/ \$ d' V        return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);
( T: M' Q1 J) v, M}
6 f) ^2 k1 j' A' l7 t' F</TEXTAREA>
- {; R+ q1 Y; O/ D<P>同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。 5 q$ @9 Z! C' b  I4 W
<P>
8 D0 v% `0 l7 ~<P>
# u* f0 ]% E; \8 l<P><b>3.3.3 WM_CTLCOLOR </b>
/ c* s' j+ L& U3 q( C& d6 q# O<P><b></b>  
; P, d- G+ T# b+ {<P>在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。
# \7 V5 E0 A' j; l/ L8 E1 ]<P>WM_CTLCOLOR的映射函数原型如下: ; {$ b- b+ l; Y1 n5 R
<P>afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );</P>9 T! d& \; S1 C' Q0 ~+ \# V+ n
<P>返回值: . Y3 ]* u# w# C0 B" [  m* F
<P>用以指定背景的刷子 & s8 T  C; S; Q8 D7 d# D4 P6 O
<P>参数: # @/ |' \+ b9 t, x; G
<P>pDC指定了绘制操作所使用的设备环境。 + |0 ^* M7 m$ ?& o
<P>pWnd 控件指针
9 v" [6 j: t$ ?<P>nCtlColor 指定控件类型,其取值如表2所示:</P>
2 N2 l& c6 H+ l$ ^<P>类型值 含义
) ], i; g0 b- M5 j/ e/ e/ t<P>CTLCOLOR_BTN 按钮控件 8 z5 z! I8 Q% G
<P>CTLCOLOR_DLG 对话框 ( @: Y1 b; x2 Y) G
<P>CTLCOLOR_EDIT  编辑控件 ! m/ w# b+ e# |: N( G# T
<P>CTLCOLOR_LISTBOX  列表框 - t# k2 Y0 p" m5 o" |. w8 I
<P>CTLCOLOR_MSGBOX  消息框 5 ?$ d: G3 Y' T$ n" R! l+ @1 O
<P>CTLCOLOR_SCROLLBAR 滚动条
& Q! J" v& E6 O& C<P>CTLCOLOR_STATIC 静态控件   @" R! f! u9 E& n# @% _$ R# H
<P>表2 nCtlColor的类型值与含义</P>1 j- \; ~3 _) o0 K" \" N/ Q! X1 B
<P>作为一个简单的例子,观察以下的代码: 4 m( U" a0 p$ k
<P><TEXTAREA readOnly>BOOL CUi5Dlg::OnInitDialog()3 N; [3 Z8 ^4 d1 @, E' {
{
$ t4 f5 }. f$ G        //…
+ a! P9 b' C! U( J        //创建字体
. I. }4 K# l. ]: N& I1 d        //CFont CUi1View::m_Font1, CUi1View::m_Font2
" D/ k; s0 C2 ]0 S5 }- y        m_Font1.CreatePointFont(120, "Impact");1 X) ^$ n% w6 w/ C) `9 J
        m_Font3.CreatePointFont(120, "Arial");- A, L9 Q- g! \/ t
        " D6 t" u* F& z+ E8 s% g
        return TRUE;  // return TRUE  unless you set the focus to a control % }* G7 T/ o. @  X
}
9 w8 Y+ Z+ y9 k0 u
/ V4 l- k) u6 VHBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
8 G# E1 r# @* h% q5 Y3 g{4 W! I: Q% G' A/ B
        HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);: `) k& J; t' S, A' ]( i
        if(nCtlColor == CTLCOLOR_STATIC)
+ q- q; ^, X, ?! A6 q( i4 \        {2 {) p; ]% ~4 i0 y! s) i( T
                //区分静态控件
# e2 X; i. J8 n- k                switch(pWnd-&gt;GetDlgCtrlID())
6 l; z) q+ R$ m% N, t) W: E0 I  f                {
6 }; |+ Z8 ]5 r; C$ R                        case IDC_STATIC1:3 v5 [* U( c- \, I: I8 y
                        {8 h6 G" p: ^* S- I# W
                                pDC-&gt;SelectObject(&amp;m_Font1);
  v8 l9 t4 E7 a/ s& V( T2 Z3 x                                pDC-&gt;SetTextColor(RGB(0, 0, 255));
) i$ {) Z* L0 y$ q5 u: k( }5 W                                break;
* `0 ?. ]/ S2 U. O; i* `/ a                        }+ M1 t- q  U# M4 k6 M1 q
                        case IDC_STATIC2:' A( Q" F+ O! o+ X
                        {
0 y* ?% t9 f4 `                                pDC-&gt;SelectObject(&amp;m_Font2);
% R0 I( e7 q; }3 C* N                                pDC-&gt;SetTextColor(RGB(255, 0, 0));$ V$ Y7 y, ~* ]. W/ r
                                break;' q- j9 K% R) L. y( ~
                        }
, |# @7 v( i! t7 S2 K6 f                }
  g6 c2 e2 h1 F; H: Z8 i$ y0 K1 s        }
1 t! _6 {2 V: @9 K
. j; m% p4 m8 L        return hbr;$ }7 ?# ]( Z- n+ Q9 X
}& X) T4 t. |% G: }* C7 `
</TEXTAREA>
6 D; o' o' j( |) t0 u- Z" K5 x<P>生成的界面如下: 9 x9 Z$ |. m  E" l3 K8 R* ?
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650321578.gif" border=0></P>0 v, j8 h6 ^' m. _0 U0 |" m! }; Z
<P align=center> 图7 利用WM_CTLCOLOR消息美化界面 </P>
/ O9 E1 i/ |/ j<P><b>3.3.4 WM_DRAWITEM </b>
; p+ p0 p9 M# [: J<P><b></b>  
* I3 N) b1 c: N  z3 W<P>OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。
% }, T; [; d% K<P>当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。
9 ^7 k2 \- H0 Z& E1 k<P>WM_DRAWITEM的映射函数原型如下: , X8 r0 f" R, \" Z( u
<P>afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );</P>7 W% |" w# h, S
<P>参数: 2 b) _: R+ F% j7 n' D& `8 |: }; R1 E
<P>nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 ) |0 ?8 q9 P2 d+ `& I
<P>lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下:
* R* x$ A/ o; [1 |<P><TEXTAREA readOnly>typedef struct tagDRAWITEMSTRUCT1 B- u+ {7 b$ o4 z/ f% W& ]7 x
{
4 @: N$ F9 d2 o( O4 b0 x8 L, w    UINT   CtlType; 5 E& h) Y3 C+ d% @
    UINT   CtlID;
* a: k! g$ [& X8 D# H    UINT   itemID;5 A$ Y1 M' s3 K7 Q7 c
    UINT   itemAction;
0 H& F6 {0 F, }3 N. w    UINT   itemState;) f& v8 w# h* e
    HWND   hwndItem;
7 N! S0 Z$ B' j( _6 i+ @" j    HDC    hDC;
5 l" H& T  l" e8 N0 ~1 l    RECT   rcItem;8 s& T( d, Z- Z) o
    DWORD  itemData;- H, L3 f$ q' Q& t: o  f
}DRAWITEMSTRUCT;( b" O! D! A+ _7 F, Y7 Y
</TEXTAREA>
, t- Q# i/ d) j) L* |/ W<P>CtlType指定了控件的类型,其取值如表3所示: ( }; {0 P/ S' H7 @5 J( R: |. l
<P>类型值 含义 ' N  K1 d7 [2 |, I
<P>ODT_BUTTON 按钮控件 - M- b% J2 m" ]5 U+ I8 L  S1 `
<P>ODT_COMBOBOX 组合框控件
7 O* \: v/ r1 E- x6 P<P>ODT_LISTBOX 列表框控件 ( k+ i! Z9 B' I$ L+ E# \
<P>ODT_LISTVIEW 列表视图
2 l9 T, j$ j  h5 U<P>ODT_MENU 菜单项
, B# U; d4 S' M<P>ODT_STATIC 静态文本控件
4 I6 Q/ A" }  ]9 d3 p" ~' g0 Y; B<P>ODT_TAB Tab控件 1 E0 h  Y' j  M+ B
<P>表3 CtlType的类型值与含义</P>9 F0 I/ E& h. l3 [
<P>CtlID 指定自绘控件的ID值,该成员不适用于菜单项 7 j$ E7 I& b5 ]6 H7 N- t
<P>itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。 ( ^0 C! s- x5 i4 m: Q3 S0 ^' s
<P>itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:</P>
1 \' q2 ^9 x9 z) K- ?. ^<P>类型值 含义
6 `. G  ?) i8 K, J5 e* F5 @8 C4 |<P>ODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。
" Z/ p: V; H* n0 g/ I" O  k( S<P>ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。 " W* q9 Q0 }5 A2 G4 l
<P>ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 1 y. \1 F6 I" `  Z; J  q1 M
<P>表4 itemAction的类型值与含义</P>; |1 {& R1 G- Z) U  E  d
<P>itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:</P>
; p$ |( E, L& b8 C<P>类型值 含义 ; `/ L4 f* B! o0 ?( Y8 y0 K$ I
<P>ODS_CHECKED 标记状态,仅适用于菜单项。 2 K& ?- u7 C# T; U' Y1 n
<P>ODS_DEFAULT 默认状态。
# J& _3 q" E  _8 p. R1 ~3 b<P>ODS_DISABLED 禁止状态。 ! b6 m, p# I3 k* ~. G% H  k4 b
<P>ODS_FOCUS 焦点状态。 6 E- e8 c7 A7 R1 I. m; S( T" D7 _
<P>ODS_GRAYED 灰化状态,仅适用于菜单项。
" T  g# Y3 ]" S0 T* _3 m<P>ODS_SELECTED 选中状态。 # g2 v+ u) \# w4 o
<P>ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。
1 Y1 w) A- {7 t: l4 `<P>ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 3 h# v* m/ j. W
<P>ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。
/ n0 x& C. J- X* M4 [<P>ODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。
; q* R. a; v' K( ^6 Y<P>ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。 3 _7 \3 r  F  D7 Y5 t+ K1 e
<P>表5 itemState的类型值与含义</P># s7 R  P* G0 l2 K! `4 X( X% |
<P>hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。 7 ~5 Q' [. K$ E$ d! C
<P>hDC 指定了绘制操作所使用的设备环境。 6 P7 |0 b1 o  ~
<P>rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 ( U7 g1 e. c5 ], D6 N; S$ u& V
<P>itemData % p5 |  q9 t9 g+ {# r
<P>对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 - Z2 L: J  T3 [" |
<P>对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 9 Z6 Y& z) u* o$ y3 k8 L# C
<P>如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。
  r! [7 t& W$ u<P>图5是个相应的例子,它修改了按钮的界面: + W2 }6 V+ @$ E+ C* p! @
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650324712.gif" border=0></P>. T; u! z4 X8 J: P4 y" G
<P>$ j0 E  `) J$ N; a7 G! u  e
<P align=center>图8 利用WM_DRAWITEM消息美化界面</P>
8 Z8 x% I- b' [" _" T5 v7 ?4 G: Z<P>实现代码如下:
* W2 r% D7 o$ J- t3 S4 _! R# }<P><TEXTAREA readOnly>BOOL CUi6Dlg::OnInitDialog()
1 {- ]8 j. I% s8 j7 m$ q; J8 m{3 o+ ?" B7 h& ^# u# Y/ Z
        //…
/ T2 G  _) [! F' ?8 I        //创建字体
0 n; h( y! D4 l. c        //CFont CUi1View::m_Font" e/ p6 m7 z9 s7 l. P- Q! u
        m_Font.CreatePointFont(120, "Impact");
+ F0 F& U& f2 P        //…( T, D* c& s; E/ ]
}
- b) }3 O. r3 L8 v- x& Z4 U" r+ P; A4 q- C
void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) 3 G0 q4 Z; ?! _' K
{
+ [8 ~+ A* t/ V8 C6 |! x- e        if(nIDCtl == IDC_HELLO_CFAN)
! Q8 w$ z9 B% ]2 \        {
2 o* U& B) u# l                //绘制按钮框架
, L/ k+ b2 M) s8 V* W2 T
8 I- ^& L, u5 S6 J6 {. S+ R9 O                UINT uStyle = DFCS_BUTTONPUSH;9 S+ e9 A. f% {0 X7 v9 Y6 {
                //是否按下去了?% O9 a; K6 u4 {6 N- V! ]+ b
                if (lpDrawItemStruct-&gt;itemState &amp; ODS_SELECTED)' s1 ?" I- W, P% j; @& g" |
                        uStyle |= DFCS_PUSHED;
9 U) e8 E' F4 G3 j1 M. j4 o% P% F9 ]. u* b  C3 U' |' B) R6 K6 P: a6 n
                CDC dc;$ ~5 [- p. W" S) N# c
                dc.Attach(lpDrawItemStruct-&gt;hDC);
0 d+ d: Q, U8 k" f) ?                dc.DrawFrameControl(&amp;lpDrawItemStruct-&gt;rcItem, DFC_BUTTON, uStyle);
% E8 z  K2 j- S1 T
8 V$ e6 z4 T7 s  H                //输出文字
! b7 V, j3 q- Z' A+ |; c& M3 k) E                dc.SelectObject(&amp;m_Font);
* Z+ C' n* |# b; z+ l: }                dc.SetTextColor(RGB(0, 0, 255));
- I' n8 `; _4 {$ u. ~4 ~) F                dc.SetBkMode(TRANSPARENT);
- N6 n! Y+ O" a$ K) _& w+ N- A5 S2 L  w8 x
                CString sText;
' q- m+ M8 ^. D0 R: y                m_HelloCFan.GetWindowText(sText);; B& N0 h, D1 @
                dc.TextOut(lpDrawItemStruct-&gt;rcItem.left + 20, lpDrawItemStruct-&gt;rcItem.top + 20, sText);7 f/ e; z- B' f, x& |) w

! {  h8 c2 N/ M1 k) g* E3 a4 q4 I                //是否得到焦点
5 S4 Y' a' s, v' @                if(lpDrawItemStruct-&gt;itemState &amp; ODS_FOCUS)6 B$ q/ S$ P, ^1 m. Y
                {, s) s+ y: X7 u7 r" D
                        //画虚框1 l6 z! ^' f0 z" w" O( e
                        CRect rtFocus = lpDrawItemStruct-&gt;rcItem;
7 X) s  p3 X/ }! u# Y                        rtFocus.DeflateRect(3, 3);
6 p" P& b8 B9 F7 w. s0 f                        dc.DrawFocusRect(&amp;rtFocus);* X; b, C: A5 ?) \! \, H) R! @/ R
                }' }* X) q# n3 N4 W6 v
. O0 d' W3 }  D+ P  A
                return;2 g) t% a5 u7 T* F8 e' {
        }/ U, m4 S' a: {* d
        CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);7 E0 n% s) \7 w) J$ ^% J
}
2 R: O# X  P0 B" S+ m3 W</TEXTAREA>
, F# \5 m3 |. [3 x% V<P>别忘了标记Owner draw属性:
& |' z4 ?- ?$ Z, }) l& f<P align=center><IMG src="http://vcer.net/upload/2004/03/1046596492605.gif" border=0></P>
7 X( W, A, i# Q& j% M; u' K<P align=center> 图9 指定按钮的Owner draw属性</P>
, Z# p% [7 |* o9 ~1 ]0 E- e% v<P>值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton:rawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。 , i1 Q7 \- d& A% B' t* Z5 Z
<P>
7 I4 h4 ^4 u5 q$ w3 Z<P>7 _3 V, u9 t% g9 D, m
<P><b>3.3.5 WM_MEASUREITEM</b>
* `4 m3 |7 s% S<P>
5 t. F4 W0 t$ c<P>% [" |+ I( Z4 m- s
<P>仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
2 j1 ~" a8 }" R( s  f4 t<P>WM_DRAWITEM的映射函数原型如下:   V9 p* L1 B( u! d" ^1 c2 T: U# u
<P>afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
7 X2 u) l+ ~  q5 }7 V( A4 ], ?<P>nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 ' L: v% J; d) Z+ s
<P>lpMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下: " }! c( z  n# I5 ~8 l, ]! q7 }
<P><TEXTAREA readOnly>typedef struct tagMEASUREITEMSTRUCT
- h: O8 q4 B) h! q{4 b4 @: t  ~& u
    UINT   CtlType;
" X7 K4 B& j% s3 J, M    UINT   CtlID;4 J/ W! u# j5 K) n+ l& y, b
    UINT   itemID;/ A. i9 C/ B: k! i; w) c% N& O6 y
    UINT   itemWidth;: C3 v+ g1 ?# o! {  n6 g1 ]5 X9 M
    UINT   itemHeight;- m6 T+ f6 U1 i9 n" X
    DWORD  itemData
+ v6 |$ y: A. X* w} MEASUREITEMSTRUCT;4 ]" A# }2 Z, i3 I5 _; e
</TEXTAREA> ) Y+ @7 I8 h  S/ a1 ?' Y% Y
<P>CtlType指定了控件的类型,其取值如表6所示:
% r1 U0 E" [! j: ]+ q& \<P>类型值 含义
0 h/ I! t5 @0 c; E$ Q( W2 {. _<P>ODT_COMBOBOX 组合框控件
2 u* S9 k5 J: f5 j* C<P>ODT_LISTBOX 列表框控件
; p  X+ p; d- n0 d. a<P>ODT_MENU 菜单项 . P' D/ h) v/ w$ k' _: K* c4 l8 R& C
<P>表6 CtlType的类型值与含义</P>
% n. c8 Y) A1 `<P>CtlID 指定自绘控件的ID值,该成员不适用于菜单项
8 f$ Y% s1 k3 Q1 c; ^* ?, [<P>itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。
/ X6 A6 o& u5 v* E( Z# F- w# ~! y<P>itemWidth 指定菜单项的宽度 ; ^9 D" G8 V) V' U  ]
<P>itemHeight指定菜单项或者列表框中某项的的高度,最大值为255   u0 }# ]% m5 v
<P>itemData
% {6 y$ U, Y3 c* v7 E. \<P>对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 3 [* r% {& o- W  [5 T
<P>对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
# M+ b* d' {! {( F<P>图示出了OnMeasureItem的效果: 3 n- ^' }# R4 o7 @
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650332513.gif" border=0></P>
# _3 ]% H8 g( e: |3 P  G1 `' v<P align=center> 图10 利用WM_MEASUREITEM消息美化界面</P>% k- x' R, N5 b$ {
<P>相应的OnMeasureItem()实现如下: 8 U1 _! u9 |$ @% X1 U/ w7 O+ Y$ w- f
<P><TEXTAREA readOnly>void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) % w) z/ G1 P/ ^5 R, D. X* \
{
+ ~$ p) ]3 t- a  |) {        if(nIDCtl == IDC_COLOR_PICKER): Q/ t' x+ q$ W
        {
/ l3 x! t1 W  b, v. k7 t3 ~                //设定高度为30
- [- C! ?. B# E- l: {                lpMeasureItemStruct-&gt;itemHeight = 30;8 \5 J- x  R) w  s: W
                return;2 d6 D/ W& X* b6 L# g
        }# d7 t' v% |4 |' o* R
        CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
) {  u: S# g# ^& N  n' }}$ S% _' f9 W! v
</TEXTAREA>
1 f; u# d- \$ P# q- x; D<P>同样别忘了指定列表框的Owner draw属性:
6 n+ F1 J& i4 T# V; @$ X! [( i<P align=center><IMG src="http://vcer.net/upload/2004/03/1046596451727.gif" border=0></P>/ h$ d2 Q+ O, X1 r  p' A5 R
<P>
' K: A' I0 s( Q- Z- X( v<P align=center>图11 指定下拉框的Owner draw属性
) a/ V) d) y' Z2 \$ j) ~! c+ X' S<P align=center>  3 G+ \/ Y7 J8 N6 V) o
<P><b>3.3.6 NM_CUSTOMDRAW</b>
! ?! J7 d2 Y- G$ N& C; T) `% o% b<P>
% T5 E& D+ `* J6 w) O# h6 k' z<P>
& g$ V9 ^+ o3 x7 y- F$ J' J<P>大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。 3 d1 x* o5 `! C' y/ p
<P>可以反射NM_CUSTOMDRAW消息,如:
' Q9 D' a! @, L" N<P>ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
  s( C+ A- z- o1 h% d0 @; ]; I<P>afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult);
  r9 ?. A: S, ~<P>参数: . b; l5 W% r; u  [: p+ y
<P>pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:
# ]2 z" R: _; h7 b( g/ s<P><TEXTAREA readOnly>typedef struct tagNMHDR
4 v% q- N1 E& M1 N" Z# o{   l" X6 O6 n+ ~! ?1 k
    HWND hwndFrom;
  o0 t9 d- F& k0 j: |    UINT idFrom; 6 O1 S% e% i. t% i! `; t
    UINT code;
9 x( L# ?8 u% G" c} NMHDR;
4 f: ]8 S8 \+ U7 g' ^, u8 J. V</TEXTAREA>
. u9 W$ K7 c5 N5 p4 o8 M. t8 }& \! g2 a<P>其中:
, ^0 _  h; i; X/ [; p% ]% {8 o  I<P>hwndFrom 发送方控件的窗口句柄 ; ?$ ]2 |* c7 |9 L
<P>idFrom 发送方控件的ID / j; I+ z  D; G" o
<P>code 通知代码
7 b/ c9 n3 K% w. L5 C<P>对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下:   B% {, x! A' |9 p$ H. r* P- ?
<P><TEXTAREA readOnly>typedef struct tagNMCUSTOMDRAWINFO% K, l$ H* Y5 Y. }" c6 R8 L/ v
{
0 A/ {! o" e, O* Q6 t8 U    NMHDR  hdr;
  h2 e0 Q+ p/ O* l4 V    DWORD  dwDrawStage;; A* I; I" W' l0 \8 R7 }
    HDC    hdc;# S/ c) N, d8 Q4 |) ~3 I
    RECT   rc;
* W4 B$ \0 U! T7 A# _5 |    DWORD  dwItemSpec;
$ @- ~4 I6 l( d4 @    UINT   uItemState;# B8 n5 S  p) B4 i8 n
    LPARAM lItemlParam;3 l( x4 T! m5 K3 S# |& ^2 F# Z
} NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
% l6 L" [, F, p, |- a1 v0 G  `( L</TEXTAREA>
0 e) a7 c, t# Q+ I<P>hdr NMHDR对象 3 |. n' x* E  i
<P>dwDrawStage 当前绘制状态,其取值如表7所示:</P>
, i+ `& m& C" U* _- U% g6 u<P>类型值 含义
1 c! z9 }$ {+ F$ o' V* j<P>CDDS_POSTERASE 擦除循环结束
, A% i. Y8 k" E/ A<P>CDDS_POSTPAINT 绘制循环结束
& f2 ^5 x! [" B: g; ~& `/ G1 F$ Y: f<P>CDDS_PREERASE 准备开始擦除循环 : \1 X, }% y  s% I
<P>CDDS_PREPAINT 准备开始绘制循环 $ H* `9 I& R6 ]# `  T) H6 J
<P>CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效
6 r: I& H* U/ ]# g* g<P>CDDS_ITEMPOSTERASE 列表项擦除结束
: I- M8 U; X% T<P>CDDS_ITEMPOSTPAINT 列表项绘制结束
* Q3 p* Y$ }- T) D& d<P>CDDS_ITEMPREERASE 准备开始列表项擦除 9 b' ?0 l- L6 }( |
<P>CDDS_ITEMPREPAINT 准备开始列表项绘制 - S+ T7 y0 O- u( K- e' V/ w
<P>CDDS_SUBITEM 指定列表子项</P>9 V- D$ v/ g' Y: {6 W) `
<P>表7 dwDrawStage的类型值与含义</P>
( g6 x" D$ l+ a- O<P>hdc指定了绘制操作所使用的设备环境。
& c8 Z' {+ ?( W% F8 P+ S<P>rc指定了将被绘制的矩形区域。 ; q/ W9 t0 O+ |6 [' M2 O5 ]. m
<P>dwItemSpec 列表项的索引 ; _; L/ x2 V& m5 n) A4 [3 M
<P>uItemState 当前列表项的状态,其取值如表8所示:</P>/ Y3 m; w4 E8 {" @
<P>类型值 含义
; a3 f5 ]' D( d* h1 ~/ V3 R4 B3 P8 z<P>CDIS_CHECKED 标记状态。
7 C+ h$ ]# B1 y1 J( T2 d1 K0 C9 c<P>CDIS_DEFAULT 默认状态。
* v: i# H) A( c1 \3 ^: q8 t<P>CDIS_DISABLED 禁止状态。 9 M& |* x& W+ H; m
<P>CDIS_FOCUS 焦点状态。
0 a4 p3 s& z/ {$ S: g9 d<P>CDIS_GRAYED 灰化状态。
. ~) J: G2 q( F; ?) ]+ R5 X6 r: z<P>CDIS_SELECTED 选中状态。
: W) \2 h7 q/ x* u8 b) X<P>CDIS_HOTLIGHT 热点状态。 0 n; d8 W2 Q, F
<P>CDIS_INDETERMINATE 不定状态。
3 u  J/ g: h# o4 N* y) ~; t+ t<P>CDIS_MARKED 标注状态。</P>
7 z5 ~6 A6 N- R<P>表8 uItemState的类型值与含义</P>
! G6 Q1 F" J% u. y! R<P>lItemlParam 当前列表项的绑定数据
9 `1 S% G$ s# [# O<P>pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage:
) X. U0 O) k/ t% n* u* D1 l* S<P>当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:</P>
# S2 r2 f$ M9 b* b. {2 H. e<P>类型值 含义 2 k9 h& E2 {+ ~' E
<P>CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。
* J+ d( {- ]2 i1 k1 Z<P>CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。 . Y( W% R7 z4 A" M6 x
<P>CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。
) N" L9 f9 \1 Q) q- c- h<P>CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。</P>: I$ u: P/ u, |  P4 @/ V' a
<P>表9 pResult的类型值与含义(一)
5 p8 D7 P; W/ ~$ ?$ D3 B<P>当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:</P>" O* M+ i+ }6 p
<P>类型值 含义 ; o- O1 D# c& \( ^3 M. Z
<P>CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。 1 x* s2 w# \3 P3 r+ E! [3 z
<P>CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
( N% {# g1 {. G& A<P>CDRF_SKIPDEFAULT 系统不必再绘制该子项。</P>
1 t: y* M2 M( v5 y1 g- W# C$ s; u<P>表10 pResult的类型值与含义(二)</P>$ z( I4 U7 R) R- C! W  g9 ]
<P>以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子: + |4 ?& w( j" x% c3 }  v4 e; o5 e
<P align=center><IMG src="http://vcer.net/upload/2004/03/1046650317752.gif" border=0></P>! a$ ~! u, {3 b- l3 s
<P>
5 U3 B; [  o+ E6 a* `<P align=center>图12 利用NM_CUSTOMDRAW消息美化界面 5 ~0 x( }9 ?: J5 I
<P>对应代码如下: 3 V+ I0 {0 @0 B7 y3 c1 u; f
<P><TEXTAREA readOnly>void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)6 K/ r. F2 q: ]
{2 Q9 }- i' s' R: _1 M, \2 p* b
        //类型安全转换
) i2 P3 P+ A; R/ _. W: B/ J        NMLVCUSTOMDRAW* pLVCD = reinterpret_cast&lt;NMLVCUSTOMDRAW*&gt;(pNMHDR);
0 J0 c6 I3 W( s        *pResult = 0;
$ ]9 z2 [( u5 f" @       
, Y4 g7 @) i% ?/ B$ t8 k; k. n        //指定列表项绘制前后发送消息' F5 z' A; u0 M2 I3 o7 _
        if(CDDS_PREPAINT == pLVCD-&gt;nmcd.dwDrawStage)- [3 f6 u0 X9 H
        {. e  \3 w/ }5 x7 d3 @9 K. X6 ^
                *pResult = CDRF_NOTIFYITEMDRAW;9 e( M) H$ C0 b/ \
        }
) Y/ S+ r+ ^$ s/ |, n0 d% }, b        else if(CDDS_ITEMPREPAINT == pLVCD-&gt;nmcd.dwDrawStage)- r; W- y4 P  y2 X2 Y
        {% b% X! I/ u* G
                //奇数行  `& l' A' M6 ^) l
                if(pLVCD-&gt;nmcd.dwItemSpec % 2)6 \, F3 H6 G8 D/ L* t$ _
                        pLVCD-&gt;clrTextBk = RGB(255, 255, 128);
5 L1 r- J2 u( N                //偶数行/ P- M! b; }7 E6 Z* [
                else
6 V/ T1 I/ K! Q: a( ]; J  T% y) i                        pLVCD-&gt;clrTextBk = RGB(128, 255, 255);
* n0 P5 y2 z1 N! C: ]                //继续
( P' \7 w4 u, b/ B- R' R! I                *pResult = CDRF_DODEFAULT;# M1 t5 A+ w. c! X% z
        }$ T1 C* {* ^2 Z9 O- Q# L- u# p
}
* g; [+ B$ M- y7 S3 n% C</TEXTAREA>
4 I+ d) v. u# V- z9 P<P>注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。
2 W2 J; C# D* a  A<P>6 n7 [( U2 i2 }( ^$ }
<P>
( k4 z1 n) m+ c! {! V* l0 m<P><b>3.4 使用MFC类的虚函数机制</b> 0 X- R; g% n) a5 T
<P>
8 Q. p4 d9 ~( ^5 a9 }3 W<P>
+ a4 D! O# M' B% M" q<P>修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子: % H# U& p$ A. P. S. ^' S# b
<P><TEXTAREA readOnly>void CView::OnPaint()
! R3 C& `0 J' {3 }8 X{
6 @9 ^2 E( a  `4 H, i0 L        // standard paint routine% {( ?5 F5 W: w$ K9 _
        CPaintDC dc(this);; W- H6 g! `! M8 s
        OnPrepareDC(&amp;dc);5 u0 o. `; U8 s  C4 I- @; r4 Y! x
        OnDraw(&amp;dc);
- ]5 n; `+ ^% U  B}# Z6 ^9 s: u2 w6 b8 P
</TEXTAREA> 0 W* l/ ]$ {: c' s9 f
<P>这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。 9 q2 W% z) Y4 |/ P( p  r2 \3 {  z
<P>以下列出了与界面美化相关的虚函数,参数说明略去:
5 T6 N6 a- E' h<P>CButton:rawItem % G* {- y3 v! ?% X) e) }8 N
<P>CCheckListBox:rawItem
( y2 z- x2 R' }8 H<P>CComboBox:rawItem
3 S( o* Y) r" L3 U1 l4 J<P>CHeaderCtrl:rawItem
( @1 w0 i0 `- _3 j2 O<P>CListBox:rawItem
* O- v6 u$ P5 j( k" m6 l<P>CMenu:rawItem 5 d4 \7 f  g' P" `
<P>CStatusBar:rawItem
2 ~- o6 I" N# V- F6 `4 }<P>CStatusBarCtrl:rawItem 2 ?: `8 P; m$ _$ H
<P>CTabCtrl:rawItem</P>
9 i' X# U% E: O, s1 V$ a<P>virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );
( ]7 a9 C0 L, ]- f  n8 `<P>Owner draw元素自绘函数 / [3 ^& X) z  O, m! T
<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
很好的东西。值得深入学习 " O7 g* P* X& G





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