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

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇6 T* V! S7 t* c0 R  G8 B: e
  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。
+ N# _/ o' B1 s! I  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。, r" N0 b! t! J1 m

" v$ O+ T& C  s7 X7 f7 A  2. 美化界面之基础篇
" {4 z; x+ r4 f  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……
; j" h0 }3 @, n; Q6 F3 q( w0 S" C" m* L$ {3 \  O, \( S0 ^
  2.1 Windows下的绘图操作
% L1 g' o7 Q  N2 a! \; F  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……# A5 V! ~0 c5 T  |0 e
  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。# m% T  `( [' ^( B
$ ^! K: D4 R0 E" C+ z/ t) Z
  2.1.1 设备环境类
% I8 ^* L6 x; s) L8 r0 e  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。
  p2 t' B- n# a, K* u& {0 _  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:
+ I( }2 T) Y+ Y: }& F: q  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式+ S3 w; B2 g8 l/ R7 P
  Mapping Functions:映射操作
! c" ^" ~( G; a( \4 s3 O4 E  Coordinate Functions:坐标操作8 |, O1 c, L& ^- q9 z' h
  Clipping Functions:剪切操作
7 z1 p& Q2 f: ~  Line-Output Functions:画线操作# A: L' B1 b8 n1 k
  Simple Drawing Functions:简单绘图操作,如:绘制矩形框3 O7 n$ h8 M6 D, P6 u
  Ellipse and Polygon Functions:椭圆/多边形操作
7 D& ]2 J/ [1 I" c9 E/ H  Text Functions:文字输出操作
6 X2 f7 H! T) P  Printer Escape Functions:打印操作. i% ~5 f& y% I8 d, W
  Scrolling Functions:滚动操作
5 p, x& V: i5 P1 J4 m+ k% g  Q: K" b  *Bitmap Functions:位图操作2 v+ G3 ]* M5 j8 F+ M4 O
  *Region Functions:区域操作
! Z( u3 i# J- z4 p1 w" Z$ s  *Font Functions:字体操作8 T$ e& K1 e/ }7 a+ D7 ~
  *Color and Color Palette Functions:颜色/调色板操作
% b5 o+ A9 \2 u8 }  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。; z- h& E* P2 }7 h/ V4 E* h
% \. r. a9 I: Z- A2 ]/ Z
  2.1.2 图形对象类
" {  c6 q0 a9 Q" L& v) f: m/ j; |  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。% s# {" [- h  e) a5 k  L0 Y
  下面的表格列出了MFC的图形对象类:7 |. E& U, B/ x9 h* Q
  MFC类 图形对象句柄 图形对象目的
; A8 E1 W+ y" t. A  CBitmap HBITMAP 内存中的位图
3 P, t6 N, ^; q7 `  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式: `5 O% A. o+ D+ u$ g  X1 O
  CFont HFONT 字体特性—写文本时所使用的字体
. S4 ~  b6 h: H9 V" k- P% ~2 u  CPalette HPALETTE 调色板颜色
7 b1 D5 t2 w3 u) R  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细
  R8 K$ A% Z; D* G; |  CRgn HRGN 区域特性—包括定义它的点3 h* x/ @+ B; k0 ^$ s, C* A
  表1 图形对象类和它们封装的句柄
' q* ~0 P1 l+ x+ @/ y% O# c# n
! q% @/ v  z- b4 Z" n) L* a  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:
- _8 O: h7 z, y7 J( i% A, p/ `/ Z
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:
* D: E0 \4 ^( Y: F3 z$ `

  1. 0 h. W  ]! B) w$ s2 C
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)
    7 J! @6 F' r2 P; `, M$ ]
  3. {0 q/ x2 E- G& [! _3 L) {% J& a( C
  4.         //设置背景色: R9 H3 Y! [7 u; v! U
  5.         //CBrush CUi1View::m_Back
    ( d: X8 L/ }' |4 X$ W7 e3 J+ ~7 q
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));5 p% F3 D& @+ A) R& v0 D8 x
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL);   K$ H4 r' F5 B1 s& n4 U9 \
  8.        return CView::PreCreateWindow(cs);
    ; b% _& }6 z$ \  k6 ]
  9. }
    ! Y  _6 H$ }6 N3 L$ P2 m
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)! L& d& W+ U$ g9 e
  11. {
      q- k) M# J8 ]% Y! Z7 o. b; V1 z7 F6 @
  12.         if (CView::OnCreate(lpCreateStruct) == -1)
    $ u: D5 L6 n' q9 s
  13.                 return -1;" N+ E9 T# j& ?( v1 N3 T8 A' G6 t1 D
  14.         //创建字体
    $ y, h  P$ S! e4 S
  15.         //CFont CUi1View::m_Font* o+ \6 A4 z* C% _% A
  16.         m_Font.CreatePointFont(120, "Impact"); & w1 G6 |' u  q. T/ o
  17.                return 0;
    : j' ]! f9 k' `
  18. }4 o& r3 n  Y1 t. X
  19. void CUi1View::OnDraw(CDC* pDC)
    $ ]' t  }2 e0 _
  20. {# O* U4 `' e, [4 H1 q
  21.         //绘制按钮框架
    9 t/ i; y5 p, ?8 C& m* v
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);/ d/ i  N3 u6 P' w+ j8 R% l" h; v
  23.         //输出文字% B* g0 O: l9 m1 b/ x8 _- J. W
  24.         pDC->SetBkMode(TRANSPARENT);
    , F: k1 A& O% p0 U0 x+ k
  25.         pDC->TextOut(120, 120, "Hello, CFan!");
    9 U  j$ \6 z: z7 W: e
  26. }
复制代码

8 o' E. I6 @3 N" Q  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。 * ]. w2 r9 Z) u% N4 y4 A' j! P
: `( Y+ ]% `, @/ S
  2.2 Windows的幕后绘图操作
- R( |) [) E6 b% V+ s  在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。, b& E, Y8 v6 |1 \0 z- Y
  有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。
  ?% M3 s- e: ^8 R, T0 v+ o/ R  所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……
# c) r1 X6 {* x; [8 N  有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。% y' c( ], J3 k; m
) I0 j) k( c0 V( l/ d4 w4 v1 P
  3. 美化界面之实现篇# g- ^+ l- h/ n0 ]0 o
  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。
' |# X1 [5 A, A8 `  G+ j: W+ B' Z( D% ~9 a2 X' K0 @
  3.1 美化界面的途径
$ m4 c2 [* l3 }( @9 y  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:
' t! r% a. T. ]. y0 B4 S8 d  1. 使用MFC类的既有函数,设定界面属性;
8 c- `4 n% n2 ?( c0 n, D& Z  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;& b3 k! T( }+ A  D- ~, B8 L
  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;8 ?7 k" S  {" ]( n
  一般来说,应用程序可以通过以下两种途径来实现以上的方法:- j1 l. c4 r7 c& H7 Q( I
  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;7 S5 Z  {5 [0 O- k& k
  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。* Q8 v6 _  h# F. S; O
  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:
+ o. S4 P, b4 c. z5 ?, G" I8 \( r  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示:
5 c5 U0 B# J. v& e: L0 M4 a5 C6 @
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;
7 O# w3 _, ~1 s1 i  以下的章节将综合地使用以上的方法,请读者朋友留心观察。
, T' L: F! n6 n1 i- `
- L8 H4 b9 p% X5 H/ A8 H  3.2 使用MFC类的既有函数* U! s" o$ k& s* T9 x& u
  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。- h5 W) F. m6 V+ d

" t- K5 t# K6 Y/ ]- RCWinApp::SetDialogBkColor
; B: w2 f9 F. _! b! nvoid SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );' u, g9 z; |  B' }
指定对话框的背景色和文本颜色。
: N6 z5 a0 Z) B9 M! z7 u% ?* p' \0 n' P& i7 z: ^3 u$ b- w9 A
CListCtrl::SetBkColor* \2 E- l! g8 m+ ^0 Z
CReBarCtrl::SetBkColor
0 V7 V( y6 j& PCStatusBarCtrl::SetBkColor2 s( Q$ I, B# |7 N# N% g. s! n8 t; X3 }
CTreeCtrl::SetBkColor7 G$ g. S5 f' l5 l4 o/ m8 ^  M5 F( f
COLORREF SetBkColor( COLORREF clr );  D) }1 U- V8 g, P3 F
设定背景色。' |1 C' |: o, `3 T; ^9 `% }: D

+ O# G6 E5 V# m0 DCListCtrl::SetTextColor' d: c) A: X* W4 O
CReBarCtrl::SetTextColor! u, G2 X3 A$ m) d* k" x$ ~
CTreeCtrl::SetTextColor
- C6 I9 Z/ |+ ^! }9 eCOLORREF SetTextColor( COLORREF clr );
. t& N7 J5 [( ~% r. a设定文本颜色。. ~& k$ M  r. h. x! c3 A9 E  W
* a- b9 z) w" K$ m9 V4 ~$ j
CListCtrl::SetBkImage
. K5 v; k. r' i* m9 g- `' x; aBOOL SetBkImage( LVBKIMAGE* plvbkImage );
% S, A7 E* j, m: B6 uBOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);
  l$ Q& i7 w  u; _2 I- DBOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );
, @; I" K1 ~2 D4 R# W设定列表控件的背景图片。
0 A- S' @' e4 [: t
0 c% S) f  A4 Q# |0 ]) oCComboBoxEx::SetExtendedStyle
3 k. T- y4 |6 ?CListCtrl::SetExtendedStyle
: N% C- t) V" w- {8 m. Y$ l: N& fCTabCtrl::SetExtendedStyle
4 V6 d6 R5 t+ B* R% i% h& U9 aCToolBarCtrl::SetExtendedStyle 2 P$ ]8 _; W  G9 v) Y/ r9 A
DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles );
- P' R9 ]* r" o设置控件的扩展属性,例如:设置列表控件属性带有表格线。
0 C4 g- @$ d+ H* M8 I, w  图4是个简单应用MFC类的既有函数来改善Windows界面的例子: ' j: T& E& e% d3 c- D
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:
- s& j5 T5 u9 t4 x( W1 p) G2 R

  1. % d6 M1 Y# D0 S! b3 j& U
  2. BOOL CUi2App::InitInstance()
    ! G9 Z, A( v: j8 \. C
  3. {. V9 U6 E2 G$ k0 }! W1 ]0 B
  4.         //…0 P  h2 i+ J' a8 u, Z( v2 ^
  5.         //设置对话框背景色和字体颜色; c+ f' T0 r+ P# |/ J
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
    1 h- L' S: {0 }
  7.          //…
    ( f% c) k" F4 o% D( e' ^& c
  8. }! b+ p1 _! `; n& R9 t' O( c
  9. BOOL CUi2Dlg::OnInitDialog()
      k: C) u  d5 x2 t4 _/ L
  10. {. B+ |; T1 ]9 G: r
  11.         //…7 j0 k3 Y8 v) Y- i  A
  12.         //设置列表控件属性带有表格线  D8 j) w9 N4 N9 V! Z' D* E7 [
  13.         DWORD NewStyle = m_List.GetExtendedStyle();
    + Y6 n! ^' W/ b/ \5 G
  14.     NewStyle |= LVS_EX_GRIDLINES;
    : W  }6 \5 E/ Y3 T7 H) J* m( @
  15. m_List.SetExtendedStyle(NewStyle);0 q4 [$ t, ?/ ?; C( ?2 R
  16.         //设置列表控件字体颜色为红色
    # V3 X( `* G; `" g4 b  P  E5 |
  17.         m_List.SetTextColor(RGB(255, 0, 0));
    8 p( w( K7 v1 I" [! E  m
  18.         //填充数据
    , Y4 I$ A! Q9 R0 d& K! N$ f
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);
    9 S4 w/ e* w( b' t
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);
    9 m% J' `8 i( x7 b+ Q
  21.         m_List.InsertItem(0, "5854165");+ m/ Y* X1 c6 e9 I
  22.         m_List.SetItemText(0, 1, "白乔");6 v( n) p2 H1 Q
  23.         m_List.InsertItem(1, "6823864");/ G" L, D2 l6 O4 \" ^$ `# }( x
  24.         m_List.SetItemText(1, 1, "Satan");$ p* i0 Y7 O; {) }% q
  25.         //…
    ! `+ h5 o! ]. \5 T% O$ V
  26. }
复制代码

8 i  y; U  z1 b; D  嗯,这样的界面还算不错吧? : `; e( j0 S8 N) F5 @
" u7 M( b% a" D, {+ u
  3.3 使用Windows的消息机制# O* D. ~$ M" q  G
  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息:
* i6 ?) B4 ]  X9 mWM_PAINT
7 A4 [! V& P6 G! d3 cWM_ERASEBKGND - p) x( z. `# |. X2 d- [' P& y9 u( ]
WM_CTLCOLOR* / Z" n& G; n3 z% z! O
WM_DRAWITEM*
7 n( b/ r! b+ c5 e2 F" Q2 O6 Q% IWM_MEASUREITEM* ) s7 H& d. [7 o5 [4 b* J
NM_CUSTOMDRAW*
1 [4 o6 Q' w! l% u1 c" D2 q  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。
' e- k0 F, F2 P/ [! C2 Y8 G9 H. T! q+ n& u$ }# X- }, e8 r
  3.3.1 WM_PAINT& y  U) W1 L( `" d5 B, A
  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。
. t0 p4 _. i$ ?  O  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下:
7 Z, E' u: [& e1 \  afx_msg void OnPaint();
" y/ I! W- o, f! n5 v5 A8 y6 e  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示:
, u6 h" {0 C* }( B  h9 B$ h& O7 ?
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单:
* N& M4 C1 c" L8 C* S

  1. * g% M  j1 S" H6 O
  2. void CLazyStatic::OnPaint()
    7 i" M5 @; s7 U/ V( D/ `, ?
  3. {
      e6 J7 I6 i- D$ i" u) b& F: d
  4.         CPaintDC dc(this); // device context for painting
    7 q3 u) V3 m$ c! B1 H# h6 ?9 \
  5.          
    . [" _% y9 E! E. d: r: T: _4 K+ Q
  6.        //什么都不输出,仅仅画一个矩形框: O; E  Z# f5 U
  7.         CRect rc;
    2 }" n2 N: s0 M; ]
  8.         GetClientRect(&rc);
    2 |- h0 [9 x% }: V
  9.         dc.Rectangle(rc);   X4 h4 q' T: O0 h- Q
  10. }
复制代码

! n$ U( e* L% I  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。
; @# E. \% Q/ f4 N( O; ?" Q& v3 t+ g' i  a0 |' g" I, `
  3.3.2 WM_ERASEBKGND
" D4 [9 V6 _1 B* c  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。 & ]) m* p$ `5 s# H/ O
  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:
# E, b" y7 @5 Q9 U; A8 ?$ a  afx_msg BOOL OnEraseBkgnd( CDC* pDC );
3 j+ z; H$ C$ D4 L# q# k  返回值:
1 U: Y* B* h+ Z( V1 J2 l' g: O  指定背景是否已清除,如果为FALSE,系统将自动清除
3 d! w+ R$ \$ G: k! l9 C. a9 R  参数: pDC指定了绘制操作所使用的设备环境。
" I. D9 n4 Y( ~" y) C$ n/ T  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景:
8 \9 P7 M! c" `$ w; w
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单: ; w0 l* R: |+ o( K+ P
  1. / U4 `% T; \2 |* O& u0 e. U
  2. BOOL CUi4Dlg::OnInitDialog()
    7 D; R0 p8 R: Q% c( G
  3. {
    6 g- @4 T) x. K' Z
  4. //…
      n7 H& s/ f) m" X, b# c
  5.         //加载位图3 q5 M5 ?+ w/ j: \" _
  6.         //CBitmap m_Back;
    3 _$ \9 \' O' ?: n# m
  7.         m_Back.LoadBitmap(IDB_BACK);1 W% T8 O5 _* O" T! u; f- w% M
  8.         //…
    4 B0 D  o6 Y, a  u- v5 o7 r- F
  9. }
    : e' b& n2 {+ \" \* g: ~2 ]
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC)
    ' s* Z; B! z) w0 l
  11. {
      l1 `. S/ Z! _6 [
  12.         CDC dc;
    & n! f4 G7 j7 G. z
  13.         dc.CreateCompatibleDC(pDC);, P1 ~; [( L" ~$ D) X
  14.         dc.SelectObject(&m_Back);
    & ]' U3 s. n& |* n, S4 g
  15.         //获取BITMAP对象% R, U. |1 m  ?4 R
  16.         BITMAP hb;/ [$ D' w% @8 m
  17.         m_Back.GetBitmap(&hb);9 y- Y; H' O* h0 [
  18.         //获取窗口大小! j. M% n" a$ E) R; w$ v
  19.         CRect rt;
    - U$ k& o$ ?) ~: |- m7 x& R
  20.         GetClientRect(&rt);: M  v9 }7 [1 i/ r( S7 h; ?- L
  21.         //显示位图
    , q3 N# O* ]: j
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height()," S9 n  ]- r, f- ~. P* }
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);
    ( }- _6 O( h2 l% M; E
  24.         return TRUE;
    - a9 ~1 m" C0 _- ^" w
  25. }
    . w2 C2 J1 x) d5 H
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 5 m9 X# Z& T) R8 [7 f& _0 \
  27. {- ]2 g  {. H/ B; P$ U1 F- K
  28.         //设置透明背景模式
    5 @% r8 {7 N7 N$ e
  29.         pDC->SetBkMode(TRANSPARENT);, W8 }( x5 K. i; W+ ?
  30.         //设置背景刷子为空1 q$ k; c* {. e
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);
    & @. X$ Z3 V# h5 [: K
  32. }
复制代码
: I& W$ }2 M/ b8 b2 z
  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。 7 T; C7 d& e6 j
9 c8 @! b9 H8 x+ g
  3.3.3 WM_CTLCOLOR 7 f1 k6 z3 `. `( P: I( H% E, T
  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。 * Q# U3 }$ `! \! i! \1 x
  WM_CTLCOLOR的映射函数原型如下:
, ~4 h6 I8 U7 G& v0 a" ]  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );
3 Z3 Q( W3 l1 Z% h* u0 [0 W' X  返回值: 用以指定背景的刷子 9 J8 n/ l# b; i9 y1 S
  参数: 2 a' }# k5 U; P0 F
  pDC指定了绘制操作所使用的设备环境。
  |* k, a9 i2 d9 E3 G5 u+ B  pWnd 控件指针 " z! M! h; `5 |9 d8 e" _: J% I
  nCtlColor 指定控件类型,其取值如表2所示:! t4 E6 U1 \6 F( B4 K6 I
  类型值 含义
# a: P, b) V/ dCTLCOLOR_BTN 按钮控件 2 x6 F7 K! p4 n$ B! u) g, O
CTLCOLOR_DLG 对话框 * ?# b6 G5 C$ V$ ]$ _
CTLCOLOR_EDIT  编辑控件
3 p) V# Z! ~3 v/ wCTLCOLOR_LISTBOX  列表框
5 H" S/ t' w! x+ v! I) _" g/ X' ^CTLCOLOR_MSGBOX  消息框
' q" y- F" f6 N5 q& s' v/ v1 dCTLCOLOR_SCROLLBAR 滚动条 : E' d0 A$ x& T2 y* A; ~( R) t1 z
CTLCOLOR_STATIC 静态控件 , `0 ?- p6 f5 {& U
表2 nCtlColor的类型值与含义
3 }* O2 d: M6 V9 m8 C$ k) \, o
' m  H: m( z" d( U5 C# w0 z/ }  作为一个简单的例子,观察以下的代码:
% Y/ J/ Z, q! J8 n/ r
  1. + L  i4 i$ x5 G) a8 \+ P  `
  2. BOOL CUi5Dlg::OnInitDialog()% a- O3 @: b( ?, q! j8 S
  3. {& }; a% K5 f9 J( p; _
  4.         //…
    & I. T6 c  M! V4 @9 @# {3 `% h
  5.         //创建字体) Z" r/ D( x. N% U8 Z
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font2
    , n; ^% y8 E$ L! b  s
  7.         m_Font1.CreatePointFont(120, "Impact");8 N' D8 ?5 U% z9 q2 g8 |
  8.         m_Font3.CreatePointFont(120, "Arial");( X' W; P; D0 m& n3 \/ C! V6 h0 X
  9.                 return TRUE;; d$ S9 h9 D( X9 V  }) m
  10.   // return TRUE  unless you set the focus to a control
    & L' U% \5 t* ]
  11. }
    & j/ V) }9 m7 W
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    3 h  F$ X8 b+ n: _  y' Z2 c
  13. {7 Q5 E5 \3 t: f; ?% N& W
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
    3 n0 Z, t! ~' e
  15.         if(nCtlColor == CTLCOLOR_STATIC)
    7 \* y- i  ?# @$ o5 q/ v+ U# M9 g3 B
  16.         {( u- Q7 j& E: j' b
  17.                 //区分静态控件
    / E0 ?: ?- \( A  N2 K1 _3 f* P! a
  18.                 switch(pWnd->GetDlgCtrlID()), {$ _% m+ o7 _$ q5 Y1 G
  19.                 {
    9 ^5 B; d$ a9 N1 x
  20.                         case IDC_STATIC1:
    8 e6 I# T3 E  a% W$ N& j$ t
  21.                         {% \9 m% \: O! _! B
  22.                                 pDC->SelectObject(&m_Font1);+ I; S, M. X8 W% \6 a% i" Y7 S4 O; G
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));" ~+ U1 ^( Z3 h; i
  24.                                 break;" e* N# x- E0 _0 h, s, t
  25.                         }9 ^+ f6 ^( v, H% }# W' F0 q
  26.                         case IDC_STATIC2:
    , m$ K6 G/ L- B( _: J& C- X
  27.                         {! s7 L$ X3 L* Y0 A, z
  28.                                 pDC->SelectObject(&m_Font2);
    2 }+ r: w4 U  q: e
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));
    1 S" b% Z& T2 r" R& o7 `5 C
  30.                                 break;! ]) z- P2 L& {; t/ G
  31.                         }7 ^9 w, d( @4 Z' Z- x! R
  32.                 }' [5 h7 F5 [; g, d
  33.         }/ a! k. i3 p; o! k. `; K
  34.         return hbr;
    + S, W4 [; O' s4 P+ A1 M3 v$ z
  35. }
复制代码

- W& S3 G. U1 o+ \8 |6 u   生成的界面如下:
; F5 Z/ f4 g' k7 a: R* T
图7 利用WM_CTLCOLOR消息美化界面

- z, P& I9 o. J" V( Z3 w' o  3.3.4 WM_DRAWITEM : n5 }- o! q# v* N: o
  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。
0 g- E. {$ t6 n) F1 o7 i: m  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。
1 U4 I8 n# y* {! m  WM_DRAWITEM的映射函数原型如下: % p8 P, T( s! }0 K# o
  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );1 U% K# t5 m3 l) e
  参数: ; J3 G* `$ n8 C* i7 _
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 : k. g# l! ?% x7 A. C' |
  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下: 0 |, @6 U! D6 V5 h, u* H0 `+ P$ ?
  1. * s, l! W1 m9 l5 X/ e3 n
  2. typedef struct tagDRAWITEMSTRUCT8 x! @; V( b8 Q% i, B% o: ]# f
  3. {
    7 n0 o# U; P- |1 V- E
  4.     UINT   CtlType;: R1 T! s% q$ D4 f7 l. K
  5.      UINT   CtlID;2 Y( B1 f4 _) {9 s
  6.      UINT   itemID;
    3 i$ H! t* h+ V9 X" ]
  7.     UINT   itemAction;
      b& s1 q; ]: o! U1 y; W4 x8 p/ M) Q
  8.     UINT   itemState;
    9 Z0 l8 Q; g5 ]& n
  9.     HWND   hwndItem;, K  V' }# q' M) f& h6 V+ B
  10.     HDC    hDC;
      _2 v* x  Z6 G6 a6 F5 A
  11.     RECT   rcItem;) U6 v. p8 i6 M! h
  12.     DWORD  itemData;
    / K( n" B  F+ o2 M/ n
  13. }DRAWITEMSTRUCT;
复制代码

. w- U3 \1 _8 @( |/ u7 gCtlType指定了控件的类型,其取值如表3所示: # o6 A$ r+ c# N& X
类型值 含义
" A' b9 _$ D. I' N8 |  O6 A$ K' j" m/ ?ODT_BUTTON 按钮控件 9 o& u" X* m8 Q- b1 K/ h1 L
ODT_COMBOBOX 组合框控件 ; _9 e+ D" P( I4 _  Q4 p
ODT_LISTBOX 列表框控件
) ~5 M5 i. K& _9 DODT_LISTVIEW 列表视图 , m1 s! T2 i9 z# R' z' a
ODT_MENU 菜单项
" C& Z& C4 s3 d% X  B  tODT_STATIC 静态文本控件
4 O1 R* @2 a. |  zODT_TAB Tab控件
% B2 p" O% V$ |7 o) q2 [  |表3 CtlType的类型值与含义$ L9 s/ {1 x. Q% t

5 D5 n1 A; `" J' A: a  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
& k3 S% u  F  H& F3 a  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。
4 N! k/ X' F, M) |+ N" Q  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:* {' Q* h0 E! m; g" N- F' P5 v
类型值 含义
) H. O! F' O% @$ S# GODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。 + S) o4 i# L- c7 a$ Y2 I) j
ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。
6 y8 v  M( P4 G, Y! BODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 & l6 A$ o7 s1 Y8 n- w+ G4 l! ]8 R
表4 itemAction的类型值与含义  M8 U* v1 l& }) `, [& @
  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:
& M' w/ }1 u4 _类型值 含义 ! M9 z- B( }2 p5 c
ODS_CHECKED 标记状态,仅适用于菜单项。
/ t/ _- t: V$ q5 q" R/ Q/ I, TODS_DEFAULT 默认状态。 , c# e' y5 {3 O* x
ODS_DISABLED 禁止状态。 1 M9 y* ]% H3 O2 d5 ]
ODS_FOCUS 焦点状态。
- I  L. L# G2 R7 p3 NODS_GRAYED 灰化状态,仅适用于菜单项。
+ B1 ^0 |0 x! a" G# Z. n. oODS_SELECTED 选中状态。 ! o' q8 [4 m6 p' ~; h% a4 {3 d
ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。
. R! k) p3 o* X! u- DODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 ! h" e5 X! x1 Q. N8 o& S
ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。
, M# s/ R4 P2 @- l( @: M' n- F  IODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。
' k) K$ h: y6 C& ~8 ?0 d; x* ?ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。   M  K5 l+ o( S$ X+ b6 Q5 s
表5 itemState的类型值与含义' |9 V4 I$ ^7 G& S9 d0 o. Y
  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。 & [( r5 S' `5 C
  hDC 指定了绘制操作所使用的设备环境。
8 [& m5 L) a% E* N+ j  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 / j4 H# H9 w$ B- ^7 Q
  itemData
$ Z  R& {  x: l# G; h  h& [  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。   v( J  T" m) g6 y7 k( ?  T0 n
  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 5 R0 U6 R( X, D4 I% X
  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。
  ~1 x/ |% x% P: @' ?* p  图5是个相应的例子,它修改了按钮的界面:
: E3 K. Z) K5 L2 i3 D$ C8 c
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下:
) w5 j2 L4 l3 q( Z% \& N4 \
  1. # t5 F5 m& v$ |8 |
  2. BOOL CUi6Dlg::OnInitDialog()
    * e4 x  x, I+ ?6 c% X1 W4 N* e, ^
  3. {- |' t) R% m1 A
  4.         //…
    - {9 _( y9 p1 u+ f4 X# ]6 K  E. c
  5.         //创建字体
    ) B& k- T9 n+ d" ?) w* U) N1 U
  6.         //CFont CUi1View::m_Font2 F7 u6 V. u/ S8 w. @, A8 F6 p
  7.         m_Font.CreatePointFont(120, "Impact");
    7 D5 A8 [9 s1 e0 f& M
  8.         //…
    ) ~) P& ]. Q% H$ Y: l, c; g
  9. }1 j/ }" G9 m# D
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) , V1 a  }0 ~9 a! H5 h) K( t6 P
  11. {
    + A& K5 R& l" @* x1 `  C
  12.         if(nIDCtl == IDC_HELLO_CFAN)
    ' S: |6 w4 b2 W5 J% ?5 X7 @  {) [/ ]
  13.         {& r) o/ M: f6 _' j/ ^( q
  14.                 //绘制按钮框架
    , i( b4 H" g6 d+ K, v( g: H  I5 J
  15.                 UINT uStyle = DFCS_BUTTONPUSH;: s- p; ?& Z: t! E
  16.                 //是否按下去了?& t  l9 k; V( G0 J3 m, `) t
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)
    . t, q2 v1 ]! B. C* C9 L$ w
  18.                         uStyle |= DFCS_PUSHED;1 K8 R; g8 c1 y" z
  19.                 CDC dc;, G) B. S# G' F$ z% ^" i
  20.                 dc.Attach(lpDrawItemStruct->hDC);
    $ k3 d; i+ b" ]/ I
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);2 [. H6 ]# V% W6 j1 o8 R/ Q
  22.                 //输出文字9 x4 `' l) f7 r% p) t- _" l
  23.                 dc.SelectObject(&m_Font);
    * K5 ~% e# ?5 k) S( |* d3 _
  24.                 dc.SetTextColor(RGB(0, 0, 255));
    % W. D4 ?/ A- T( e" }, S, n! }
  25.                 dc.SetBkMode(TRANSPARENT);2 F2 r4 E7 T1 R9 H
  26.                 CString sText;! i( t" [* \1 H( z
  27.                 m_HelloCFan.GetWindowText(sText);
    : h1 z; L! b/ W9 D
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);
    % U; D3 l0 U8 I0 ]
  29.                 //是否得到焦点
    , r& c. I. f; `7 e2 K8 o
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)6 ~! q9 Y. S" O/ K# H
  31.                 {0 ^% ]8 s3 ?* ^1 _6 N2 S$ d: l% j
  32.                         //画虚框$ N6 v1 U8 z* x3 ?* l
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem;
    ( ^6 ~' e! t# q) z! g& j
  34.                        rtFocus.DeflateRect(3, 3);' M* o, k9 T5 \6 g/ @8 g: s
  35.                         dc.DrawFocusRect(&rtFocus);1 f- W' s8 G: a' m3 N4 c
  36.                 }( U9 m1 |% Y# W5 Z- k
  37.                 return;
    # ^2 ?  o, g  l$ p" v% D
  38.         }% ]( ^% W3 V) e+ C8 F. S+ y
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);$ T; O$ F1 c$ ]1 G9 h" E/ S1 w
  40. }
复制代码
" h- ^4 o& `# W% I: O, Q7 ^
  别忘了标记Owner draw属性:
7 U  P/ @$ J# n$ O0 J. S
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。 - C' ]9 [5 {3 I9 f
+ s0 U1 u' B0 _) k* v, v3 q$ n' |
  3.3.5 WM_MEASUREITEM + S$ w4 s6 S4 g2 N! W0 p
  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
, ?0 o8 [& o( y8 p  WM_DRAWITEM的映射函数原型如下:
, Q. l) A1 [0 |5 A  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
+ D4 |4 z4 L( Q; Z3 T  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l# I7 \, F# E, Y; U
  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下: 4 _! A: O2 s0 a9 [% Y
  1. ; p/ w8 f' g2 j# }1 R: I
  2. typedef struct tagMEASUREITEMSTRUCT* e3 }) u( U; P/ }
  3. {8 b9 t) J) R- S* h7 Y
  4.     UINT   CtlType;3 Q& v) J6 I: z2 ?, X
  5.     UINT   CtlID;
    # X' ]+ \0 N. V
  6.     UINT   itemID;
    & e9 c3 _% o% U. \5 Z* U
  7.     UINT   itemWidth;' J3 \* N9 |. d4 y! u
  8.     UINT   itemHeight;
    . V6 s5 @' W) A" g
  9.     DWORD  itemData;
    & k5 U8 H* s- i+ s$ C
  10. } MEASUREITEMSTRUCT;
复制代码

' I7 C, q, w, v  CtlType指定了控件的类型,其取值如表6所示:
7 J! ?# D* v. k! K类型值 含义
1 [( M& M% L% [$ G8 U' S7 kODT_COMBOBOX 组合框控件
) d- [$ n$ q) S; t4 t, cODT_LISTBOX 列表框控件
2 K) n( J. U- B. U& @2 IODT_MENU 菜单项 * O% S; H* J& y9 l- x
表6 CtlType的类型值与含义
# R4 l- V" d& x. g8 W; L9 j  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
9 r& \* B9 F8 O% G7 w  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。
: p& V  N  ^0 B. W/ T- h  itemWidth 指定菜单项的宽度
' `' h( m, W3 M  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255 ) s) `4 [) m# o( n
  itemData
( {8 C# T0 t) P5 C; L  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
' P, C4 B  j+ D3 a, Q, y  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
! ~" L1 v* Z: N  图示出了OnMeasureItem的效果: ) P# x* |) N, n4 @
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下: 2 Q# k+ r% t& \$ @; K1 Q4 E
  1. / q8 r  ]0 b- r6 z  l6 d, l: @4 T
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) ; B- c- h* ?$ Q6 e
  3. {: `2 [+ m+ O: V4 O
  4.         if(nIDCtl == IDC_COLOR_PICKER)- T  M6 A. P0 Z& l- H! n+ F
  5.         {. C* s* \2 O+ V) S& q
  6.                 //设定高度为30
    1 f, }; G/ Q0 u1 i% N6 j# k- D
  7.                 lpMeasureItemStruct->itemHeight = 30;) V4 p$ E% b; G/ H& k
  8.                 return;' H( Q  ?, r- G% |& s3 R# X5 G
  9.         }
    " u3 S$ H5 @9 ]
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
    3 b, @6 b" q+ ]5 T" m! Q9 }
  11. }
复制代码
, j5 J0 F1 X/ ?& H5 C
  同样别忘了指定列表框的Owner draw属性: 6 a7 J( ^9 ?0 K  g$ v& ~7 h7 O5 U/ e3 D
  图11 指定下拉框的Owner draw属性  0 A- p* P6 H5 a/ {

5 |, S0 H9 S" J. U8 h3 ]  3.3.6 NM_CUSTOMDRAW
  S/ G' ]+ V. h8 X( y* }. X0 z  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。
5 n" ^0 A: L$ Z* Y* v2 F5 u5 d  可以反射NM_CUSTOMDRAW消息,如: ( N4 Q3 y5 D8 O+ k* e  g) [+ q, G  j
  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
8 r; _" G1 O0 X' x. }  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult);
+ E5 E8 K" V9 b7 g4 v  参数: , M$ p) D0 S, u8 c& h6 D
  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:   e# f$ ?8 i6 _  w' J
  1. 6 o; M" f$ O8 F! d$ X& @- i
  2. typedef struct tagNMHDR
    ' W( V, `& u( o0 |; b8 E. {
  3. {
    ) Z" _0 w6 c4 X
  4.      HWND hwndFrom;& ]9 A3 i2 K; q# ~; q+ j
  5.      UINT idFrom;/ O9 f3 t3 m& ~8 o# w4 ~
  6.      UINT code; 1 N; l- w, ]2 V* D  L
  7. } NMHDR;
复制代码
6 e& }& ]8 U4 F! h3 e: A
  其中: 4 X* q( {- u" u$ `
  hwndFrom 发送方控件的窗口句柄 " q2 z' ?8 r: L
  idFrom 发送方控件的ID code 通知代码 : P* L5 \- c0 Z' [! V$ z8 N8 x/ V
  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下: , [) q: z3 c  P. e
  1. . r7 Z8 s- J3 \" p0 j8 g/ Q. u
  2. typedef struct tagNMCUSTOMDRAWINFO
    5 Y% x; q: n0 d4 f4 c) V
  3. {
    . O9 w; e+ j  x: g& X3 i. r/ l
  4.     NMHDR  hdr;7 k0 x# V9 v2 g9 N. A9 h
  5.     DWORD  dwDrawStage;. j6 r7 F6 ~4 T. F# D$ i
  6.     HDC    hdc;- \6 a" W' p. u# _
  7.     RECT   rc;2 p* U% e( {* c% O. y; W
  8.     DWORD  dwItemSpec;) e% l& J# |7 W! \
  9.     UINT   uItemState;
    . [# T7 S. w) }: w3 L$ R  M
  10.     LPARAM lItemlParam;' S) |& M: W) f$ p, X9 S  u0 F
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码
# P/ \& d; m9 _( L' j# ]. J' n
   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:
( M4 Y5 s: i6 l类型值 含义 & |0 m* C. ?% a) m
CDDS_POSTERASE 擦除循环结束
  B/ y' Z% |9 q, V2 F5 i0 T$ rCDDS_POSTPAINT 绘制循环结束 . h8 [* a, }6 c. |# ~
CDDS_PREERASE 准备开始擦除循环
5 E, s0 r  _' p' M! Z# ?' X, LCDDS_PREPAINT 准备开始绘制循环 # @2 q! J# G6 r; ]
CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 $ ]$ o3 b8 m2 ^- W- w! [- u
CDDS_ITEMPOSTERASE 列表项擦除结束
6 Z. l) N* ?0 h7 Z3 }7 w$ v; ^/ f' xCDDS_ITEMPOSTPAINT 列表项绘制结束 , c, O4 h- o  y+ a- i
CDDS_ITEMPREERASE 准备开始列表项擦除
& F- _$ `* H7 H+ Z6 @CDDS_ITEMPREPAINT 准备开始列表项绘制
9 ^6 x0 K4 ]; H/ GCDDS_SUBITEM 指定列表子项, \% p* Z' T% ?/ H- t7 Z  H0 f: X
表7 dwDrawStage的类型值与含义; ?" S$ J7 z% V2 J: n$ w
  hdc指定了绘制操作所使用的设备环境。
) G7 O9 d) E1 r6 J* `  rc指定了将被绘制的矩形区域。
/ M/ d2 `" p9 S  dwItemSpec 列表项的索引 * @8 l3 o" V8 `& _& B$ _
  uItemState 当前列表项的状态,其取值如表8所示:. ~" {; j* j* Q7 {
类型值 含义
; t1 E& m* p) `3 rCDIS_CHECKED 标记状态。 5 h4 j) z/ C, W0 m$ @( T
CDIS_DEFAULT 默认状态。 & Q& e( f/ ]' x+ u3 h% C
CDIS_DISABLED 禁止状态。 + W+ D/ r" A/ W1 _
CDIS_FOCUS 焦点状态。
2 S7 e/ Q( b2 r7 c# Y8 K+ }CDIS_GRAYED 灰化状态。 " P; [$ g: n5 j& z6 J# H; u) J0 {! x
CDIS_SELECTED 选中状态。
9 L" _! @0 D/ d1 L9 VCDIS_HOTLIGHT 热点状态。 8 I1 `2 J: R" L0 ?
CDIS_INDETERMINATE 不定状态。 7 P2 e- a" f8 H. I! Z  w  [
CDIS_MARKED 标注状态。
8 S1 j. {. X* S1 w* X' t2 ^5 x表8 uItemState的类型值与含义
/ V5 ~$ ~0 s$ y  lItemlParam 当前列表项的绑定数据
) I2 L. }) _; U1 v* f- l- b4 c  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: 8 k; B8 C' j# G: Z% _
  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:+ t, S3 N8 e2 Y% m0 b2 `
类型值 含义
5 {5 o3 X, b! }; ]CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。 / O1 m' B! Q) X0 J* _" Z
CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。
; o" \* j/ ~$ c5 \$ f/ s6 rCDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。 : x* z: m8 P7 m% g+ e
CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。
1 n# M1 g: t2 u6 l表9 pResult的类型值与含义(一)
5 n' `7 z  I. ^9 p  U  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:
+ ?* `( r' W5 W1 f& S: x: J类型值 含义
" f2 k' J6 `! }) i( e7 x0 rCDRF_NEWFONT 指定后续操作采用应用中指定的新字体。 % W/ w( K1 K. t& S! b7 {1 o, |) w2 _
CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
8 c, T% L# A3 f( q6 BCDRF_SKIPDEFAULT 系统不必再绘制该子项。
6 X8 F/ s) h1 c- m$ d表10 pResult的类型值与含义(二)
0 l% a; B. ~/ d5 A1 N  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子:
+ ]6 `% y2 _$ F9 o
  图12 利用NM_CUSTOMDRAW消息美化界面
# H  t9 i, [& j  X0 A  对应代码如下: + {8 P! V% W: x$ P; d5 m

  1. 1 C4 h7 p8 z! n) I$ k' ~
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)( S; W+ U0 h3 M6 c: l
  3. {9 F) Q* `( S: m5 j; x. N; r4 Z
  4.         //类型安全转换& D! L3 u8 |2 ]3 `# |. ]& j
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);. D; G/ A! G: v& h5 }2 H& e9 M
  6.         *pResult = 0;
    / L2 A- o0 K. _3 i+ ?) z
  7.         - d  u% N0 v! }& m6 z# y
  8.         //指定列表项绘制前后发送消息4 O" [) X2 c* P) d+ T
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)0 @& A" a* d; R& y' o
  10.         {0 I# ^3 g: T* G& \; w% I
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;
    ; {) f8 b. E' a/ U, ]
  12.         }! T/ K/ b% T3 D. V4 ^( i+ o
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)
    4 h* c. V/ [& `$ R. ^) F
  14.         {
    7 _4 p- x0 S- ^) o' k  v6 r' o
  15.                 //奇数行2 q) A& r" |- g/ k- Q
  16.                 if(pLVCD->nmcd.dwItemSpec % 2)
    $ s+ D! G- x  V
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);! B1 v  S8 e4 F/ [; k/ b& s
  18.                 //偶数行
    - p. ~- R1 ^7 ?: Z/ d/ b
  19.                 else' x0 q+ Y6 o; M& ?2 ^
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);
    8 E( `. k2 k$ u6 O, @0 A1 ~. R. D& B
  21.                 //继续; x8 q) R% |* c- c  V0 }
  22.                 *pResult = CDRF_DODEFAULT;# g- G/ ?# L3 r" d  }. C8 S4 V
  23.         }2 c2 F5 s. C& U
  24. }
复制代码
& y) s( C, B0 e  g7 f% E
  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。
( R( y8 d- x% i, u' j* t' g, x$ V  C" g$ u) A1 B# f
  3.4 使用MFC类的虚函数机制 $ i  X  e& g, `$ Z, K
  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子:
/ v* l# E! A/ s3 b
  1. 9 f; ?+ |2 H, Z% y% F& V7 P
  2. void CView::OnPaint()
    9 a5 F5 m9 c/ Q( |; M1 `, h
  3. {' N8 G' r) G! A/ ]
  4.         // standard paint routine
    ! V/ Y$ d: P3 J8 {
  5.         CPaintDC dc(this);
    / B* z! R1 n5 g- c3 y8 ?1 m8 A
  6.         OnPrepareDC(&dc);. R8 U& ]: @1 Z  w
  7.         OnDraw(&dc);
    * \7 f! P2 r+ X$ Z
  8. }
复制代码

2 ]4 |. x, @9 \8 W3 q. P/ |# S% N  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。
: w. U% E& N  m9 S; s  以下列出了与界面美化相关的虚函数,参数说明略去:
4 M+ M0 u" K( YCButton::DrawItem : Z: N8 n* X2 O9 M, @
CCheckListBox::DrawItem
( j# y( _' c7 x. ~CComboBox::DrawItem
/ a+ ^$ i& ]7 s. N* ECHeaderCtrl::DrawItem 3 `+ a; S9 z4 A9 X) J9 U
CListBox::DrawItem
, Y# U  M7 j( mCMenu::DrawItem , l+ L+ @; r' X. G- H/ ]% V2 ?: X
CStatusBar::DrawItem 4 [( ?% G" k3 h2 x$ ~
CStatusBarCtrl::DrawItem $ C- b" R+ R( |7 E6 ~" U
CTabCtrl::DrawItem
4 }  g  Y" ^0 r  J( S" Yvirtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );
! f' f9 G8 B' T. `  ^9 vOwner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。* x# q! r: m! U  c
  限于篇幅,在此不再附以例程。 5 `5 K* @3 E- t7 }4 I% f
) j+ ]' |% C$ b5 B3 d- F: k& G
参考文献
$ `( b, r% U* ~+ \$ y本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-5-2 08:57 , Processed in 0.022605 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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