找回密码
 注册
搜索
查看: 6157|回复: 0

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇: a" {, {4 q8 N' \& v: T2 k
  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。8 {5 K2 h" C4 `3 \" \
  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。  P) @/ @: g( |9 N  s0 ^
; K6 e& G( ^# y$ k
  2. 美化界面之基础篇. Z8 ^2 p. ?- g# }8 [
  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……
; R' Q5 z2 `, c, N/ d1 O
/ R! G5 P2 C5 Q. E3 \$ K2 Q  2.1 Windows下的绘图操作& R6 q" U. `8 o3 {% b  @! q3 n
  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……
7 W  K+ H* L+ B  X' v2 q. W2 C  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。
4 ?9 S- |# d& C+ v% [* m
$ f, b! K4 W1 Y: u1 a( e) ?# m  2.1.1 设备环境类
8 Y1 t: Z2 w: t% {6 q" |+ f2 m  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。: j; [" \) s. z! W$ H( n
  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:+ P/ s& |) X! ~+ i4 ~
  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式& Q& F/ k( _! P( ?: }
  Mapping Functions:映射操作
/ t+ D8 ~- G' ]" p8 ~! j  Coordinate Functions:坐标操作) w6 [4 Z6 @( f1 I/ j
  Clipping Functions:剪切操作
1 j1 I1 n# R  k& N2 ~5 s* _# t8 j  Line-Output Functions:画线操作
# a* J7 U% R3 a, @9 X: B& ?  q) ?) E  Simple Drawing Functions:简单绘图操作,如:绘制矩形框0 c0 `. r& q; e, a- b1 ^
  Ellipse and Polygon Functions:椭圆/多边形操作
% O) }1 Y0 K' J0 q4 m. `% ~  Text Functions:文字输出操作, ~0 u6 c2 }5 k, z$ J
  Printer Escape Functions:打印操作
8 g. t2 G5 y% W$ a/ m  Scrolling Functions:滚动操作
* z+ m% s3 q# m/ |  *Bitmap Functions:位图操作
+ Y1 z5 o! B8 c8 j( b- ~5 C# w  *Region Functions:区域操作+ f5 @! V- [6 K$ X! n3 ]( G  z
  *Font Functions:字体操作
; ]4 j7 x( [/ i  *Color and Color Palette Functions:颜色/调色板操作, K3 m) \2 t0 L, s% y
  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。, I4 ^& l3 {% G* U3 {' M" g, |$ o8 k
; S8 H! Q: G2 V- W5 B) c5 O. y. {5 L
  2.1.2 图形对象类
' k3 X" w, c2 k5 b7 m' ?5 A7 v; t6 d  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。
3 F2 t7 u. A& B  下面的表格列出了MFC的图形对象类:
1 x0 h5 N+ q2 l. d+ j# a+ _  MFC类 图形对象句柄 图形对象目的
5 g0 K2 W  K4 \# x# W1 H# j8 J  CBitmap HBITMAP 内存中的位图
4 W  S* _- R7 f8 Q! ?( K  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式9 _" \2 H8 a( b
  CFont HFONT 字体特性—写文本时所使用的字体- i) p8 F" E' ]" _, x
  CPalette HPALETTE 调色板颜色/ l; O& C* Q. L/ A, D' b+ p% T
  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细) O: C; X( o+ |( _( p; G" N( ]6 `
  CRgn HRGN 区域特性—包括定义它的点. [$ [. h. ~' b6 |
  表1 图形对象类和它们封装的句柄2 T, m6 ^3 S' `) `  \# U9 Q

5 i; V5 ]/ S; i6 M! R) ^# A0 {7 F  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:
2 j& V7 |7 [2 F) \7 i( b" b
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:6 \' H- R+ _( w) A& G
  1. , N! R: ^$ n' }) A+ D: G  f8 p
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)
    1 u9 d/ E" U; m& ~. r- J
  3. {
    + X0 F" r7 t( ^1 @  D
  4.         //设置背景色- k: L: |1 H5 M! H2 a
  5.         //CBrush CUi1View::m_Back
    7 [, Z- b- V* s' K+ ?" ]1 J
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));
    ; P! ?+ H9 S6 }" y% x
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL); 1 J6 H% ~0 w* Y' ^
  8.        return CView::PreCreateWindow(cs);
    0 u: B* E7 O) N$ ^
  9. }0 C& c1 p& b6 E! r
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)6 L5 N/ f& g+ E. |
  11. {
    7 ]. \$ c+ N0 f9 l' w( ]
  12.         if (CView::OnCreate(lpCreateStruct) == -1): V6 k7 i$ M  P/ q8 z# O
  13.                 return -1;7 {* [2 C+ f4 @/ r  i6 g
  14.         //创建字体/ f' W- m5 C( y% j  c
  15.         //CFont CUi1View::m_Font
    + O' g6 C8 t' C$ k
  16.         m_Font.CreatePointFont(120, "Impact");
    5 t1 I8 ^: V. {) o* l4 M
  17.                return 0;7 r5 Y* J: @, y; S9 t" U
  18. }
    , x5 `6 U* k, C0 F
  19. void CUi1View::OnDraw(CDC* pDC)# v; X2 e0 ~9 W- W
  20. {
    2 a9 ], y) k* D& a; M/ S) U
  21.         //绘制按钮框架
    ) L3 K! V% V  v6 l4 D
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);  Y; _. V! J# @! E3 K2 x
  23.         //输出文字7 ~! r& @, M" j" T* v. q
  24.         pDC->SetBkMode(TRANSPARENT);6 g+ ?1 [1 @1 y# X9 F/ y* M: }
  25.         pDC->TextOut(120, 120, "Hello, CFan!");. a) f. i7 O; m
  26. }
复制代码
' \4 M" X. l) |# C
  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。
) W/ d7 Q7 [2 l* l' }. m7 k3 F+ ?( C4 m  O
  2.2 Windows的幕后绘图操作 0 ^" _$ g" ^4 K) M
  在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。
2 s) E* n1 e# Q  @' l7 T& ?  有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。+ R+ ~& S4 G$ b2 P
  所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……
% G- U- i- k) d, w* z  有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。! x' U# V& y. r( h9 E$ J$ ?( I
+ T% j1 V4 j  {% X; q4 |
  3. 美化界面之实现篇
/ e2 f7 q# E2 ~( L0 c' e7 O( O  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。
1 h; P. Y; F# n. C7 h4 p( b3 X( t/ }- f% G
  3.1 美化界面的途径
2 L% s3 X' @( R% ~: o0 \. C  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:
4 q0 n9 Y$ D( R! M# {7 J' }3 P1 i  1. 使用MFC类的既有函数,设定界面属性;" |  l2 a, l2 T* |3 T4 ^6 g
  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;
( c- z) ~  p: [  }. d- H  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;0 o4 }1 N* X7 {' b- W5 a4 [
  一般来说,应用程序可以通过以下两种途径来实现以上的方法:' W2 Y8 y2 t5 a3 s, T8 f
  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;0 F8 ?  m- R. B9 k, t4 m) ]4 C: j  [
  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。
' T. g: G% N' x7 m+ p3 Q7 x7 s( l  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:
1 b3 }% }% o# X8 m0 k6 K  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示: ! R% y0 k6 d9 ?
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;
" Z2 D7 U2 C, m8 y% Z$ w! [  以下的章节将综合地使用以上的方法,请读者朋友留心观察。. A4 {) V7 |) q# T& g

  I4 q! u+ v' y, Z! A  3.2 使用MFC类的既有函数
, J) M# j- v. P  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。- @* D2 w% H& A0 }8 R) n
! e) C3 a5 F" `% y
CWinApp::SetDialogBkColor
; ]8 G* H5 @  ^void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );
( s7 Y" A  s3 }指定对话框的背景色和文本颜色。2 \: j% W! [" ]) S
( H2 M, Z; @+ _/ s. Q" Y
CListCtrl::SetBkColor- H8 F; T5 Y6 u! O, {; z4 X
CReBarCtrl::SetBkColor
9 b& `0 M% h. l% R2 Q8 nCStatusBarCtrl::SetBkColor! q6 `$ \9 K9 i" a& K
CTreeCtrl::SetBkColor
5 V+ k+ t% T; FCOLORREF SetBkColor( COLORREF clr );
* a& N' ?: k, b! _" ]设定背景色。5 w: _' Y. ^7 q( J6 m

( ]: Q2 Z; ?: d: l8 {) \+ JCListCtrl::SetTextColor
# U, q7 i' p$ u/ i. D" R" wCReBarCtrl::SetTextColor$ |5 B9 w+ {& ?0 t
CTreeCtrl::SetTextColor/ V/ x0 a6 j) @- q+ G! z
COLORREF SetTextColor( COLORREF clr );
7 L8 Y. h+ `* o5 j设定文本颜色。
) _' V& [' E) {. d& V; B' l. `, ]5 F0 M3 M% ?$ g% P# l0 _0 _
CListCtrl::SetBkImage
0 B' G' \% Z: Y6 dBOOL SetBkImage( LVBKIMAGE* plvbkImage );0 J6 C$ p0 x6 ?/ p. y1 A- Z
BOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);1 N5 L& ~1 D. W$ a: v/ B, }
BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );
6 U: }* V* x* K" ~7 ~: d设定列表控件的背景图片。0 h9 ~, p7 l- P- W% N7 p) u$ S

6 e& f: [9 @. ^4 eCComboBoxEx::SetExtendedStyle
* d( x, n5 i9 x* J. k3 p5 pCListCtrl::SetExtendedStyle0 p; m& |6 o) z6 o2 s
CTabCtrl::SetExtendedStyle
6 n; F& K5 A! J% `7 l& G9 `CToolBarCtrl::SetExtendedStyle : K; ~  h' F5 F% `" u
DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles ); . h; O9 C4 }: K4 V
设置控件的扩展属性,例如:设置列表控件属性带有表格线。
% I9 j  ~9 f* Q8 J% V6 g+ f  图4是个简单应用MFC类的既有函数来改善Windows界面的例子:
8 B9 a, K4 w$ P6 \8 [: i+ L
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:& Z) i: y8 w2 _5 L

  1. 8 }* o' ]5 t( G2 S, T2 P
  2. BOOL CUi2App::InitInstance()
    ; \- ]0 [) B4 d6 m. C7 {" T
  3. {; z, x3 \# c" j. J( F' B
  4.         //…
    8 O3 z  N$ H9 Y" ~! z% ]
  5.         //设置对话框背景色和字体颜色
    5 A1 t5 l6 l% z) \  I+ l8 x3 V9 ~7 F
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));& ^  z0 m' @; o. U9 h1 b9 t
  7.          //…
    * m% W0 s0 T) T* K0 G9 Y
  8. }1 L' q5 j8 r& w. }& W
  9. BOOL CUi2Dlg::OnInitDialog()" e: V- F* X3 w8 P; V/ ~
  10. {, B0 L  b& `9 h( }4 g
  11.         //…
    4 [- n) t: L3 Z% J
  12.         //设置列表控件属性带有表格线
    ' ^9 D! d. c8 k7 e. p1 R
  13.         DWORD NewStyle = m_List.GetExtendedStyle();
    0 m, l$ ^! P: O5 G8 a
  14.     NewStyle |= LVS_EX_GRIDLINES;
    ! V1 {+ ?+ P' v5 _9 Z# l
  15. m_List.SetExtendedStyle(NewStyle);
    & ]3 {& a8 Z) `0 R
  16.         //设置列表控件字体颜色为红色/ b- c8 k- K4 y1 ^& ]  B2 ]
  17.         m_List.SetTextColor(RGB(255, 0, 0));2 |( y. m" A" L! b; k$ U* g
  18.         //填充数据
    + S7 u6 ]0 i- m3 z$ c
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);" V9 s8 \2 X$ S- K' k$ ]+ O
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);
    ! q( h3 @+ S; ^( v# o. Q( Q% L
  21.         m_List.InsertItem(0, "5854165");  Y) h9 N" J( l& Y( b2 x! S
  22.         m_List.SetItemText(0, 1, "白乔");
    6 A9 }) H3 J: T
  23.         m_List.InsertItem(1, "6823864");5 s; P) y  O5 y# i4 `5 Y) n- i
  24.         m_List.SetItemText(1, 1, "Satan");
    3 M; g8 K9 h8 ]
  25.         //…
    ) h1 Q. Y" r0 |% U* C3 h
  26. }
复制代码
/ ]. O; w5 x4 c
  嗯,这样的界面还算不错吧? 7 m" M1 G9 G# U( ?

* w" `8 f# `7 q2 W0 ], F  3.3 使用Windows的消息机制! V" D  K2 ]" S' d
  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息:
4 ]" l1 K$ c% mWM_PAINT - l+ K/ G  M+ H& U6 Y4 s8 }0 _
WM_ERASEBKGND 5 W+ e! g- @% w/ B4 w" t
WM_CTLCOLOR*
% u/ U9 q, K* |$ aWM_DRAWITEM* ' B) H8 ~2 n+ Q$ B: l9 K
WM_MEASUREITEM*
& t1 O) z& Q* `/ T  ONM_CUSTOMDRAW* $ Z. c0 {: D/ O2 L  q; Y( o
  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。; h, B  c1 N7 d5 ]! r; ?

' _, t+ C, ~6 d* }  3.3.1 WM_PAINT/ s( C) t: D5 ~
  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。0 ?) o* c9 {/ Z
  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下:   U+ D  l2 c5 z; K3 q6 E: J5 \: A
  afx_msg void OnPaint();
# f2 \, I' M8 [5 h! j! z  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: + \1 k) U% l$ G( I' }; v
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单: . j4 ?. l; {) b0 E: v/ M6 x

  1. $ s8 l: ^# w# m2 V# j
  2. void CLazyStatic::OnPaint()
    , {' V, u; O2 J: O8 L' E) G
  3. {8 u9 l/ ~' q; v3 k* a
  4.         CPaintDC dc(this); // device context for painting3 J3 l* f  \' J" ]
  5.          
    7 H, K# o9 I% k1 _1 h; j& c
  6.        //什么都不输出,仅仅画一个矩形框
    8 w  N$ J0 I& `. a
  7.         CRect rc;4 p/ A# {! S/ F6 S; y% ~+ a" z( T' y
  8.         GetClientRect(&rc);3 ]+ A4 \0 S# n, N' w+ B; J: @3 T
  9.         dc.Rectangle(rc);
    / g" ]/ L" T, U9 X
  10. }
复制代码
  z% Q& W( o; @% ^' N0 y
  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。# r% i) q- N3 T  l) o# x

7 z& d- p" X% v+ ^  J' I+ _  3.3.2 WM_ERASEBKGND $ X% `9 Y8 O0 p) F. I/ y
  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。 4 _" c5 L8 c9 q4 h! w6 k! E! @6 P
  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:& i) ?' w+ R0 F+ t# O7 k
  afx_msg BOOL OnEraseBkgnd( CDC* pDC );
4 Z4 e3 }! {# r# Q1 [  返回值:
& s& V/ P; H5 v- M4 Z* V  指定背景是否已清除,如果为FALSE,系统将自动清除
6 |+ L# ]& n! i( b  参数: pDC指定了绘制操作所使用的设备环境。 2 P8 P7 k9 F6 \1 }$ i. J
  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景: + E1 i7 ?4 Z- _, {& v6 o, V
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单:
! e) Z* Z7 N: ^5 c/ }$ Q

  1. . Y9 u: \) K3 n4 _$ D) {& j
  2. BOOL CUi4Dlg::OnInitDialog()- z; F; h& ?$ u+ L6 n
  3. {  r) c6 |5 P. P  j' o: R* J9 B. |
  4. //…6 z  [( t! z" Y  E. {
  5.         //加载位图
    ( \  W! t4 I/ b* y+ F) q" p& _
  6.         //CBitmap m_Back;
    6 T, ]  U4 h; X# Z. k" u
  7.         m_Back.LoadBitmap(IDB_BACK);, _! {) p& {7 F  L0 R$ ?/ ?
  8.         //…1 E) N4 O" ]2 Y6 C
  9. }' ]- r' s) P( ]  p
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC) + T& s  Y: S: i1 Q- N
  11. {
    2 w( S0 G9 n% ^0 e$ M
  12.         CDC dc;2 H: \! J, J! @2 S* s2 [9 _5 }) j$ P
  13.         dc.CreateCompatibleDC(pDC);; ^5 g1 D: e+ a6 ]/ [. A) f4 _
  14.         dc.SelectObject(&m_Back);3 l, g% }9 N7 a
  15.         //获取BITMAP对象  V$ i, J7 ~  v5 |( {" D' q- ?
  16.         BITMAP hb;
    6 I3 F( \" m- @1 _7 W) V
  17.         m_Back.GetBitmap(&hb);
    - t" M5 q( a4 ]" s
  18.         //获取窗口大小+ G- M9 c2 D, U
  19.         CRect rt;8 ^9 j. g6 U# Z0 k* H" c9 ^8 E2 d
  20.         GetClientRect(&rt);/ q1 T3 @: s+ D3 X* s. R8 l
  21.         //显示位图
    8 \' W, N6 e% _5 H* h
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),; O3 b" `  K9 b5 }1 ~" c6 ^. F7 l
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);/ I0 [6 V/ D6 Y% |! o% \
  24.         return TRUE;
    $ z, l9 V7 x. ]5 o' k  }
  25. }' n" A: Y  l4 @  O$ I. o( A
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) & P/ t7 x% y" X
  27. {
    / u, g+ U/ b0 f$ H
  28.         //设置透明背景模式' o4 {' z8 I. |7 V
  29.         pDC->SetBkMode(TRANSPARENT);
    * Q. m4 G$ e3 Z- T
  30.         //设置背景刷子为空
    : F" I5 T# s* S4 a& m
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);
    - g  U; s8 l6 r5 D& ^
  32. }
复制代码
& q; C+ h, `' S. H' y& l
  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。 # e; l* h% c+ _4 f; |

5 T8 F/ x4 e! O2 C5 V! Q  3.3.3 WM_CTLCOLOR
3 C# ?7 {( l' V$ U. `  {  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。 % f" Z3 z- k, J" C2 i0 D
  WM_CTLCOLOR的映射函数原型如下: * Y! w- S  b, d' s1 M
  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );+ B  F0 b& a5 h# z( r1 m; C+ j
  返回值: 用以指定背景的刷子 5 p2 O. H5 O9 @
  参数: % u+ @6 d% a# |8 W4 [2 D
  pDC指定了绘制操作所使用的设备环境。 : G* \% Y: t9 G  @5 F* L( a- A
  pWnd 控件指针 * x/ n9 `' z' ~
  nCtlColor 指定控件类型,其取值如表2所示:
+ V' y; e: ?# I: T  类型值 含义
8 H+ E  j4 I7 m0 X# p6 r3 b4 PCTLCOLOR_BTN 按钮控件 6 H( H3 x: I9 k$ u4 r/ Y
CTLCOLOR_DLG 对话框 , D. t, u* m; O1 |# @
CTLCOLOR_EDIT  编辑控件
3 I. O6 k8 D4 x" L7 E, GCTLCOLOR_LISTBOX  列表框 9 {8 x! @; e# {. w; l
CTLCOLOR_MSGBOX  消息框
/ d6 ]% A8 J" q- F' T) o7 T1 ACTLCOLOR_SCROLLBAR 滚动条
! z6 p; N% o, B/ ~CTLCOLOR_STATIC 静态控件
6 c/ t' n, M: h% A. C  V: R$ x! }表2 nCtlColor的类型值与含义. A4 u+ R$ P3 W& Z6 e% y0 n5 j
% Q$ b* f) @, g, P" R( [
  作为一个简单的例子,观察以下的代码:
9 Y8 Y* |& d  `' v
  1. # x' \+ R9 W" x. [
  2. BOOL CUi5Dlg::OnInitDialog()* G2 @3 o, Q  N8 L5 g% l( v2 \. V8 O
  3. {- B* Z! S) ^+ M+ u
  4.         //…
    & K+ y, u) ~( v$ Z" N& @
  5.         //创建字体
    + A# s# I' W/ \  ?
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font2( |2 w8 w( i5 X, l/ G. M7 J1 G1 G
  7.         m_Font1.CreatePointFont(120, "Impact");
    - n2 b3 j0 U7 p( q
  8.         m_Font3.CreatePointFont(120, "Arial");
    7 ~/ t* a% s: F, d5 A7 ~
  9.                 return TRUE;  b% t0 \! ^! ~5 W
  10.   // return TRUE  unless you set the focus to a control 0 N# }4 Q, }5 I6 x
  11. }
    ( b  x* ^! F. a9 ~# C' d6 g! j
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    $ n6 r% j% R+ b! i; E, |
  13. {) K* ?( r& {+ ^* p
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
    . I4 U) _( v5 O# n4 L0 P+ r
  15.         if(nCtlColor == CTLCOLOR_STATIC)# h. {+ N& C3 C" u
  16.         {2 P% r2 _" W' W+ t  h% b) z
  17.                 //区分静态控件
    # J" C7 D5 _' z9 M
  18.                 switch(pWnd->GetDlgCtrlID())
    ; u* Q" q6 w7 }3 Y
  19.                 {- j( l9 s: C1 f2 d4 E" M2 G. s
  20.                         case IDC_STATIC1:
    ! m6 G6 H( F3 w' W
  21.                         {1 p! [' J8 C5 h
  22.                                 pDC->SelectObject(&m_Font1);
    : D1 l$ U" Y3 d* F' ]8 s& X/ X
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));
      _' P# |& p7 s; w. ]5 L* O3 v0 C1 r
  24.                                 break;- I; i- R: q* [" A  q2 _
  25.                         }3 ~( u4 Z7 L  R# [
  26.                         case IDC_STATIC2:
      x# [& c; }' S: |' L& L8 E2 D) h
  27.                         {
    / O* x" K+ `% i0 a' }' R# o
  28.                                 pDC->SelectObject(&m_Font2);1 D: F& I) J! b/ `4 z9 W4 ?
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));
    , c7 G0 i, }4 g7 |- @" y
  30.                                 break;# H9 N) L/ A2 ~1 b0 }6 |
  31.                         }, x" z' V; q5 O& Z
  32.                 }( a( ], _; D/ a8 I+ v" Y* B4 }
  33.         }6 I3 e: s. j" z" M" ^0 Q* \
  34.         return hbr;+ V4 j0 n) n* G8 Z/ y* K
  35. }
复制代码

# I9 T& N; W9 o* N# ]   生成的界面如下:
: @) a# o$ i  |. z/ m- r
图7 利用WM_CTLCOLOR消息美化界面

" u. t1 U1 ^8 N; u. S+ ~  3.3.4 WM_DRAWITEM
2 [' {# Q9 ~! X  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。
" Z7 V. ^7 e3 D8 K0 I  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。 ' x# Z, \' k/ h. }) r* m
  WM_DRAWITEM的映射函数原型如下: 1 T# ^& {: H8 m+ a( _0 ~1 U
  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );' c* r1 @/ \) b7 _6 Z) i0 g$ G
  参数: 8 n5 g, F  V' m' Z
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0
% l8 o1 c/ Y2 r! ]  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下:
4 k. Y5 F9 }+ N
  1. : [. w$ o0 {9 Z$ h) `# W
  2. typedef struct tagDRAWITEMSTRUCT
    4 q$ X, e6 f; ]0 b+ F( o0 q2 L, `
  3. {* l" x% ^4 U9 t6 }. {
  4.     UINT   CtlType;) G) E& ^/ I) s; X9 x
  5.      UINT   CtlID;
    + q" g8 |7 B4 R  d. [- n
  6.      UINT   itemID;
    ' ]. d3 m  U, M, G% u% `* v) H
  7.     UINT   itemAction;
    * |4 p, F; b5 ^& ~
  8.     UINT   itemState;) V" F5 B* E$ d
  9.     HWND   hwndItem;6 v. j2 N0 N( H$ b! n: ?6 o& d, b
  10.     HDC    hDC;, Z9 g1 |1 s, a5 ^9 L$ N
  11.     RECT   rcItem;
    $ q- g# p: i% K+ _$ q
  12.     DWORD  itemData;
    4 ~' P) o# t2 }& O+ R
  13. }DRAWITEMSTRUCT;
复制代码

3 z$ a" l3 {/ R0 W' MCtlType指定了控件的类型,其取值如表3所示: ; u1 b5 T# w, U* k; v6 F
类型值 含义
0 u' X' |! o6 l3 m1 o5 K5 F! }- g( JODT_BUTTON 按钮控件 % m# p. ~7 N# Z+ O# L7 ~
ODT_COMBOBOX 组合框控件 " ^4 g& X0 k6 e1 y/ W
ODT_LISTBOX 列表框控件
% c6 {6 }  @4 {( |- w1 P% \ODT_LISTVIEW 列表视图
9 t- Z% ?: f  _# V/ YODT_MENU 菜单项
& W$ U+ Z0 M! K9 g7 S2 TODT_STATIC 静态文本控件
! j  D- R& i  R1 _ODT_TAB Tab控件
1 a) c: n6 r8 _2 k8 L表3 CtlType的类型值与含义
9 _- Z9 f) |) t9 q8 L3 V! N) ^9 C7 B7 x: A
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
7 B  D' C- r& S2 j  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。
8 M) p# H2 f( ?( C# C! ?& t  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:
) R# ^" t1 A, L# l类型值 含义
. V# ^. l! F$ y/ k8 Z, R4 L5 j, \ODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。
; O  r- C& i' l3 f5 N/ QODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。 1 r1 N5 W; I! M' @- r9 d4 ^$ a1 j- I( T
ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 ' z$ Y# I; R. I# f' N
表4 itemAction的类型值与含义
# \. i5 |0 L6 {1 B& h% e  P8 w  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:% `$ i& M$ q, c# @. L; J
类型值 含义
0 y; Z$ e8 h1 z" YODS_CHECKED 标记状态,仅适用于菜单项。
, O& B+ M; i, ?  \! pODS_DEFAULT 默认状态。
+ A* N7 M. e/ C8 Q8 sODS_DISABLED 禁止状态。
! J, K6 i2 C. U: ^$ ?* R3 LODS_FOCUS 焦点状态。
1 W" E/ \% q  R; uODS_GRAYED 灰化状态,仅适用于菜单项。 ! `5 l* H, I* p. M& c
ODS_SELECTED 选中状态。 4 \+ |7 `  {' a  N
ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。
! {5 X" }  T9 E# P* u& t+ Q, cODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。
+ O4 T, f6 \) FODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。
  I; w/ b: U! xODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。
' S! P  e8 U* l& w  x; R" AODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。
. p* K9 l3 Q. z% E; y* q表5 itemState的类型值与含义
$ ]/ X% Y2 [) P( q, m& L7 v" O: B3 Y2 G  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。
  `# b% J; Y6 j" Z0 w' G9 o6 X  hDC 指定了绘制操作所使用的设备环境。 $ v- w2 N9 e/ t; ?3 e1 O( K
  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。
$ F- M; P. ~$ x; F  itemData 8 n/ O- F/ N% O* W6 J
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 ; A2 s/ k/ t# h
  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 - o# q( h$ j6 y/ i* M! H% v2 O
  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。
- n% L* K  k0 N( U1 ^6 E* _+ K  图5是个相应的例子,它修改了按钮的界面:
, ~+ V3 k/ y7 p& o  s
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下:
( v/ n# m/ y, @6 w& N1 Q+ T( u+ D

  1. " }! u9 B, i( l
  2. BOOL CUi6Dlg::OnInitDialog()+ }' i. T3 L% [4 N9 m6 a& V4 B
  3. {
    ; U3 n% z- w& h' w
  4.         //…
    3 b; z) ?' m8 T  o" ?( Q
  5.         //创建字体' |, P1 z: M) _, K" I  Y/ u
  6.         //CFont CUi1View::m_Font/ h, n; c+ s5 L' G) q
  7.         m_Font.CreatePointFont(120, "Impact");, Y$ N5 |, S$ q2 U
  8.         //…
    ) G5 C: M1 Y; _  l/ ~6 A5 J
  9. }
    , R0 w6 w1 v. f0 ]& @
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
    , t4 K. N2 p0 B5 n7 j
  11. {2 V8 z' Y8 N* t$ H& ?4 m
  12.         if(nIDCtl == IDC_HELLO_CFAN)
    $ p/ Y0 o) \5 @. J5 Z/ T2 _: `
  13.         {
    5 J) h; h2 g& W; `: }
  14.                 //绘制按钮框架
    9 _( k5 p* A7 s; N* {
  15.                 UINT uStyle = DFCS_BUTTONPUSH;6 L' V; }- f) U% {. A7 i, O
  16.                 //是否按下去了?& w8 {0 t$ H+ x9 [* X
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)! Z5 i4 J9 H* `6 h, h
  18.                         uStyle |= DFCS_PUSHED;9 Y+ {& N+ c0 \. R5 U/ q! Q& j
  19.                 CDC dc;
    6 H2 f3 L" N" N* d
  20.                 dc.Attach(lpDrawItemStruct->hDC);, j& N, C( q  E! a
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);  X! Q- b9 r) `& l0 U8 V: x6 o  O
  22.                 //输出文字
    ) T" I% q! c' E3 n* I2 x
  23.                 dc.SelectObject(&m_Font);* i8 P/ h9 M0 ^5 B: g- n" l; d
  24.                 dc.SetTextColor(RGB(0, 0, 255));
    9 I( X0 W) g+ }
  25.                 dc.SetBkMode(TRANSPARENT);0 V3 c$ u3 t  U/ X
  26.                 CString sText;7 @, t8 V  E' B# J6 ?2 V
  27.                 m_HelloCFan.GetWindowText(sText);0 c+ @& |/ _$ s$ @# c* O" A
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);. ~" c9 P4 b" {% O* Y5 U. v; V
  29.                 //是否得到焦点
    4 [" f3 q' h" {- Q4 ~
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS). z+ w5 K* ~+ T; C/ c# @: P
  31.                 {( i; m: E6 r2 ~* i! J/ c+ b; |3 L
  32.                         //画虚框% s* l" W; t+ v( }1 Z* M) j
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem; 5 q( u- D, U0 F1 f0 w' \" d
  34.                        rtFocus.DeflateRect(3, 3);: w$ E7 N4 q" ?3 J% ~
  35.                         dc.DrawFocusRect(&rtFocus);$ O: a% y+ x# |* K* A5 I0 c
  36.                 }) m; i- z( k8 Y! i7 @1 |3 v% l& y
  37.                 return;4 r# @/ f: G1 P7 D6 \+ V+ \! n# j
  38.         }
    ; h9 i( k* ~0 f4 g) i1 A* e5 N
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
    2 z- h6 |8 e7 A5 t% q9 u
  40. }
复制代码

# q* I( e9 ]/ p  别忘了标记Owner draw属性: 2 X& ?8 g& S$ B- D/ X: a6 }
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。 8 ^8 T3 q5 I' i% m0 c" m

, u7 t9 y# ]3 L. H0 L  3.3.5 WM_MEASUREITEM * M& n5 g  N; L- T
  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
; A4 }  s( _+ m; t+ c  WM_DRAWITEM的映射函数原型如下: 2 k  L# L$ v- ^1 `5 R8 Z
  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct ); - f" d4 ~. a' m* f( G) O
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l
) {5 j' |7 F9 d0 @# o( Y  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下: ) K) @% r4 N/ u# [

  1. / Z0 w4 t; L* M6 d
  2. typedef struct tagMEASUREITEMSTRUCT" J. u8 p4 \& x' {7 j8 t; y* }
  3. {4 N5 s& y) f/ a
  4.     UINT   CtlType;8 ^, w. k. |1 f2 O8 w! `2 T" C& P
  5.     UINT   CtlID;) X+ [1 z6 H( K9 B. H4 r9 v
  6.     UINT   itemID;+ h( a4 ]- V2 x1 o) S
  7.     UINT   itemWidth;
    6 a+ j5 ?! B2 V  C" B9 T! O7 d
  8.     UINT   itemHeight;
    5 P4 w; A; X  {1 q$ Z5 _
  9.     DWORD  itemData;
    2 r  q' k; ?! T6 U+ X! u1 [
  10. } MEASUREITEMSTRUCT;
复制代码

. ~3 C, d2 z% U" x  CtlType指定了控件的类型,其取值如表6所示:
/ ?- a- u# Q9 L+ w$ a类型值 含义
* E1 e0 d2 m1 ^ODT_COMBOBOX 组合框控件   b7 ?$ P: }8 Y+ V2 s- I4 ^  @7 v
ODT_LISTBOX 列表框控件 % ~. ?% K6 {  W' g9 O$ v, \
ODT_MENU 菜单项
8 K4 A! o* S8 S  u- G( o; ?表6 CtlType的类型值与含义
' G5 {0 P1 D/ N/ @/ b  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
: k* ?, J3 z5 n# X) X4 D. [( N  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。
% z  j8 b) D' J+ n+ I# t  itemWidth 指定菜单项的宽度
% s. B* |4 p3 c/ P' a  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255 6 F+ f* \; O6 Y' H' D% U
  itemData
8 e6 t6 ^+ ]2 i  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
- U# v; r( f+ y6 K  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 $ \1 K* G) D( I1 o# P6 X
  图示出了OnMeasureItem的效果: - P' y' w6 s5 d& E2 L% c. A
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下: 7 |1 |: n1 X. g4 y& ?$ b
  1. 8 H8 l) c- }2 M. Z: U
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) 5 N/ D* j+ O' T6 A  G
  3. {5 o0 Q/ b7 K1 M/ T6 l
  4.         if(nIDCtl == IDC_COLOR_PICKER)
    ; o/ h5 ?$ `" z. v
  5.         {( Z1 J: i! M3 \6 ]; v! `; n
  6.                 //设定高度为30
    1 q) g/ ~. x3 C7 M, h
  7.                 lpMeasureItemStruct->itemHeight = 30;
    # \0 ]7 K" K# h7 ]3 [3 ?3 j/ r# k
  8.                 return;0 p' @. U9 w7 [+ G3 G
  9.         }
    4 m& s' B* {( c0 E: k9 C( J4 ?
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);7 s! E: X% c- N, [
  11. }
复制代码
/ i: g# R# x  z2 ]2 ^8 `
  同样别忘了指定列表框的Owner draw属性: 2 A! ]: p% \5 P" H( \1 _. j
  图11 指定下拉框的Owner draw属性  
5 o3 h5 p$ E# h& a0 B2 }" P
' D) Z( [: d) Q& w  3.3.6 NM_CUSTOMDRAW
% j0 q4 r' ]6 p0 O. H+ _9 L  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。 4 I! `% f0 S0 H. t- d, c
  可以反射NM_CUSTOMDRAW消息,如: - r" O8 w( a3 F/ ]' S
  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw) . k) G) e) y* q+ k. o
  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); 9 t8 W8 V2 a5 B% J& J% g9 {$ z
  参数:
- ], m1 }  B0 ?4 [  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:
# X% A4 ?! v0 [: l
  1. 1 d4 \/ M/ Y5 z# p
  2. typedef struct tagNMHDR7 U) c* \2 T: S. ?; Q) r1 }
  3. {! D2 O8 I% k) S* l
  4.      HWND hwndFrom;% b4 A% a) Q# _# Y( j
  5.      UINT idFrom;% [# c+ ?7 X$ E% K$ F
  6.      UINT code; 4 J9 @1 i" G& d- ^/ _! w
  7. } NMHDR;
复制代码
3 D  J+ H* }( a# P; \* r: m& s
  其中: 5 q1 A8 K  A' T* {8 Y2 p2 S( [3 v
  hwndFrom 发送方控件的窗口句柄
; k. z! B  R6 [9 s- V- }  idFrom 发送方控件的ID code 通知代码
! @, V4 }0 L% m7 n2 [% i  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下: + Z- s3 Y# _2 k1 m
  1. ) k. O3 c+ q; P0 r3 H+ K
  2. typedef struct tagNMCUSTOMDRAWINFO& B& p7 r  P  c" G" I8 b* @
  3. {
    3 G3 ]+ g2 ?  f* ?( r
  4.     NMHDR  hdr;" }1 k7 c" y7 r2 }
  5.     DWORD  dwDrawStage;
    % G, U3 A( o% X  p
  6.     HDC    hdc;9 X6 x! R. S2 e3 H& V! K0 _/ d- {8 ^
  7.     RECT   rc;
    ) a' N! C) [5 x
  8.     DWORD  dwItemSpec;) H0 G+ ~. m( U1 c+ J& a1 r5 _
  9.     UINT   uItemState;) }3 z1 [9 ?6 r" `
  10.     LPARAM lItemlParam;) Z/ \" w. @, Y) A" m) P1 i( ]
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码
2 I0 c) [  b8 D* h4 V1 k
   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:6 ~- j& q% J! L$ ~' g
类型值 含义 0 z8 r+ _5 U1 T' H2 E" E& H2 T  Q& D
CDDS_POSTERASE 擦除循环结束
9 N) l0 z2 t# t* T; E3 x0 lCDDS_POSTPAINT 绘制循环结束 1 D8 D$ F/ T; e, B2 d# j9 }# e
CDDS_PREERASE 准备开始擦除循环
# A; x: B( i1 F  c# o; {CDDS_PREPAINT 准备开始绘制循环 9 o3 {, y; c7 w- v
CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 % Q- C% Z5 T* {* J( T" K
CDDS_ITEMPOSTERASE 列表项擦除结束
9 G3 I# G4 m: E& ^CDDS_ITEMPOSTPAINT 列表项绘制结束
, Q# w' i4 W3 w) k2 A1 V! fCDDS_ITEMPREERASE 准备开始列表项擦除 & `0 m8 ~8 g; B2 N
CDDS_ITEMPREPAINT 准备开始列表项绘制
/ _5 p- d, `# v; i" O0 o) oCDDS_SUBITEM 指定列表子项
" k! _' u" t" w# t" \1 q表7 dwDrawStage的类型值与含义
, O: L) y  b$ n; H4 c  hdc指定了绘制操作所使用的设备环境。 # n! Y( D4 N% {4 e9 y% ?
  rc指定了将被绘制的矩形区域。 ! Y3 K6 P1 U/ k
  dwItemSpec 列表项的索引
! S$ [, S: ^9 T+ T2 C  uItemState 当前列表项的状态,其取值如表8所示:
$ a' K+ Z* ?  L; N$ Q  x& T1 Z( H5 l7 e类型值 含义
" {" P9 x) m1 e* o0 P& ]2 }' ^CDIS_CHECKED 标记状态。
, U. h* N+ z. ~CDIS_DEFAULT 默认状态。 1 D; w  x/ H4 o; R* j3 O
CDIS_DISABLED 禁止状态。
# P6 @8 B9 J0 F, HCDIS_FOCUS 焦点状态。
4 U+ i0 s5 L( F. j& Y$ m  bCDIS_GRAYED 灰化状态。 & D4 g/ x) v0 {* W3 Y9 @/ \  q
CDIS_SELECTED 选中状态。
, g# Y3 x" k5 S  H8 f& x* ^CDIS_HOTLIGHT 热点状态。
4 \3 F& H" B& i  XCDIS_INDETERMINATE 不定状态。
- K1 q% ^; N) s8 g$ ]6 Z$ tCDIS_MARKED 标注状态。! H+ ]2 r0 o, X# o* }! ?: N: K
表8 uItemState的类型值与含义; v3 Y$ c. T2 \& \) C
  lItemlParam 当前列表项的绑定数据 . m0 |6 N+ C( N  f
  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: : ^7 S) X2 h* h1 A- u$ G
  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:
8 b% o/ c0 Z% e: b* r7 _1 R! L类型值 含义 & h( P; g0 A) G5 a5 Q
CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。 6 U. i& E8 g3 {
CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。 2 F, u, c& `( k, K9 A, j: ~2 S
CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。 & X6 ?. O5 y3 q2 q1 a7 g$ v
CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。
* Q8 ^2 Y. a/ f: x/ s" b表9 pResult的类型值与含义(一)
, O0 d; ~% v( {! o  `  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:5 ]8 e/ I# b% y; h4 b
类型值 含义
) }! ~7 q2 X  I. c; O4 FCDRF_NEWFONT 指定后续操作采用应用中指定的新字体。
& \6 G& k, x- x: \. r+ c: [3 xCDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
  g( p) |2 x0 l! }. SCDRF_SKIPDEFAULT 系统不必再绘制该子项。
' s) V0 g  `+ a  u4 q表10 pResult的类型值与含义(二)$ s3 e- k  w& b0 y4 i) v7 o
  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子: 9 \" g% g. l3 z" [, \
  图12 利用NM_CUSTOMDRAW消息美化界面
9 m+ g0 c; \: k4 |3 z% C- [2 j; G  对应代码如下: 5 ]* M3 ]1 w8 }1 K, N2 d. @
  1. ) Q( \2 _1 R; i7 A+ q
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
    " I6 K1 @+ H# [' C& [2 j7 _
  3. {
    + U5 u8 V( e) _: B+ K% z, {; [
  4.         //类型安全转换
    ) e( u! P) o. g* G; {) g) T6 k
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);( ?  q& p5 x1 H' b: x2 H
  6.         *pResult = 0;5 l3 A6 H5 e* \0 @4 j! a2 u" C( w
  7.         ; ?7 q( K" }- c# G% y
  8.         //指定列表项绘制前后发送消息( F* R. m) `* M* P% }1 C8 _6 F% K
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)3 ?' U) V, L+ K2 w! {9 j. e' a
  10.         {9 O4 B- s+ m- C& D+ s+ C+ e
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;, ]6 T" Z& t3 ?
  12.         }
    $ H' ]; U+ c5 ?$ y
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)* p+ _- w& q+ W& Q. h; W& a9 D, K. D
  14.         {8 D5 ?9 z7 [: J6 T
  15.                 //奇数行# b: j/ c' K7 c7 U$ L7 `) \
  16.                 if(pLVCD->nmcd.dwItemSpec % 2); K/ s& H8 ?9 x: P
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);
    $ B8 N, T+ L, P$ b9 k' {
  18.                 //偶数行( Q8 f" z# R1 I; |1 C
  19.                 else
    3 N0 h* g( b/ M8 Q1 d1 H
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);- X; ~; [0 K$ s/ j7 Y- U
  21.                 //继续5 y! @! K6 j1 {" A
  22.                 *pResult = CDRF_DODEFAULT;2 |5 i; f9 Y" p% T5 p( s
  23.         }
    ) p' o' D6 u5 y7 @
  24. }
复制代码
, K4 K' L' }$ ^
  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。 6 B5 U: L' w- L7 a6 |
6 W5 _/ C6 n+ E
  3.4 使用MFC类的虚函数机制 , I9 ~+ C, T4 V
  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子:
& _2 X/ ^! J5 B5 {; H7 c/ @# l
  1. / }0 b, X' a$ l1 S( W
  2. void CView::OnPaint()
    . w9 a2 w, m5 W/ d+ @# \0 b% U
  3. {. e! V( H/ F: \" ?* ~& U) c1 q
  4.         // standard paint routine/ O* |- l, a! y  Z. I1 C! v% K
  5.         CPaintDC dc(this);
    2 V. m( b5 M( V9 D
  6.         OnPrepareDC(&dc);6 b- R8 H, B8 S: V; Q
  7.         OnDraw(&dc);3 ^3 Z, P1 Y4 ^" Q; v
  8. }
复制代码
% l4 R5 I, M+ `* S) Y# d* u
  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。
; e/ e! S* A3 n6 }9 j8 o  以下列出了与界面美化相关的虚函数,参数说明略去:
! U, b0 `6 U" }8 ~3 v$ p; jCButton::DrawItem
6 L& r6 J' d5 ZCCheckListBox::DrawItem
! K, X3 m3 f) k/ J+ FCComboBox::DrawItem
# ~  d8 }/ f, d; r$ F" X  TCHeaderCtrl::DrawItem
! i& Y: C2 }2 O0 u' p+ V1 DCListBox::DrawItem   d  s1 }0 \! G2 w' C5 D# m1 Y
CMenu::DrawItem 4 c. Z- x& j2 W5 Y1 l" @
CStatusBar::DrawItem
: s3 k' t# t( f8 KCStatusBarCtrl::DrawItem
! t; a$ Q$ B# w1 g, `' H* U) CCTabCtrl::DrawItem
7 d4 P" |% G, c. s- K2 e! T9 ?virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );
! l+ V6 k; w; P  aOwner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。) k2 P+ Q( x# ~, x6 z0 D! q' W
  限于篇幅,在此不再附以例程。 0 D8 m' V7 E8 D9 I8 `* p- }& v
  p1 U' G, F5 m  Q
参考文献
2 d: Z4 F( w7 N4 Z. c, S/ ]本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|宁德市腾云网络科技有限公司 ( 闽ICP备2022007940号-5|闽公网安备 35092202000206号 )

GMT+8, 2025-6-19 16:12 , Processed in 0.042578 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表