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

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇
0 B$ ?) n5 j7 _: h. E7 K  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。% W1 y/ \5 q0 k: K+ V& g
  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。; t6 v7 H3 ^. Z$ D

! K9 q, \, ~+ ^+ l' Q5 B: Q  2. 美化界面之基础篇6 Y0 f7 E" J) }' f, H/ N  A
  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……; E; ^7 N( @" G: u; L5 m

9 S' i; M9 F, {5 e  2.1 Windows下的绘图操作
2 o8 N: a$ B- G( `+ b$ h  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……6 B7 l4 W7 I& T& @8 F
  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。
+ a& f7 d0 o0 L% B! V+ A0 Y4 d! ?0 C
  2.1.1 设备环境类
2 I% [* |0 y" p3 C  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。- {& }/ O3 _4 B7 g( W% ~
  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:
' x' W, P/ G+ W, _' x! c+ K: ]  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式. `: c2 `1 W& [0 l* k2 O% M  U
  Mapping Functions:映射操作  n( r) K! p6 {8 {
  Coordinate Functions:坐标操作
2 e1 m2 ]* ?3 t! {% ]1 o  Clipping Functions:剪切操作' A8 q. V/ m( L/ ]7 c
  Line-Output Functions:画线操作
/ m/ o( B2 u  z2 }% _( ~  Simple Drawing Functions:简单绘图操作,如:绘制矩形框
2 L& y! Z" K9 e* y1 _! ^6 }, P7 m  Ellipse and Polygon Functions:椭圆/多边形操作/ Z( B3 e+ Y& d* z
  Text Functions:文字输出操作3 D! ]4 N! C2 T. I3 Q9 r, r
  Printer Escape Functions:打印操作3 d6 A* J6 e+ C# Z
  Scrolling Functions:滚动操作' G' y  ^" X) J* b" |% r* s7 v
  *Bitmap Functions:位图操作! O, v8 z( j8 z$ V& Y
  *Region Functions:区域操作
9 b" W+ S" g) v2 M8 K  *Font Functions:字体操作" [# t9 S4 A* l
  *Color and Color Palette Functions:颜色/调色板操作
' C; l) P1 ~$ x1 h  R, Z  R1 f  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。: \, E: B) B2 H: p# _  j$ s" ^
3 u& m7 a' s6 a( `& |# Q4 d
  2.1.2 图形对象类
( q' K# I$ d2 |& R, M  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。- Y* V/ t3 d& k2 i: b: ~
  下面的表格列出了MFC的图形对象类:; G: V1 m! P$ g5 b( S
  MFC类 图形对象句柄 图形对象目的, Y% A, O' {) d) a- m
  CBitmap HBITMAP 内存中的位图
3 V. E1 P7 a+ ~  D  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式; X8 o% u9 J9 _" n6 S
  CFont HFONT 字体特性—写文本时所使用的字体
5 |1 O* @% `5 ~5 }' [8 T  CPalette HPALETTE 调色板颜色" f( v. B; ?$ Y% c5 A
  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细
* D5 m/ l) p. [# H0 f% e  CRgn HRGN 区域特性—包括定义它的点
3 `0 z) K- \+ t! p. Y  表1 图形对象类和它们封装的句柄
& b4 j% S1 u3 b; a5 V! D* X6 S* K, Z& X' h
  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:
, d3 i: ]& G' ~  f
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:& ~; D  }$ J* L( ~& }
  1. 9 V' V8 e. J) A0 r, T
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)
    ) h/ i! I0 v& M! H" s$ Y
  3. {& o% I9 y' H  [# w; n6 k- B& m6 Y; }
  4.         //设置背景色8 i; {: X( z5 j) x
  5.         //CBrush CUi1View::m_Back" \6 Z  w5 |" V2 Y) r
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));: A% W( {+ ~. A" v/ t
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL); : }$ k$ ]% T" E4 C6 l
  8.        return CView::PreCreateWindow(cs);
    % p# ]3 }7 c& @
  9. }6 v  G$ x+ N7 Q8 Z3 U+ x" V
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
    : o# S9 l/ R5 @$ T$ q4 |
  11. {/ ?9 e6 F" T* U' t$ `7 q$ h3 ]
  12.         if (CView::OnCreate(lpCreateStruct) == -1)+ P+ Z2 ~9 M4 P; H( v( \4 I
  13.                 return -1;
    0 q  d' E! I: J/ N4 M  D# }
  14.         //创建字体. @/ E  j+ l0 u$ k# w0 O
  15.         //CFont CUi1View::m_Font% v  {' v% E. p5 C1 `
  16.         m_Font.CreatePointFont(120, "Impact");
    2 [0 f# i( j1 {) @. M
  17.                return 0;
    ) t: |8 q  M& B' \8 N
  18. }
    # d( J; h4 w' [$ M/ A% x- p
  19. void CUi1View::OnDraw(CDC* pDC)! f5 y) r, U4 Z2 n3 H  o
  20. {& `5 C# U# K9 A. p3 k+ m3 g  P
  21.         //绘制按钮框架
    5 q( {6 E* J! d. q6 S8 y  c
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);) Z: H: G* _, f) G8 d; P
  23.         //输出文字& O8 l( V; v4 U
  24.         pDC->SetBkMode(TRANSPARENT);8 c: N+ i8 A; @6 ]5 g5 r6 T
  25.         pDC->TextOut(120, 120, "Hello, CFan!");# T3 A6 Y6 e  E% c; W5 G7 D+ H
  26. }
复制代码

3 n. n: W, I( l( [$ p" h. q  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。
! b2 K! _$ e9 y
  l3 q' S' s, N: t% k  2.2 Windows的幕后绘图操作
- N- y9 R6 a- j. u  g  在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。1 V4 q, K$ U+ L5 ~2 Y( I% y
  有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。, D+ C- |6 o$ t$ Y; U
  所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……
% A' e" h: H/ }- W+ R4 _/ ~4 G, f  有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。
! R( N1 A2 q4 u5 S) z( Z2 {
" @. {. N' V9 P2 [* C/ r  3. 美化界面之实现篇( p. r" k3 e6 N2 D/ m
  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。0 D7 q9 Q  T% x
7 y8 Z' w1 i* c9 z7 h7 O
  3.1 美化界面的途径
  T% k  `9 a# O. i8 [  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:4 f; C/ C  D* M" \/ V: ^- ]1 {/ W
  1. 使用MFC类的既有函数,设定界面属性;
/ K, b! ]3 t3 O% a2 g; P2 g  O! E3 {  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;: s! W. U0 Z4 W- U( l; j# [2 I
  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;8 M- T- F' g$ s. Z; I; {9 C
  一般来说,应用程序可以通过以下两种途径来实现以上的方法:# Q/ x; O+ w% F/ a8 P
  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;
0 T% A+ ]8 t' Y+ B9 |! y  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。
1 Z) l" u/ m* Z5 E4 m3 K5 \- L/ z* c  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:3 R) S8 j6 `0 C! U- n: N
  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示:
& i  B' \3 f- O( Z  J9 N( M
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;  X+ O1 W8 l' @( O" |
  以下的章节将综合地使用以上的方法,请读者朋友留心观察。
6 D$ E+ m* c) q0 p/ L
5 N* l8 j( u6 O' m( K  3.2 使用MFC类的既有函数7 s# g% ?; c8 f% J
  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。" J8 @& J6 Z$ t% y
6 w8 g; p0 A! @& {. s
CWinApp::SetDialogBkColor) r& F% c# s* w, \  @/ N$ @$ {
void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );! A: _4 f4 [! W3 ^# H' m6 x
指定对话框的背景色和文本颜色。
6 e/ [) l0 ~) O/ [' o  A0 C$ v+ N3 n
CListCtrl::SetBkColor7 ^; d1 J0 ~; z- z) Q
CReBarCtrl::SetBkColor% N7 h# m7 O+ q
CStatusBarCtrl::SetBkColor8 l- E8 Q$ K2 N0 |$ j* k# |
CTreeCtrl::SetBkColor
/ \8 ?0 ]5 n/ [! LCOLORREF SetBkColor( COLORREF clr );. ~/ i+ t& |( n6 ^7 Y. p+ V( J
设定背景色。
$ N) [7 o+ v/ T. P' `. m! y) w, X" l" I9 R$ Q4 s
CListCtrl::SetTextColor6 }% _( M) d- |9 M
CReBarCtrl::SetTextColor
. a* A( O8 l. rCTreeCtrl::SetTextColor
& v5 C/ K% w9 hCOLORREF SetTextColor( COLORREF clr );' X" p& m* Y& E+ J5 ~
设定文本颜色。
; g! v# h7 Z) z& T! |0 q! x" X  W8 c: u5 P
CListCtrl::SetBkImage9 \, A* X7 F+ o5 S+ n
BOOL SetBkImage( LVBKIMAGE* plvbkImage );) i, D* |0 P0 w0 I# V
BOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);
' m. I% h5 r1 r2 B2 V, e/ KBOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );+ x. ^5 y8 ]9 Y
设定列表控件的背景图片。+ F" f9 v( H. W- u9 l" \
7 }9 S7 f) c' n8 U
CComboBoxEx::SetExtendedStyle
# l! ?6 B- ]' @/ iCListCtrl::SetExtendedStyle. n1 F" Y  l, J9 c$ v
CTabCtrl::SetExtendedStyle1 U5 w' R: ~; d" H; K
CToolBarCtrl::SetExtendedStyle
7 W  O  N8 N1 Y( rDWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles );
1 u  P; Z# W8 V; x+ m设置控件的扩展属性,例如:设置列表控件属性带有表格线。
* O+ a- E( Q/ }$ e1 b& t+ s  图4是个简单应用MFC类的既有函数来改善Windows界面的例子:
% f5 k$ D0 z5 S
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:7 W' O6 }5 F" g2 g; B: M) Z" g. L
  1. 9 R* U1 X4 m% j- _! u
  2. BOOL CUi2App::InitInstance()
    1 @/ _8 }8 s+ {' e+ a( s; v
  3. {( B$ o8 a1 H9 R) ]0 |5 a
  4.         //…! u' t* f3 z  {' Y, `9 W' I
  5.         //设置对话框背景色和字体颜色0 H4 p, {( G) `2 y( ?1 ~; F
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
    $ {8 e8 X: F( X7 ^
  7.          //…$ b! c* x' A1 ]! z" J6 o' H2 U
  8. }$ P  o  o6 y* k" V" L% K( M9 M
  9. BOOL CUi2Dlg::OnInitDialog()
    8 D( k$ L. p% G
  10. {* |7 E2 h9 R8 k( W) A
  11.         //…
    / l* N$ v$ s5 S  x7 k
  12.         //设置列表控件属性带有表格线
    - d. b/ P6 D$ B- ]5 j1 v4 C3 d
  13.         DWORD NewStyle = m_List.GetExtendedStyle();
    ! g3 ]. i, N$ L& E! M& k: n4 a
  14.     NewStyle |= LVS_EX_GRIDLINES;
    % Z/ T# T8 j& G5 z
  15. m_List.SetExtendedStyle(NewStyle);
    # G0 u9 {) Y/ B
  16.         //设置列表控件字体颜色为红色
    6 z) u5 t/ i; k1 R5 M
  17.         m_List.SetTextColor(RGB(255, 0, 0));
    * z' U( k5 Y/ F" I
  18.         //填充数据
      j) O( a1 R' d* Z, [
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);. j; p3 O; L/ }9 x/ d1 K: j7 Q
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);8 y8 }. W$ t  v- ?
  21.         m_List.InsertItem(0, "5854165");
    6 }) Z6 h% M8 O" I* b! }( o+ S9 \
  22.         m_List.SetItemText(0, 1, "白乔");. j; |" t1 Z1 d: Q/ I
  23.         m_List.InsertItem(1, "6823864");
    8 m7 d5 `" \2 q. y" }
  24.         m_List.SetItemText(1, 1, "Satan");
    : E$ m( ~8 Q( h: H! n
  25.         //…
    . x% ^2 C) B: o: C+ X
  26. }
复制代码

  }6 A9 ~+ W/ a# R) ~6 b  嗯,这样的界面还算不错吧?
8 J! Y4 F3 e6 j8 }. c" T/ q2 P/ i$ y
  3.3 使用Windows的消息机制4 g9 W; e8 A- c2 G) i
  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息: ( B$ M: V* U, Z! N( k. }, _
WM_PAINT
2 B$ j( F- I' K( L7 FWM_ERASEBKGND . h/ v% J( L4 V' s* `# o
WM_CTLCOLOR*
# w& {1 ]% H+ O: SWM_DRAWITEM* & I  w3 e% S& ^8 p0 b: b6 t
WM_MEASUREITEM*
% T( e0 S3 ?+ O3 W6 L: U6 BNM_CUSTOMDRAW* ' j* X- ~* c* w+ I; J0 `1 o
  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。& j( s9 V* L1 {; c4 @
9 N& A6 A2 ]; b# e, N$ H5 l
  3.3.1 WM_PAINT* W. ?2 @$ _7 S+ C7 F! k
  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。7 T/ p/ G  s7 d. U% [4 C+ t
  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下: 7 T7 d! a' P0 q) y9 W4 N
  afx_msg void OnPaint(); 1 U# ^$ j" R6 D( B* b! W
  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: : j! h* @) e: G3 u
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单:
" T6 e, D% |8 S" y

  1. % P& H% @6 f/ P% }
  2. void CLazyStatic::OnPaint() $ D2 j& |) V1 a$ }( g
  3. {2 W  K! @* e" a( d7 E: U* e) U
  4.         CPaintDC dc(this); // device context for painting
    6 V- T6 e& C" U
  5.          
    5 d, \3 M+ F+ e+ R
  6.        //什么都不输出,仅仅画一个矩形框
    4 E- H$ V. B3 [' G+ X3 S3 ?& ^
  7.         CRect rc;
    1 V. p  N) B' `
  8.         GetClientRect(&rc);. Y3 `6 k$ V1 D( u- i
  9.         dc.Rectangle(rc); , C. S! W8 p) {# {2 @
  10. }
复制代码
* h, l* K4 n% t# ]6 J$ S
  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。/ I* Z- b8 z5 B+ x) T- @5 m
6 s) [! G$ h- l& @1 k8 v! Z" y& B
  3.3.2 WM_ERASEBKGND * p/ M* g# Y7 t* J
  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。
; i8 f2 T' y# Q  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:( S. N4 `! v1 @; G1 x8 {
  afx_msg BOOL OnEraseBkgnd( CDC* pDC ); & X! i# m/ z* Y7 d# E7 S+ I
  返回值: 3 g: W: T% n+ K) |& ?8 T
  指定背景是否已清除,如果为FALSE,系统将自动清除
/ v0 G3 x  N- n& ~0 v  参数: pDC指定了绘制操作所使用的设备环境。
4 a7 @- Z. r# S4 ]  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景: . e& D+ A- s3 @' L' l9 ~2 Z. n
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单: ( s6 \4 t8 ~/ n, E; b, M
  1. / m( i" m( U( O( s
  2. BOOL CUi4Dlg::OnInitDialog()$ T; N4 h+ n+ t) q; J- T) G
  3. {- y3 ?' Q( @1 r! W& i3 K  S; G
  4. //…' ]) m: z$ R4 u' b$ [6 B4 }2 |# p9 K
  5.         //加载位图
    , }; s, I* ^2 ?8 Z# c
  6.         //CBitmap m_Back;5 A5 V9 q  M% V3 A/ O% E5 E4 d5 x
  7.         m_Back.LoadBitmap(IDB_BACK);
    " t* i9 O5 T, j# f8 g. }
  8.         //…( W/ [8 O1 s9 b$ S
  9. }
    8 W: l7 i! z- |7 z
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC) 1 f  j7 k6 k  \' p, t( K) s
  11. {
    * w$ o7 g; h3 A9 {8 z+ s4 ^2 f. f
  12.         CDC dc;
    5 I1 K: R/ A# H, X: c2 P/ n
  13.         dc.CreateCompatibleDC(pDC);
    " |" ]2 X) S- j6 L2 V
  14.         dc.SelectObject(&m_Back);6 B; R$ ]% w1 w7 Y
  15.         //获取BITMAP对象
    * n) A9 }7 j$ U6 E6 W0 m' ?
  16.         BITMAP hb;
    1 k* E3 m6 Y( [- }
  17.         m_Back.GetBitmap(&hb);  _7 Q0 J7 e9 M2 I; D6 D2 q
  18.         //获取窗口大小
    4 h3 e/ Y' h) Q( G, Q
  19.         CRect rt;
    : l/ K. X" c7 g4 w* U8 i+ p
  20.         GetClientRect(&rt);
    * w+ K& V0 Y2 l" X
  21.         //显示位图
    ! I; G1 ?) X6 ^- {& S5 j! Z8 n% Y! I
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),
    - m% z" K6 ]6 c, m0 s' J6 C) y: [
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);- n( S0 J' g2 [. r* M& F: O
  24.         return TRUE;
      |3 F4 F8 Y2 O* ^6 B; g
  25. }+ g1 c# A" |# V! v. B& T
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    : P' b8 d& J" J' p! K
  27. {. x& y1 P; {5 d' K& V
  28.         //设置透明背景模式
    / a' s/ f* [5 A0 ?3 P, @
  29.         pDC->SetBkMode(TRANSPARENT);* \9 K! M) ]( k; P2 i7 v& C, H
  30.         //设置背景刷子为空
    % ^$ k+ i: C2 o: s
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);
    ! x& z+ E  g' s1 q/ o0 ]
  32. }
复制代码

# }8 O6 p3 G, B9 U& q  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。
3 W8 ~! u6 z6 g$ u& m9 Y0 c" U! p* p
  3.3.3 WM_CTLCOLOR
6 V6 ?) G1 P( ~1 K7 q3 D, e  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。
( \+ {- A- [1 M7 [* |9 D) y6 \' H' e  WM_CTLCOLOR的映射函数原型如下: , J. y, K" A3 u: [, C
  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );
; U8 }) n+ U, w. j) [  返回值: 用以指定背景的刷子
& F& r% r5 A2 i$ i  参数: 6 w3 W9 z7 o5 Q0 g1 w
  pDC指定了绘制操作所使用的设备环境。   ?' a/ t# l3 M7 h
  pWnd 控件指针
! W) b' z. o2 P) K/ Y+ Q$ E  nCtlColor 指定控件类型,其取值如表2所示:6 |$ {/ ?. C5 a5 k2 w
  类型值 含义 2 ~, L6 e, \( H% J
CTLCOLOR_BTN 按钮控件 % P& ~  Z5 G; P3 R4 \
CTLCOLOR_DLG 对话框
$ g. z( V+ \& Y+ j: LCTLCOLOR_EDIT  编辑控件 $ E2 Z& d, V) P8 ]
CTLCOLOR_LISTBOX  列表框 $ E: N& \8 \! N. @
CTLCOLOR_MSGBOX  消息框
5 D. L! T; ^+ tCTLCOLOR_SCROLLBAR 滚动条
/ Q3 p5 r) v. t9 U, J8 WCTLCOLOR_STATIC 静态控件 $ M) b0 f6 E. [# k; S; O1 F) V/ N9 y
表2 nCtlColor的类型值与含义2 F3 w% I8 l6 q" S
# i( E9 d/ V% r' R8 ?2 r7 W
  作为一个简单的例子,观察以下的代码: ' L: }3 W+ z* _7 j

  1. 9 r/ |8 J1 Y* n) w/ s4 _8 d  z; u
  2. BOOL CUi5Dlg::OnInitDialog()
    / N: b: {8 G$ g  H0 @
  3. {
    $ o. d7 O' e0 n, x6 d# ]0 e9 Z
  4.         //…4 D0 N1 _% N6 P8 s7 w& V
  5.         //创建字体
    $ p+ M/ L- T' n3 j7 r) L
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font24 E  {* S/ ~% o8 b! A4 \. L5 u7 y
  7.         m_Font1.CreatePointFont(120, "Impact");
    7 ^8 B" U: M9 K6 c* E6 a& V
  8.         m_Font3.CreatePointFont(120, "Arial");
    * S, u6 x7 ?) s2 V3 L8 P
  9.                 return TRUE;
    2 O' G! C4 y+ t
  10.   // return TRUE  unless you set the focus to a control , |$ q6 b, @; b1 l  {, m5 T! D1 O/ X4 O
  11. }
    4 n4 S+ v# v5 ]8 A4 e7 Q
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    1 i6 G2 n9 }) d" Q& s& E( d
  13. {- k  Z! H5 C' V5 P
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);/ Q0 w+ a0 Y) p9 i0 A; L
  15.         if(nCtlColor == CTLCOLOR_STATIC)
    - r: C* w# X3 C0 M- x$ L
  16.         {
    9 h2 \3 v6 u3 L" G
  17.                 //区分静态控件+ u3 O1 x# D- b
  18.                 switch(pWnd->GetDlgCtrlID())$ I6 m6 p* t# `) p: w% u
  19.                 {4 \% o/ v6 z# I8 i; ]
  20.                         case IDC_STATIC1:
    & S2 r; r6 X9 P( p
  21.                         {+ u7 m& N! @1 ]0 G6 I9 m  u; \
  22.                                 pDC->SelectObject(&m_Font1);
    5 K. n7 Z, |$ _, ^# k
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));
    ) q/ i* P! r& h1 g: `
  24.                                 break;
      n5 M. \+ C# j) t) [
  25.                         }1 J/ S3 {) i' H. g& I  y
  26.                         case IDC_STATIC2:
    + V$ G( y2 a$ h8 r
  27.                         {8 C0 b7 r1 w1 W
  28.                                 pDC->SelectObject(&m_Font2);8 X% I  }  O- v8 u& A  _
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));
    1 k+ O. a9 M0 T# @: k
  30.                                 break;
    * }% P' b9 D5 ^  Q
  31.                         }$ K( ~4 M- P- O/ H$ g
  32.                 }3 u+ g% b; ]2 M( N$ R# ]
  33.         }
    3 q3 c  x% A7 T" E2 \! a- _$ |! D2 `
  34.         return hbr;  D  E$ k  i! ^0 F
  35. }
复制代码

& }- h% H1 B9 Z# W* R1 h! f   生成的界面如下: & N0 `: }" }8 u3 V! I
图7 利用WM_CTLCOLOR消息美化界面

& p3 d3 T# O; d4 [  3.3.4 WM_DRAWITEM
% d- e, a7 @1 p6 M% h& u# [( Y  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。" V# N1 a5 r; q# H2 v! w, p% S
  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。
! ]* |; C& `. n  WM_DRAWITEM的映射函数原型如下: , F  L  C6 E* [
  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );& Y. p2 L: _2 J
  参数:
9 K4 e7 T9 N& v  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0
. p0 _( K  j4 _; Y9 ]2 ?  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下:
( @( S, p2 O) k2 U: R& `1 S+ X- Z. @

  1. , Z+ ^& F" w6 T" u8 T
  2. typedef struct tagDRAWITEMSTRUCT# N' x. T8 ~) p% C" @7 R
  3. {/ T2 r$ A) T2 Q# F) _
  4.     UINT   CtlType;
    / q) t# q6 C, X- G
  5.      UINT   CtlID;! }' a4 P* K7 |- ?; G
  6.      UINT   itemID;* o. E% W+ ~7 D
  7.     UINT   itemAction;" |* H+ z% i- V$ d1 b
  8.     UINT   itemState;- M, }8 x: c& P
  9.     HWND   hwndItem;
    " T  Q! n2 W  o+ I
  10.     HDC    hDC;( ~4 D) t6 |/ q4 m
  11.     RECT   rcItem;
    7 C' N' Y' G/ N
  12.     DWORD  itemData;9 g" ~; g$ R! d- H  t& c  Q' e  P
  13. }DRAWITEMSTRUCT;
复制代码
1 h; @! X8 v9 \/ ~1 g
CtlType指定了控件的类型,其取值如表3所示:
5 K8 J, u; x. J3 Z类型值 含义 + W! T7 s* y: ~6 [: D
ODT_BUTTON 按钮控件
- p7 }" Y2 u5 N* k  {* S6 s! nODT_COMBOBOX 组合框控件
$ `  e5 ], ^; K( X8 k  v+ mODT_LISTBOX 列表框控件
' p$ t6 f' V6 dODT_LISTVIEW 列表视图 ; p8 ?# {, v0 y: P+ b$ ~
ODT_MENU 菜单项
) m6 U/ L5 D( e" h, oODT_STATIC 静态文本控件
4 P* g1 \" A- V7 y# ]+ Q0 AODT_TAB Tab控件 + o" u1 ]2 M, V) A
表3 CtlType的类型值与含义
7 `6 ?$ X0 y/ f! A6 Y) a% Z* }7 J7 M. H
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项 2 W4 n  z3 S4 b8 e; {- b0 i
  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。 & R4 ^: \3 ?7 \$ l$ a4 [; n
  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:& B% w: C6 L5 {$ I! A! C$ c
类型值 含义
/ w/ _( R% L# }; q, b( t- x$ oODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。
' G6 ]* T( ~/ }# N4 nODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。
2 ~5 ^. ?( l6 R; a$ l6 N7 K! QODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 & ?0 y  h$ U4 F. W: T
表4 itemAction的类型值与含义5 y+ W& }5 f9 Z% x$ F  z8 f/ J( ^
  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:
+ ?' S3 `  k0 L: D类型值 含义 ; w2 m" W0 X( j
ODS_CHECKED 标记状态,仅适用于菜单项。 * C( V2 o, P# a' u
ODS_DEFAULT 默认状态。 9 j' M  Q* p# W) r
ODS_DISABLED 禁止状态。 5 a# V" o, @6 U9 |  Z
ODS_FOCUS 焦点状态。
  C7 O# E8 e; Z8 ?ODS_GRAYED 灰化状态,仅适用于菜单项。 * |, ^3 o4 {* {5 w: _; \3 K
ODS_SELECTED 选中状态。 % @* E: A/ n6 a$ t6 J
ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。 ; b$ F' `3 W+ |! E/ j7 @0 k- `/ ]4 a  j
ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 1 J$ d' T" F5 ?  L$ l; @- i" D: D0 }( R
ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。
; i4 ^. a" {* G4 uODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。 : ]; \: J/ B2 ^
ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。
" T/ Z2 R& }1 V6 N表5 itemState的类型值与含义' ?* B; I- ~  y$ t! @9 g4 n0 H7 R
  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。
* q5 s' y9 z, {, T- D" x9 t& C  hDC 指定了绘制操作所使用的设备环境。
' U% |) G& Q7 ?7 b& G8 T- p- H. w  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。
/ U% ]4 T# _$ {! E& S8 Z# p  itemData ! h( g$ `) B, n* A8 d. I) u
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
1 h. Q# g4 d# l: B  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 % y6 G( T: g* c! s  p$ I
  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。 - l5 T) m" i1 u; ]5 S
  图5是个相应的例子,它修改了按钮的界面:
# j( L; |, P* N) X# I
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下: , I6 r4 E/ [, j
  1. 7 E% k3 q, z4 ]5 ~5 H/ B- W
  2. BOOL CUi6Dlg::OnInitDialog()6 a6 a' n- C  B. t1 G6 x' p  r9 a
  3. {# i  d1 ~4 u7 J3 `  _
  4.         //…
    : s, r* n/ L; J$ s  a
  5.         //创建字体- O' R, d5 y7 c5 I- }$ w
  6.         //CFont CUi1View::m_Font4 [0 J5 U, z/ m& P5 M
  7.         m_Font.CreatePointFont(120, "Impact");" A4 q5 |( ?# }7 u+ W- [
  8.         //…; L& Q$ }' \" Q$ o8 b5 J
  9. }% S) H9 G6 a3 g
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) # z. g$ U& S. g) m9 g* I8 W
  11. {* o% H) v" S% ^8 U9 e7 @; R
  12.         if(nIDCtl == IDC_HELLO_CFAN)
    0 A" X+ ^; {7 K2 X
  13.         {: n( I  D* ^. K! S
  14.                 //绘制按钮框架) J' ^( i  F  n- X( a) W% U6 I  H/ i
  15.                 UINT uStyle = DFCS_BUTTONPUSH;
    * o: E- `2 k9 u2 F) z5 d! _" x( n" \
  16.                 //是否按下去了?
    8 n: E% h" d) B! o& d/ A, B8 V9 h* r
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)  i9 T; m& S) l1 g! A
  18.                         uStyle |= DFCS_PUSHED;  S6 Q3 j/ S: D6 q0 q( i- k
  19.                 CDC dc;
    " s& B4 \: I+ ~6 ^# e0 B6 f# z
  20.                 dc.Attach(lpDrawItemStruct->hDC);
    4 `: N+ ?9 ~; D- V$ M# ?
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);( F+ H3 {$ W# t2 }# h. Q0 A+ d
  22.                 //输出文字
    3 F3 v7 q* h2 [7 G
  23.                 dc.SelectObject(&m_Font);6 A8 r9 Q% b& b6 t0 o
  24.                 dc.SetTextColor(RGB(0, 0, 255));
    8 }& Y. U: F7 }* t: O
  25.                 dc.SetBkMode(TRANSPARENT);7 J! T7 o- K( I( c
  26.                 CString sText;
    9 y/ a7 i: K8 ?# d) R
  27.                 m_HelloCFan.GetWindowText(sText);
    % \, c1 d2 c9 ^
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);$ J/ F2 r& u/ ~; S  A
  29.                 //是否得到焦点
    9 T7 A8 k+ G" B2 m" j3 h, X
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)
    3 \0 A: V& |4 _9 Z
  31.                 {: I! k$ A- {+ c
  32.                         //画虚框7 r  t) _8 u- x! `+ t
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem; 6 t2 x3 }$ l$ {) R7 t0 m$ \0 N, y
  34.                        rtFocus.DeflateRect(3, 3);7 o  T# _8 G" t, Q% [
  35.                         dc.DrawFocusRect(&rtFocus);% _0 X3 s: B. B1 I' j9 i- y
  36.                 }, u) T" j( I! n: j, F. C
  37.                 return;
    4 P. f1 e1 N; c. T, M- R) {; l
  38.         }
    2 }6 d* R' @+ |
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
    ; Y0 E& y- @$ _9 \) R# J4 b# ^0 c+ ]. V
  40. }
复制代码
4 G# P5 y8 y4 X& j
  别忘了标记Owner draw属性: 3 {( H, W' [- K4 c, ?
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。
3 ?0 P$ R5 }8 [7 j
2 g. G& C/ v9 ~2 f( R+ F6 s- p  3.3.5 WM_MEASUREITEM
/ m2 M! w0 l# H0 ~  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
, q2 {$ G! ]( _/ X1 c+ B6 s  WM_DRAWITEM的映射函数原型如下: / t+ ?0 i) R, Y
  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
- x& _. `& N* z2 w  T1 |, K5 |7 Z  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l! U$ P  J4 c/ L0 x
  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下:
7 D& O  Z0 E" b* t& `: k. W
  1. ; N* L' ?* ~; i! S
  2. typedef struct tagMEASUREITEMSTRUCT
    / p& E5 Z; E+ H
  3. {
    - ^0 R/ _6 Y2 k9 z; V& O. d
  4.     UINT   CtlType;
    / k: c9 ]+ @8 Q$ j2 J5 q2 O
  5.     UINT   CtlID;7 i) O5 O0 E1 _! O0 q2 F
  6.     UINT   itemID;8 l4 c$ q6 c6 @1 `' {5 P6 ~5 a: k
  7.     UINT   itemWidth;% |/ o& [% a2 O. t+ v7 U" H
  8.     UINT   itemHeight;
    , N7 Y' F6 B/ f0 N
  9.     DWORD  itemData;" L& s5 x& }! \4 l; C# M
  10. } MEASUREITEMSTRUCT;
复制代码
/ q: I5 }$ ?7 y" U
  CtlType指定了控件的类型,其取值如表6所示:
, {7 o9 s% r4 U; ^" J. w- M) h类型值 含义
! y8 C5 ]( W' a) J3 W" a4 Q1 G6 n. Q. |* CODT_COMBOBOX 组合框控件 3 L! }/ @. u. R. h
ODT_LISTBOX 列表框控件
& X+ N. F# m5 z7 p2 zODT_MENU 菜单项 9 l0 x3 O' B  z, e# V
表6 CtlType的类型值与含义
: a! A) Y! o% y# b* k  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
3 n9 g$ u$ O, k2 J/ j  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。 ) x0 \+ v) F/ `2 h. R
  itemWidth 指定菜单项的宽度 4 Q9 Z0 b! F4 x
  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255
9 x5 c3 E% U0 i" s2 z  itemData
1 m4 v  t/ \" g' |: f' ]4 @  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 2 a- L# k( \7 j! S. h& x
  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
' x  _8 E4 x. {8 }$ [  图示出了OnMeasureItem的效果: $ u% S! M2 m9 m: D& h
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下: 6 \5 N) I( Y- N- M5 X

  1. & P9 k( T! S6 P" u
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
    " {* Q3 [; R7 l
  3. {: k; w. h* P, n7 X9 D
  4.         if(nIDCtl == IDC_COLOR_PICKER)$ p2 g" G1 }0 N
  5.         {
    4 Q8 b1 G) i5 T5 B9 {* B
  6.                 //设定高度为30' T! [" q# t- A( i) N
  7.                 lpMeasureItemStruct->itemHeight = 30;
    . D. ^% o4 F4 w$ P! k+ ?
  8.                 return;; Y- d7 T+ O( }* `- S9 y
  9.         }
    $ `; t; t$ `9 z4 J
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
    7 {7 G& Y$ G, @, l+ Z: S
  11. }
复制代码
7 W: A7 T' E4 ]  y3 F
  同样别忘了指定列表框的Owner draw属性:
8 D) Z- d3 ?: a# B! g5 M* g
  图11 指定下拉框的Owner draw属性  
, z3 T7 L/ i" C3 a! {9 G+ e8 R& s+ W( u# n  f5 F4 B( Z2 l) T
  3.3.6 NM_CUSTOMDRAW
, n! p& {/ ~) h5 Y$ X" M: M  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。 * E6 R3 P. ]" F6 e3 p7 O( P
  可以反射NM_CUSTOMDRAW消息,如:
$ j# T- ~, {) L$ l  t  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
' O5 ~8 k& H; B6 W! z  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); , u4 r( S4 H$ j4 o& E( u: l
  参数:
4 ~' N6 Z% F1 U& r# c  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:
) j* C0 y! j! V; m

  1. 9 r- z: F+ X) z9 D1 H4 m3 A
  2. typedef struct tagNMHDR. _2 r' }' C) q+ h; g
  3. {% Z' O3 ^! }9 Z; J( z/ [
  4.      HWND hwndFrom;
    7 ?( G1 {8 a: |2 L8 U+ v
  5.      UINT idFrom;$ o" [. B% H! l
  6.      UINT code;
    , E5 D+ m( B' o2 E  B/ v
  7. } NMHDR;
复制代码

7 T# s  U% w- `  J# r  其中:
4 P  a. V2 Y7 a3 R  hwndFrom 发送方控件的窗口句柄
7 c( @" N) c+ H1 \2 v: a  x. ?  idFrom 发送方控件的ID code 通知代码
" o+ M+ g* C  K1 f7 f  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下:
! O. t' g1 j# a6 n. R: L
  1. ( i* L- T1 b3 m! {% N, c
  2. typedef struct tagNMCUSTOMDRAWINFO* e# v  t; I  v. J" J6 m
  3. {
      {. S& F. W7 K' p% D! a* L
  4.     NMHDR  hdr;  ^  \) C# p  x, V: |& E
  5.     DWORD  dwDrawStage;" _# B) x& W  I. }: j3 E8 l
  6.     HDC    hdc;
    % b5 F/ V# ~2 @1 ^
  7.     RECT   rc;
    1 I; l# y; _, _' ~; Z4 D: F; ]  D
  8.     DWORD  dwItemSpec;! O2 y: U& A  j7 I- k& P0 A6 z
  9.     UINT   uItemState;
    9 V4 u6 `6 d9 ]: J8 Z; k( q
  10.     LPARAM lItemlParam;
    ! f, f! R/ U5 \
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码

; T" X4 S7 d' g4 a) L   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:
# K" ~- e% _) \- g- c# l6 Q类型值 含义
+ D) p. p8 w& S3 uCDDS_POSTERASE 擦除循环结束
( F7 i9 C/ M- o9 |( s; ~8 \$ R2 NCDDS_POSTPAINT 绘制循环结束
. x' ^" A' Z  b$ RCDDS_PREERASE 准备开始擦除循环
( o0 Y' U3 A0 ]; u& E9 cCDDS_PREPAINT 准备开始绘制循环 * q& L& L: f2 S; o! S
CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 1 ^2 G( q( g% [, ?
CDDS_ITEMPOSTERASE 列表项擦除结束 & H) f! C3 f7 v
CDDS_ITEMPOSTPAINT 列表项绘制结束
/ S; z; u# b5 ?" CCDDS_ITEMPREERASE 准备开始列表项擦除
$ t3 r+ A, w7 Q! k. s" K& A' nCDDS_ITEMPREPAINT 准备开始列表项绘制 : m' E. k) o: i7 i& U- S+ G* U
CDDS_SUBITEM 指定列表子项
4 R# h1 \8 x2 f3 }2 n表7 dwDrawStage的类型值与含义+ f5 `- c7 X7 b) _5 L( |+ s
  hdc指定了绘制操作所使用的设备环境。
. F" V# [0 T' @' X  rc指定了将被绘制的矩形区域。 & F; h$ P  L# e
  dwItemSpec 列表项的索引 & @3 f0 Y! D8 N' w3 y: {
  uItemState 当前列表项的状态,其取值如表8所示:
) c3 W$ L. [) r类型值 含义 3 M4 G& p) Y. X1 |
CDIS_CHECKED 标记状态。
  J! n1 q; I! j" h% oCDIS_DEFAULT 默认状态。
$ f- h: F6 q% f$ x' b7 O+ `8 M* LCDIS_DISABLED 禁止状态。
/ ?5 p9 Y" I8 E+ Q8 C- }CDIS_FOCUS 焦点状态。 4 i% F1 y, N+ E8 a4 X$ g
CDIS_GRAYED 灰化状态。 # W. {+ L) E* s
CDIS_SELECTED 选中状态。 $ `+ s3 f- `8 E
CDIS_HOTLIGHT 热点状态。 - A& B- g4 P' [* W
CDIS_INDETERMINATE 不定状态。 ! C3 J) T* p- B. X/ K8 j; |7 G" R
CDIS_MARKED 标注状态。
. y2 m# M# a( R/ M表8 uItemState的类型值与含义- \& r5 f# _, f4 k
  lItemlParam 当前列表项的绑定数据 5 @$ u$ o: N" x+ I3 S! o5 {
  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: & {; d7 B3 K1 W5 W: @4 o
  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:2 w. g; w6 ~! U: f  F  v4 h, e
类型值 含义
) t4 J6 A3 J% q$ O0 C  qCDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。 : r8 b9 l& k! e7 b& M
CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。
( U% v1 ]9 t2 D) c4 X& iCDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。
4 A. C/ U# T- a" L6 ]( yCDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。* ^" z6 b. x% o# y+ a( X2 J
表9 pResult的类型值与含义(一) 5 z7 k5 G: A8 X/ s. i
  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:2 o8 {4 ]( a6 K5 a
类型值 含义 " H0 p" X9 G& g
CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。
. n$ R1 e" b8 f6 W8 |; tCDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
& t% [: E. K# `% t& t$ E, kCDRF_SKIPDEFAULT 系统不必再绘制该子项。2 T' x0 o8 m5 k7 K* c2 I$ W
表10 pResult的类型值与含义(二)  @& o: e6 {0 V/ s: A
  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子: 6 u- U$ n% C, \9 R
  图12 利用NM_CUSTOMDRAW消息美化界面 6 F" P3 {" |0 m% m; R/ Y4 I
  对应代码如下:
! o: B7 T5 q2 G) H) X( N

  1. & f" `" n: T$ x
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)/ b3 t9 F7 X/ v
  3. {/ B+ j# O& |  m- M7 {6 e+ L
  4.         //类型安全转换% n8 w' _( E/ t( v3 y5 {2 i
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);
    . a; O; q% H  o# A
  6.         *pResult = 0;
    8 Q+ F2 ^* ^. _9 f% \0 l, j* U
  7.         ) ^7 U4 v* |5 j& h8 Y2 V2 l
  8.         //指定列表项绘制前后发送消息/ q  t& E; d3 q2 m1 g: v1 y
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)2 H; V8 P* N3 m% ~2 Z$ W
  10.         {2 E8 i3 G5 @( ~8 [6 d6 v
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;
    6 f5 I  l/ K3 o1 \* a8 _
  12.         }+ A* l9 P' i+ M
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)" @! A  \7 \% a% ^$ f8 a
  14.         {" M8 J/ k- g. ~* b. x
  15.                 //奇数行0 }4 i0 ~! b. M: C, A" H
  16.                 if(pLVCD->nmcd.dwItemSpec % 2)
    8 j- ^, \  ?3 ~* T# t& R  W
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);
    9 J+ N5 M. ~4 J+ u) _
  18.                 //偶数行5 F9 m) e& U, [0 p5 @
  19.                 else, \. ^6 z3 S8 Y' i! O
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);
    7 @; t0 h$ P/ }( y3 w1 k5 R9 V
  21.                 //继续! f& M% ], R' @4 q8 {- `) B
  22.                 *pResult = CDRF_DODEFAULT;
    ( T- }& D) T3 `" D
  23.         }
    2 G( c( N( [2 c5 c7 p
  24. }
复制代码
0 t' H& @1 J1 x7 Z: k3 J( [5 Z
  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。 ' \$ O! y4 f# \( l& j$ a

5 X0 ?% h9 \8 y; H3 u  3.4 使用MFC类的虚函数机制
/ u; d+ G$ E# X- ^. A  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子:
/ W8 y  q) x& e7 e  w. l8 {

  1. / ~6 z8 [* d$ g
  2. void CView::OnPaint()  Y: e2 s6 [5 j  I
  3. {
    4 ]: e* S, E5 c  j: a  N
  4.         // standard paint routine
    / r+ N2 p1 N2 ?( R, h
  5.         CPaintDC dc(this);# w& X" J& x8 `0 m. n# c  k/ V
  6.         OnPrepareDC(&dc);
    ) z& [1 [- p+ I; p$ [
  7.         OnDraw(&dc);
    . |" d6 x. e0 B9 A/ H
  8. }
复制代码

+ ^& m% X9 B) B% s. S6 k  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。 ( j0 G- o- }1 |# A. D- `0 x8 t
  以下列出了与界面美化相关的虚函数,参数说明略去: " Z- E$ ~$ u4 t, y' s, {, |1 q
CButton::DrawItem 5 M; E$ m: h& V' Y
CCheckListBox::DrawItem 1 k% d- t  Q/ D) d0 x: I* [
CComboBox::DrawItem
! d* I. f1 K; P# Q% bCHeaderCtrl::DrawItem
% T$ q" \: g& }3 oCListBox::DrawItem % b: U, Q4 k9 l: X# i: e6 R) |
CMenu::DrawItem
/ Z, C% F' U' x* p# z% F" s' HCStatusBar::DrawItem
2 A# [' |  ]9 vCStatusBarCtrl::DrawItem & o7 ?! R: }2 b& b
CTabCtrl::DrawItem
1 A: T( f1 x" }+ Dvirtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct ); 4 u* {/ i2 l- u/ i" T- v
Owner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。
! R& \, m( ^+ ]1 P. ^  限于篇幅,在此不再附以例程。
6 D& i# e$ Q: j( y6 C( x) c$ e5 ]6 ]% l4 i* u& Z) H  y
参考文献
! `1 `( C) I- ]0 N本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-6-18 08:04 , Processed in 0.019127 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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