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

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇: }8 _: v; @/ D. [$ [$ E2 T
  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。
5 v9 s$ ~7 \/ l  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。
8 ^7 o" x$ v1 V) m) O
+ z5 ^, L1 u+ ]  J" q  2. 美化界面之基础篇+ R& B, {$ X3 k6 T; u
  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……
3 ^, E; U- r% U. {) g5 A# }6 U0 k* k3 v
8 A* E8 M) j# ]* x% F/ R  2.1 Windows下的绘图操作
! @3 L; ?! i0 M2 _+ r( f  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……
& Z+ E% i4 G7 u3 m  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。
) [1 B! G. x: m' W: U7 l6 `. r* l7 p( X, N! I# e' ?2 W
  2.1.1 设备环境类
  {2 B3 u1 T8 s) e4 \  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。, k3 W& t) C" o, d
  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:' m2 z" Y( ]/ e2 ?, f' B
  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式# ^, `0 {' _& u( J- A5 C- l
  Mapping Functions:映射操作2 E9 ~& Q7 H& G2 {2 x
  Coordinate Functions:坐标操作
. b# f$ m7 N! H3 e  Clipping Functions:剪切操作
6 i9 M$ O* W. q- k0 R- j0 o2 V! ]  Line-Output Functions:画线操作1 d3 }! H% j8 v  B
  Simple Drawing Functions:简单绘图操作,如:绘制矩形框
2 \) j& N$ S8 p/ T7 H( _  Ellipse and Polygon Functions:椭圆/多边形操作4 D$ a0 z* H, M0 j
  Text Functions:文字输出操作( q) j$ G* ^! V* k
  Printer Escape Functions:打印操作8 z/ t& t/ H( L: v& k
  Scrolling Functions:滚动操作3 E& v% Z) J" f$ N2 s$ K
  *Bitmap Functions:位图操作4 Y1 L) q. t1 S1 [! ~+ j
  *Region Functions:区域操作. i$ r" e; k3 t1 l. R+ b. `2 J
  *Font Functions:字体操作
7 H1 a, V; S5 N( `2 \5 w  *Color and Color Palette Functions:颜色/调色板操作8 G$ {! U- h: J( a' V# @- U  A
  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。
8 i8 M# a" l: p1 [( p0 g' m
$ B1 o$ i. {. p; z( F6 Q  2.1.2 图形对象类* W7 _* J+ |5 N0 _7 R
  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。
3 l( |: {  v& f6 z# Q. ]  下面的表格列出了MFC的图形对象类:
$ M- F/ h" T7 s  MFC类 图形对象句柄 图形对象目的/ c  S/ Q9 u( T/ Y2 l7 g- c: H
  CBitmap HBITMAP 内存中的位图
, }% n7 ]* n( H' y  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式( ?0 J9 X% ]8 V' L) F' t' w
  CFont HFONT 字体特性—写文本时所使用的字体9 `+ D7 c: D6 O' W* ?
  CPalette HPALETTE 调色板颜色
$ |' o  i4 G# B/ ]# U  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细3 J+ T, n# L( X- ]8 {, u( d: F( D1 I
  CRgn HRGN 区域特性—包括定义它的点
' M# u  S( P) D! Q& e  表1 图形对象类和它们封装的句柄
% w$ [# M" q0 n. l3 R
# q4 O' `  e& ]1 N- W8 s  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面: / p' ?4 F9 c- v2 g
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:0 z* i3 [+ S  g
  1. : @; G$ R3 a) D# m9 D8 K
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)/ f, p1 t# O! L) I& w2 g7 g
  3. {
    ! P5 U, |3 R5 w1 D2 x/ t
  4.         //设置背景色
    & I  L/ g- [. C. U
  5.         //CBrush CUi1View::m_Back8 ]0 F' S8 |% g  M& p% g+ O, K4 v
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));; i$ x  y$ @- f/ P( ]
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL);
    # E3 Y4 b  A, w2 H
  8.        return CView::PreCreateWindow(cs);( k2 z' w9 P8 x; U4 ]3 y0 W5 D
  9. }
    - m3 v9 ~# k) Y1 z9 ~' t
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)9 Y( J9 j. K# D8 A
  11. {% H; L- Q0 Z% d9 }
  12.         if (CView::OnCreate(lpCreateStruct) == -1)3 Y: h3 ^  G' z. W5 w# w+ l8 F. m$ Z
  13.                 return -1;# I1 _9 n* X% E3 F3 F) I
  14.         //创建字体
    ) M6 t' Z8 `$ ~
  15.         //CFont CUi1View::m_Font
    5 ?" u/ ?# U& T" x/ a
  16.         m_Font.CreatePointFont(120, "Impact"); 3 |8 e% s9 Z1 d, {! M  \# v
  17.                return 0;
    ; B6 }9 M; k$ z0 v* Y# K
  18. }8 K4 g* w6 `$ e
  19. void CUi1View::OnDraw(CDC* pDC)$ [' V8 H5 t( Q
  20. {
    0 z( @- x& f( v. k
  21.         //绘制按钮框架
    & e$ ~, l- }) K* `! h( G6 L
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);
    # s% A$ L6 c& l# f3 o8 K6 c9 }
  23.         //输出文字; ~) b- ?, A& q( c; C' ~
  24.         pDC->SetBkMode(TRANSPARENT);1 ~6 t' q3 I3 ~9 k
  25.         pDC->TextOut(120, 120, "Hello, CFan!");8 C$ \7 J% z4 B, Y) P8 B6 x
  26. }
复制代码

, k& t, }; a$ \2 \, N/ A  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。 : ~9 S1 u" w7 w- ?

, _8 S. n* [$ [# H( [/ d  2.2 Windows的幕后绘图操作
, U& v( g+ v4 @' r  在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。  d8 r8 m) p- ^8 Y5 Q! I5 Y
  有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。7 _) S9 t+ D1 z9 D
  所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……- f0 P4 R. k! z: i) {0 c& K
  有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。/ Y; x; @5 Y" G

# S* D( P& ~8 \/ Z( m/ T$ B4 \. W, W  3. 美化界面之实现篇" m; P# R0 c, M& S
  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。1 D2 `7 o5 L  @  `4 z

, Y2 F! |' z% L0 n* F  3.1 美化界面的途径* e2 p9 ^0 b/ q5 `
  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:
$ V$ w5 u! |, P7 |% [- ~* G& V  1. 使用MFC类的既有函数,设定界面属性;7 ^4 U. p! @. }  O
  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;
" N4 e; @+ b+ K* @$ [9 S- k  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;
/ s- `' }# A; ^% \/ w% V  一般来说,应用程序可以通过以下两种途径来实现以上的方法:
% A  j; A  @7 x9 V' y  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;3 Z1 M" x7 E( ^7 K6 {
  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。8 s% K  }0 `. L# b, |
  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:. ^; |% d% a& d" a/ z% J
  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示: 2 H- L( t# E/ |3 N
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;+ s; P& j0 i1 n# O
  以下的章节将综合地使用以上的方法,请读者朋友留心观察。- ~( M) {! a; U) ~

& ], o" U  o/ ?' `  3.2 使用MFC类的既有函数# N1 g4 G+ V% B( f' U# N2 k6 p
  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。. d/ ~8 m  \( f$ j
) ]0 |6 R8 v2 r' n
CWinApp::SetDialogBkColor* w* M9 G- {, X0 v6 _$ p4 b* Z! ~
void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );; c  S0 ]8 E& F/ ]) ?5 }
指定对话框的背景色和文本颜色。/ p% I0 m8 J- o
9 X% P* S" K+ K7 ?3 y5 E
CListCtrl::SetBkColor
4 L$ H* L% V2 C5 M$ lCReBarCtrl::SetBkColor8 L6 {( S2 b# x. C
CStatusBarCtrl::SetBkColor+ }  g% g1 q7 m! e& ?: B
CTreeCtrl::SetBkColor$ X: P- g$ q% J* x
COLORREF SetBkColor( COLORREF clr );
( g/ n# o+ P; s) g设定背景色。4 ~+ @( b7 F: T& R) Y6 E: K

2 G1 u# m  i7 y( F) iCListCtrl::SetTextColor5 M- v5 F. x. I2 z9 \$ i8 E2 h/ N8 T
CReBarCtrl::SetTextColor. C. _$ ?6 \% m9 U( C2 B
CTreeCtrl::SetTextColor7 G7 x+ @1 u+ O# R
COLORREF SetTextColor( COLORREF clr );/ J( d# p) B. I% w0 b+ k
设定文本颜色。$ r: n1 E  H1 c: X4 A/ s
! F9 L$ J( E& E! y* C3 T0 P
CListCtrl::SetBkImage
/ I- B9 E6 Z2 F  ?1 k0 {BOOL SetBkImage( LVBKIMAGE* plvbkImage );
- @# H4 M4 e) h3 W/ Z  @, cBOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);" |4 G- \& |* B& H3 ?
BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );' D- n7 @. q% E
设定列表控件的背景图片。' \8 ^; x+ n6 E8 P4 P0 l

5 i7 \. Y' U5 n# `8 ~CComboBoxEx::SetExtendedStyle+ ]: I7 I" Q9 z& X& K( l
CListCtrl::SetExtendedStyle4 I" {  h9 s# t0 R
CTabCtrl::SetExtendedStyle- S6 n1 m7 \0 }' ^6 Y
CToolBarCtrl::SetExtendedStyle $ L# ?% s! \3 `+ S
DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles );
# u5 X! }: ]( e. I1 j设置控件的扩展属性,例如:设置列表控件属性带有表格线。0 n' J/ ?# r& ?, c  W0 _
  图4是个简单应用MFC类的既有函数来改善Windows界面的例子:   `# y9 {3 H$ L4 U2 D
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:7 P) @" m4 t% p! @6 r
  1. / [8 Q, h+ e; E  O9 C
  2. BOOL CUi2App::InitInstance()
    ; B0 E2 J$ U6 m8 x8 H! w6 r, S
  3. {" ~- ?* g% h$ n' S" `+ I/ h
  4.         //…
    ! B5 I* P( H( l& b7 \: I  V
  5.         //设置对话框背景色和字体颜色+ _; v; `* M0 q+ A9 j. S. J( X7 `
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));9 j" `5 q" x& e7 h. Y( r$ K4 z9 M
  7.          //…% R; A8 D$ Q- f
  8. }+ K! Q5 [: Z3 x2 z, z
  9. BOOL CUi2Dlg::OnInitDialog()
    4 o/ y8 ?$ ]* c6 j  {$ V+ J5 s
  10. {
    6 M6 I6 R6 z% G' z
  11.         //…" h/ ?0 A8 m  t, q! K
  12.         //设置列表控件属性带有表格线
    - b& S5 Z) B) W& V% d
  13.         DWORD NewStyle = m_List.GetExtendedStyle();
    7 D, o; D( D9 s9 ]
  14.     NewStyle |= LVS_EX_GRIDLINES;& n4 W  h* i7 T. M9 x% O& F% p
  15. m_List.SetExtendedStyle(NewStyle);
    7 j* S; T! U- e. ]; ~5 h
  16.         //设置列表控件字体颜色为红色8 m- R# W2 }+ Y; T1 X: l0 A
  17.         m_List.SetTextColor(RGB(255, 0, 0));: u, _. f' c  s) V
  18.         //填充数据
    0 G+ q1 U% R8 U! C9 F& V8 H1 v
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);
    2 M9 P6 H5 Z0 Z  a- r
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);
    0 o. d( g, e+ U2 M' O% ^- K! E
  21.         m_List.InsertItem(0, "5854165");
    + W5 T7 N( q; ^4 x  P, M
  22.         m_List.SetItemText(0, 1, "白乔");$ m. \* a1 `# d
  23.         m_List.InsertItem(1, "6823864");
    , h# X3 G  q4 t2 o
  24.         m_List.SetItemText(1, 1, "Satan");
    ) c( ]. |& d5 o) _% c
  25.         //…$ ^- z6 J) C2 _7 c4 a. w+ `
  26. }
复制代码
4 G( w! j; o1 P
  嗯,这样的界面还算不错吧? 6 ~1 Y9 W/ \! y- f% c/ \

0 w5 {: u9 Q$ @6 w% O  3.3 使用Windows的消息机制
3 u0 m6 K" L1 l  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息: " z4 h3 q+ m4 S+ g; x
WM_PAINT 7 d# w0 j3 ]; N: F4 Q2 k4 J
WM_ERASEBKGND
: L( z- L1 W9 Q/ \, LWM_CTLCOLOR* 3 m8 L8 @" u+ B: M
WM_DRAWITEM* - [$ J/ E# e: ~1 t% ]! S
WM_MEASUREITEM* % S+ t0 ~3 \4 U; z1 i. V
NM_CUSTOMDRAW*
; {% j) K8 L- I9 @0 Q  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。- t0 b: z. ?! c1 K2 W
8 L2 m. y7 F0 U/ B2 U
  3.3.1 WM_PAINT
9 i* X' Y8 L% x1 T# {8 r  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。
6 s8 {( C$ ^' K  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下:
; q1 E  E0 `; ?$ L/ N  afx_msg void OnPaint();
6 s+ v, u0 i! P: u6 V  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: 2 }7 C7 S/ T% c1 Y) a
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单:
. r6 K+ k! ^) P3 S
  1. 3 a7 k, n! f, I7 R: a
  2. void CLazyStatic::OnPaint()
    " V" E, ^& |- Y9 {6 L
  3. {
    0 I6 V* \$ t6 Q  n- H8 F
  4.         CPaintDC dc(this); // device context for painting
    ! q4 |) R" w) ]  C/ k% W
  5.          
      M. U8 S* q! N9 {2 T3 \+ r) }
  6.        //什么都不输出,仅仅画一个矩形框5 N4 t* r# J/ t& G  {- F- Z, A
  7.         CRect rc;8 j% e9 G% K( U6 r
  8.         GetClientRect(&rc);  L8 ]) S0 N9 M7 a
  9.         dc.Rectangle(rc);
      ^7 H1 X. ^- _7 ~9 z
  10. }
复制代码

* d; t# R7 B+ c8 j6 M5 ~; Y6 s' e  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。! @7 R+ o3 v/ N" D; L
: s  t* F$ ?$ n( p+ E4 d7 H
  3.3.2 WM_ERASEBKGND
: V1 Q. b% n+ s3 i. x2 T  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。
: c, e1 G; }4 R3 `! @, z  n  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:  u7 \1 s9 V5 }1 @1 ~( @
  afx_msg BOOL OnEraseBkgnd( CDC* pDC ); $ Q& |- G: ?$ s5 E
  返回值:
; q( o; h8 D; A9 F) Y  指定背景是否已清除,如果为FALSE,系统将自动清除 5 I6 O, g. m1 q" q8 E- q& _+ M
  参数: pDC指定了绘制操作所使用的设备环境。 6 r1 k% ]9 q) e4 K
  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景:
2 Y9 ?6 A7 L6 _3 |1 q7 [
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单:
0 ^: A' x7 L6 ]$ v

  1. # \/ N% z; L- A: v
  2. BOOL CUi4Dlg::OnInitDialog(). m4 Q/ h# n  D
  3. {
    0 f& S/ |/ m! X: h
  4. //…3 w; X6 T5 `3 H- f% w
  5.         //加载位图. t; M1 f% S+ m4 h! K
  6.         //CBitmap m_Back;/ f/ N% l; Q7 a  M) P, }  Q
  7.         m_Back.LoadBitmap(IDB_BACK);
    $ p  s9 m( j% x! b8 F
  8.         //…
    3 a; E! g: n$ i% p5 |) q  ?8 V4 I; E
  9. }
    ' @1 I$ o& t/ `$ L4 I
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC)
    / A8 L% Q, p; H9 l
  11. {
    6 a' n0 f7 D4 w# s
  12.         CDC dc;5 ?8 w8 W! f; W
  13.         dc.CreateCompatibleDC(pDC);: D8 o) b1 n+ U: o3 }" O. l- }! [
  14.         dc.SelectObject(&m_Back);7 I! F" Q$ b& \" g/ ]
  15.         //获取BITMAP对象
    $ y1 F1 N/ D0 H* B* {: `8 T+ `
  16.         BITMAP hb;
    ) J: R2 M* f8 Q& m# ]9 ]' C, e- @
  17.         m_Back.GetBitmap(&hb);
    0 D  V, X. @6 [; r: p
  18.         //获取窗口大小. p; ~: ~8 P. _5 b6 k: L
  19.         CRect rt;; v* ?) M5 Y' x# m8 N* [! v8 R
  20.         GetClientRect(&rt);
    ! o- P! H6 K8 |
  21.         //显示位图
    / z! w6 s+ \8 Z( v) u- F/ [3 a: t. t0 m
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),5 }0 o# u% L) N1 |' t" u7 E) v1 Y
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);  @. f' u/ x; `! W& H( V
  24.         return TRUE;
    / C# N: P4 f" I
  25. }" M: ]! D; K2 @2 B  q" D1 D: k# n
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) . G, b( W$ g* @
  27. {- D7 @& `5 r$ r5 ]2 W/ m; M
  28.         //设置透明背景模式* b0 j$ Y* @; v' H+ H7 [2 @
  29.         pDC->SetBkMode(TRANSPARENT);: l+ _# N, q/ L( Q, N9 s
  30.         //设置背景刷子为空
    , f. E) k, q2 D& h$ o& g7 L( s
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);
    3 [0 [/ p& L: h! ?+ ]( W
  32. }
复制代码

4 d4 S. P2 K: N  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。 % g+ y* t9 f5 u7 r
' h9 ?* G# ?5 T7 w
  3.3.3 WM_CTLCOLOR
' E( h1 D" _2 J) U* d9 B  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。   o6 W% M+ I) ]
  WM_CTLCOLOR的映射函数原型如下:
9 N% H* e5 t. f0 |  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );
- M. Z2 W% B9 l" k" B& c  S  返回值: 用以指定背景的刷子 + K' g" C, \& c
  参数: 8 ^3 r$ _* o3 A4 p( E( u/ F
  pDC指定了绘制操作所使用的设备环境。
) b# n% v7 B* a* |( W! ?  pWnd 控件指针
% M* m) V  n' M8 O  nCtlColor 指定控件类型,其取值如表2所示:
& i, [! s: Q# S% i  X# C; A  类型值 含义 2 H1 M+ m- ^( r3 \  I" P
CTLCOLOR_BTN 按钮控件
" X- A& l7 R( `+ LCTLCOLOR_DLG 对话框 / \% ]3 L1 u1 T8 c8 t2 k6 ~
CTLCOLOR_EDIT  编辑控件
% `/ a7 y' A# V, w6 OCTLCOLOR_LISTBOX  列表框
0 }5 A3 w5 I8 `  s2 oCTLCOLOR_MSGBOX  消息框 4 s- n0 R: H7 n( V5 I2 C
CTLCOLOR_SCROLLBAR 滚动条 ' E2 H" U1 w  z0 X& }
CTLCOLOR_STATIC 静态控件 - Y4 I) G: z- W- [* N" j3 Y
表2 nCtlColor的类型值与含义" j# x7 p' a1 w9 x6 b

; L  h( ]1 x- Z" G- x" |  作为一个简单的例子,观察以下的代码: 3 m1 P: [. B( q( \8 i
  1. / \# O/ U" z6 D& c# l& o/ W* r
  2. BOOL CUi5Dlg::OnInitDialog()
    - R  F- F* I! ^8 n: _
  3. {
    1 z) g4 @) |( y) p0 Q, N# @
  4.         //…$ a# |9 R8 }8 l  z) Z
  5.         //创建字体
    , h8 {2 @, B3 E4 _0 N& B8 S
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font2, I) U1 E* P$ ?4 D! E
  7.         m_Font1.CreatePointFont(120, "Impact");% c  \' S! q/ p& q& a
  8.         m_Font3.CreatePointFont(120, "Arial");8 h- z( b: D; h: C9 {
  9.                 return TRUE;8 ^. Q- x& @- K
  10.   // return TRUE  unless you set the focus to a control * W4 S2 V& H: g" f: V& N
  11. }7 M" f4 e5 ~* X8 n& B
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 6 k9 |+ x% ]6 H/ X, b  L2 o
  13. {; P, {. r3 O$ B, U
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);( f5 l- T- u: L
  15.         if(nCtlColor == CTLCOLOR_STATIC)
      G  X# t; M: J7 {0 T- ^
  16.         {
    6 T2 }" h; o0 d/ w
  17.                 //区分静态控件7 \6 i% K* k! i( e0 k
  18.                 switch(pWnd->GetDlgCtrlID())
    ! G; O, L- H% j( @% A, B3 b) F
  19.                 {
    ! h& V: p$ y3 E/ A2 L. u/ C9 S
  20.                         case IDC_STATIC1:
    5 ]; t: W4 J6 Y3 b
  21.                         {
    3 D) ~7 p4 f  H0 s
  22.                                 pDC->SelectObject(&m_Font1);5 y' c1 n. a! r, H# Q" o
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));& p, M% V/ ?  D  w) u
  24.                                 break;3 e, a- e2 m/ R* p: ^2 ~
  25.                         }9 M5 ^  [; a) D2 ]* D. c) q! o! c
  26.                         case IDC_STATIC2:" s- S6 l% C) Q/ X1 U( G7 g% i
  27.                         {+ {, f* g- H9 O0 n7 }3 V
  28.                                 pDC->SelectObject(&m_Font2);& o  V0 D* f, g  \# r' J! h
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));
    ) b  j) y' u/ U
  30.                                 break;8 k9 V  B3 ?) R( J' S& C0 `: G
  31.                         }1 ?4 r% `) V9 k& `% E7 s) ?! |
  32.                 }
    * z! P1 _* r0 _! n: ^/ X* o
  33.         }- C2 ]# P- ~% O* B) m- P# S
  34.         return hbr;) r. F7 l! K) G7 I, K, Y9 |
  35. }
复制代码
5 X5 [& j) m1 b9 y6 [. v; |
   生成的界面如下:
; H0 U! \" j3 w' k- l: J" I8 A
图7 利用WM_CTLCOLOR消息美化界面
6 }5 I5 G% V* g& g0 T( H
  3.3.4 WM_DRAWITEM ( `. M# O; K# M' g& u/ {2 y: c: x
  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。
7 p9 u4 h- y7 R2 m/ r( W2 x  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。
' x1 @, N; d: L. _8 K5 D  WM_DRAWITEM的映射函数原型如下: ' j  x3 l. h. ?
  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );
- I5 Z5 G, W, I$ g% `, B2 u  参数:
9 B) ^) y; r3 R" l0 n  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 $ O  J+ b7 w9 O$ r3 }" O0 E( ?
  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下:
# [9 ~( u: k: g5 R
  1. 3 A' a/ G9 E. W$ }/ Q" h5 w
  2. typedef struct tagDRAWITEMSTRUCT0 U# x+ j& E7 N: j! b( }
  3. {
    6 ^. I4 q  u; d, y# ]$ _$ a: v
  4.     UINT   CtlType;
    3 |* L0 h  X% J/ R' m4 s
  5.      UINT   CtlID;0 T% C3 I. {0 A, s% h
  6.      UINT   itemID;
    2 ~+ r% p8 @1 z; z
  7.     UINT   itemAction;
    . F. \3 x: s/ q# x
  8.     UINT   itemState;
    # T, @8 P1 H# ^1 _5 S
  9.     HWND   hwndItem;" V  L: n' U/ L% B  `! D( k
  10.     HDC    hDC;
    3 }5 t* W( c/ Q- @3 w
  11.     RECT   rcItem;
    ; ~! a1 g; O$ O+ C" f4 V- w! a0 g
  12.     DWORD  itemData;8 c: C( W, u- i# |8 i
  13. }DRAWITEMSTRUCT;
复制代码

6 e4 h& B) X: u7 v0 `- ZCtlType指定了控件的类型,其取值如表3所示: - t% w" o( _# O+ i. P  Q
类型值 含义 / m: s" J5 g) s; n
ODT_BUTTON 按钮控件
4 Q7 f0 d; t8 c4 _ODT_COMBOBOX 组合框控件   ]) v6 \* c: a' w' y, U: x
ODT_LISTBOX 列表框控件 1 {1 Z+ A- y5 u* `2 }/ P; d5 r
ODT_LISTVIEW 列表视图 : O  W" D. w: q% z6 i7 U
ODT_MENU 菜单项
" f2 ]& S! i" ?' k4 u# BODT_STATIC 静态文本控件 9 i5 e7 O( x+ w$ x) J/ ~. d9 F6 T/ m
ODT_TAB Tab控件 * Y% J) b$ e) B6 F7 F- D
表3 CtlType的类型值与含义
+ B$ i: e0 t4 u7 T) [* e+ J- _0 T- C5 S. s3 {; K
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项 . Q' N  G2 T4 @
  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。
& I! U. B3 s1 C: }* a  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:4 Z- U) Z. C2 \6 n, c4 W) O. d& e0 H$ T
类型值 含义
5 Y( m8 J0 w2 n- Q0 aODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。   d! V( K0 b" h7 a! z( H# g
ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。 6 w# @# j! f7 Q+ z5 y' w
ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 + d2 O: r3 \$ t: x4 ]0 M* _) N
表4 itemAction的类型值与含义
2 u; t  k+ G9 _0 |+ U  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:
3 ?% E& i. B  {, T/ a类型值 含义
: T- M6 a$ W$ N# ~ODS_CHECKED 标记状态,仅适用于菜单项。
" h; D, A0 v) l2 g' s, U: d2 SODS_DEFAULT 默认状态。 8 _( f" n: p9 b+ v( B1 B  Y1 h( |: |1 _
ODS_DISABLED 禁止状态。 . K1 W+ z7 E" ?$ C$ M# S
ODS_FOCUS 焦点状态。 6 H3 `4 ^; B: A+ c
ODS_GRAYED 灰化状态,仅适用于菜单项。 ; M' l, m' x7 B. K& K/ e" D
ODS_SELECTED 选中状态。 7 i* Z6 J, s; c. b
ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。 % A% t5 |1 o# Z; @, F, c# j8 F
ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。
: e: D3 T- J( J0 C2 K) YODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。 / t* z4 A5 ^  B) Z
ODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。 + k! M( p6 x& D! ~8 ?+ [
ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。
9 O% p9 O# `: q0 o表5 itemState的类型值与含义
/ }6 f9 h: Q3 S  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。 # u$ h! Y: y7 k7 |) V; r
  hDC 指定了绘制操作所使用的设备环境。
" v1 F' p8 l* Y3 ]  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 5 u/ D* T, x/ c7 j
  itemData ! A* [* s* s4 X& L0 U8 C
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
5 A- C5 s1 I; P3 _* M. L  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 0 a) x3 f$ F* O. a$ j: \0 j. \' a
  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。 . m7 X1 Z( Q4 e/ Q0 H
  图5是个相应的例子,它修改了按钮的界面: 9 G, y" H5 p5 ^* _' J" p* g% a- c
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下:
* B& E5 s  M5 O( _5 p

  1. ! G; i: `1 l: T8 A9 ~  v* v
  2. BOOL CUi6Dlg::OnInitDialog()  j* A) Z# Q( I" L2 d; Q* t2 `' t
  3. {
    / B" q* Q5 ~' y" T. \. }1 P- }+ `
  4.         //…
    : ^2 c( x$ Z  x3 o# X
  5.         //创建字体- c. P8 l0 G# u+ f6 u# g2 q
  6.         //CFont CUi1View::m_Font
    - \- m; P2 _& R* k$ u0 l5 w
  7.         m_Font.CreatePointFont(120, "Impact");8 O2 k% ~% U3 n. G1 H' j) Q( Y
  8.         //…: ]- K+ r8 B% F/ t6 M+ M
  9. }: d* `" Y' S9 q' ?
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) 3 {; I! b7 X1 L+ S6 l& T1 R
  11. {2 ]6 v5 k3 M/ ~% K* C
  12.         if(nIDCtl == IDC_HELLO_CFAN)
    & a6 H( ^! {4 c
  13.         {
    - _$ f7 j/ B) n
  14.                 //绘制按钮框架
    & K2 M3 {+ N1 G1 K0 E7 f
  15.                 UINT uStyle = DFCS_BUTTONPUSH;8 z$ y/ {2 G+ c$ L
  16.                 //是否按下去了?
    0 Z" X. D) J2 M  W
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)
    ! `) N1 z1 L& J$ p
  18.                         uStyle |= DFCS_PUSHED;
    * y1 t) E: F: @# D" J0 z
  19.                 CDC dc;  R+ z. m( H% W) o' m# Z
  20.                 dc.Attach(lpDrawItemStruct->hDC);
    , a% }; R( q4 F, d- N
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);
      p5 ^7 X, M0 m8 m# |! C
  22.                 //输出文字
    ( a7 {" p2 u- w
  23.                 dc.SelectObject(&m_Font);" L, P9 R  L) I9 `" o' Q! O) r
  24.                 dc.SetTextColor(RGB(0, 0, 255));
    6 h1 [8 e; V+ m3 D3 u% }  O! Z
  25.                 dc.SetBkMode(TRANSPARENT);/ z# I' }0 A# f8 w2 _9 H2 ^
  26.                 CString sText;  P( U. z9 f( Z- A  ]& O5 C7 f
  27.                 m_HelloCFan.GetWindowText(sText);
    3 |% ~# [. e# w4 ~! o
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);
    1 r* F( s. M5 m& k# X. }
  29.                 //是否得到焦点
      U. C* |5 |; a* b  l
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)6 e& u9 p( b& J
  31.                 {
    + Y- R3 o% t7 g) U6 H) ]+ ^
  32.                         //画虚框
    ( T; x1 p$ ^4 b; g5 [% T
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem; 2 h0 v) u. m) P5 G3 _
  34.                        rtFocus.DeflateRect(3, 3);
    7 r9 P3 X) ~! D  V' }% _
  35.                         dc.DrawFocusRect(&rtFocus);* E3 \7 [3 C. C8 ?6 M) \' R
  36.                 }
    $ a+ p, W" T, L! Z
  37.                 return;
    3 l# n; |' c: ?5 m) P
  38.         }
    $ ]( k, L0 Y) H7 e  Q8 x# r
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);+ U* s. Q# b! u, `
  40. }
复制代码
1 ~/ W5 j; o. {; O1 |
  别忘了标记Owner draw属性:
. @- Q& F4 F3 v1 k
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。 / P2 z' G/ v7 t
9 u; t. p  ], ]  }' E
  3.3.5 WM_MEASUREITEM ) k. [6 z# b! [2 H' Q4 O/ g
  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
' R: E7 A/ r- J" K: ~, j1 }  WM_DRAWITEM的映射函数原型如下:
$ M8 _' t$ X( M: j/ L1 T/ ~; R+ _; I( ^  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
* @0 R# m' \3 k! k  t- R! y+ ~0 _  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l. d# X- a- v6 w' T3 K% \5 j
  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下: * i" T. s5 {" g6 q) H) A
  1. $ L5 P4 U1 J, @& T7 ~
  2. typedef struct tagMEASUREITEMSTRUCT
    3 S# Y- N& h7 a2 R8 W& g1 q
  3. {
    . b! u5 `) Y( [( A9 ^
  4.     UINT   CtlType;1 j/ x0 U1 @3 J; ~6 I3 o! E7 z5 l
  5.     UINT   CtlID;
    0 X0 `- r8 _: V
  6.     UINT   itemID;& ], D/ n5 [  Y) k- H% Y1 e
  7.     UINT   itemWidth;
    6 e6 h3 `& e/ ^6 o0 d& n9 V
  8.     UINT   itemHeight;3 V9 R# Y- ~8 d
  9.     DWORD  itemData;
    % E* s: T  A: @% o% X9 B
  10. } MEASUREITEMSTRUCT;
复制代码
7 Z+ M% r! r. R
  CtlType指定了控件的类型,其取值如表6所示:! @! q( B# q8 @" c
类型值 含义
# X+ b) h. a$ D9 Q. J# Z+ AODT_COMBOBOX 组合框控件
& \$ C- S5 Y: RODT_LISTBOX 列表框控件 ) _/ w( n* {( r6 @# y" c$ j# Y
ODT_MENU 菜单项
, ^1 j- T2 c9 r) q- s- h) {表6 CtlType的类型值与含义
7 a& |( g9 d5 y' v" k, B  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
6 Y0 n% N$ K, w& S( O+ @! p4 z( U  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。 7 W$ E; R5 @: U4 c  t
  itemWidth 指定菜单项的宽度 " _3 D7 l% D; ]. M$ n$ R9 q
  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255
4 T* w+ a4 U( B8 |. C  itemData & a7 P0 a2 i5 v% V& u
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
- ]: N2 I5 e1 B  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
' c* X/ s4 X) \' X6 _  图示出了OnMeasureItem的效果:
. r% n. i- H- |! x6 h. b
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下: ! [4 n( C) j$ a% _. H- M* o8 y
  1. $ F, x) P+ B5 K. ~0 W% H3 ]; J
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
    5 o7 P5 m$ ~1 e. W6 Z( }! q
  3. {$ g4 f: R" H/ d
  4.         if(nIDCtl == IDC_COLOR_PICKER)
    6 R7 K8 X8 p; L
  5.         {
    2 q( n7 z+ \: f4 [
  6.                 //设定高度为30
    . O# }4 c) G3 s) I+ r/ T
  7.                 lpMeasureItemStruct->itemHeight = 30;! h# J) _# K# T
  8.                 return;
    / A" ?3 j7 e7 u  E" Q2 y5 Q8 Y  L
  9.         }+ n* z: c0 a/ x5 F, T* [
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);# L1 D. j, `6 _  V2 ]  X
  11. }
复制代码
: ]4 s& x( l# F1 H( S3 M( g* M5 x
  同样别忘了指定列表框的Owner draw属性: 7 L0 L( p" b' X  K
  图11 指定下拉框的Owner draw属性  " ?- h* ^) q! {4 G1 S! A5 H

+ q# U" E, M8 X/ R  3.3.6 NM_CUSTOMDRAW , k' {- R" n- V4 N: u* ~
  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。 * D$ F( h. j  p' T, g' A, e
  可以反射NM_CUSTOMDRAW消息,如:
/ }$ u7 v$ K2 F5 c$ ?1 S0 E& k# |  L  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw) 3 S2 B. y7 J$ j- m/ J
  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); ! @3 y" K4 D: I: w4 Q
  参数:
# M" Y. U2 J$ O# K/ r. ^  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:
8 X: B* l- F( U2 K

  1. ! {" n# b) [' `' ]
  2. typedef struct tagNMHDR4 M+ k4 V1 k6 |) L# ]
  3. {: d) P4 V; r3 x. {
  4.      HWND hwndFrom;( f/ ]8 A3 ^/ _3 r
  5.      UINT idFrom;
    8 b4 ]  S8 N( U  L2 F3 ]
  6.      UINT code;
    * G% K9 n4 P, t& O# I
  7. } NMHDR;
复制代码

& V9 R# b+ e2 V5 n6 w' H, u: h  其中:   E( X% _" R, o7 {4 W2 N  P
  hwndFrom 发送方控件的窗口句柄
% F/ A  k' M# q3 e- `' }  idFrom 发送方控件的ID code 通知代码 2 [8 _, A, _/ w& j& ~" {) S
  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下:
4 X; L. G& B, n" s! p6 G* ]

  1. ( H# o# n2 w' d! P: J) ~" L0 h
  2. typedef struct tagNMCUSTOMDRAWINFO
    5 B3 o  L8 G. A" Q
  3. {- t& d, s2 F, {. T. L7 Y
  4.     NMHDR  hdr;
    ) y! s. x0 m1 E0 S
  5.     DWORD  dwDrawStage;6 p, J% R0 {; g+ m
  6.     HDC    hdc;. X# c3 O$ q1 |. i% N+ `  c# {
  7.     RECT   rc;
    : D, S- @  `! N9 h
  8.     DWORD  dwItemSpec;
    + _5 ^' S! Y; K1 N: O/ L. v& o
  9.     UINT   uItemState;& \- s8 u: j5 y2 `0 M6 A
  10.     LPARAM lItemlParam;
    8 y* ^) `) w" Z: h3 V
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码
* x4 ^( w1 U, }; q- N8 ^) i
   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:
) K3 K& K- k& D/ H. g类型值 含义 & w! b6 o: y* I. J+ Q1 o* }+ B
CDDS_POSTERASE 擦除循环结束 * M. T2 J4 b3 z/ }% K7 h
CDDS_POSTPAINT 绘制循环结束 $ P5 k: Z% y; ?' i
CDDS_PREERASE 准备开始擦除循环 / w4 N+ ^# H# b: n% B4 J4 W2 U8 B
CDDS_PREPAINT 准备开始绘制循环
0 _6 Y! M9 V) T! F8 B2 v; gCDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 8 @6 E+ ~' W, s/ ?, X
CDDS_ITEMPOSTERASE 列表项擦除结束
9 c- d0 X! _: Z' i1 HCDDS_ITEMPOSTPAINT 列表项绘制结束
1 f% c( u6 F5 r( ?3 @8 @) CCDDS_ITEMPREERASE 准备开始列表项擦除
' k$ H9 g7 Q' U4 n! LCDDS_ITEMPREPAINT 准备开始列表项绘制 . @9 a" W2 @( Q
CDDS_SUBITEM 指定列表子项
. G& U' n' n% \4 Y% x9 b( z2 i表7 dwDrawStage的类型值与含义
# ~, D! X8 D; l- w7 E7 \  }: r3 q  hdc指定了绘制操作所使用的设备环境。 " f0 z9 p) X0 |6 @4 @* z" k
  rc指定了将被绘制的矩形区域。
& \8 z* C+ j- {. V# u6 ]  dwItemSpec 列表项的索引 ! e- s& a" _; w$ F2 i/ ]& u- g
  uItemState 当前列表项的状态,其取值如表8所示:6 G( j' ^4 e( y' X
类型值 含义
- H% A# `5 p# d! YCDIS_CHECKED 标记状态。 2 ~) c/ x, S0 I$ }
CDIS_DEFAULT 默认状态。 " U1 M! R( `1 g0 ]$ o' s! @6 x3 p
CDIS_DISABLED 禁止状态。
0 q; m* C6 }+ B8 }% H4 l9 ZCDIS_FOCUS 焦点状态。 - _) X+ k. ], Y6 p6 e
CDIS_GRAYED 灰化状态。 . v2 O& C6 |2 L" @  O/ J2 {
CDIS_SELECTED 选中状态。
) S6 Z  }" K7 e$ T1 p* p# eCDIS_HOTLIGHT 热点状态。 ! N% ?4 g+ v7 ~
CDIS_INDETERMINATE 不定状态。
2 e2 ^( Y: P7 i) F' M  xCDIS_MARKED 标注状态。
. L! ]% U; f6 e" T表8 uItemState的类型值与含义: K8 ?% w7 [' \5 h
  lItemlParam 当前列表项的绑定数据
5 f0 d; m# j( N. }  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage:
4 J$ ^. ~- ]. G+ O) s  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:
" L' V- `2 F# D类型值 含义
& k$ I9 W) b, b# e( }CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。 ) k8 u, `; O& i1 x0 t
CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。 ' p; b: F) ^# r$ j) |5 P& u0 m0 z
CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。
. D* A! r& E8 S% ?' Y. j" {CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。
8 _* ]6 \. j; R+ n表9 pResult的类型值与含义(一) ' `. n# h& Y$ z+ y
  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:
3 k7 B8 T# X$ i2 I4 x类型值 含义
4 T0 J+ f, ~- g1 D* s9 t( pCDRF_NEWFONT 指定后续操作采用应用中指定的新字体。 ) w' N) Y$ C( a( v. S& ?2 F( v3 n
CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
$ `2 m9 {: b5 D+ @' v. @: Q! KCDRF_SKIPDEFAULT 系统不必再绘制该子项。& @$ R  n  }- y9 j
表10 pResult的类型值与含义(二)( i6 x* z( K5 C$ c( Y; Y* S9 v
  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子: 7 c8 h# t* n; l5 U6 O3 G$ A: i
  图12 利用NM_CUSTOMDRAW消息美化界面
( A4 d! ^* u% E/ z; u6 ~  对应代码如下: % K) a+ L" X: O, k
  1. 6 J0 K7 x3 \: @7 E
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)+ d/ \8 L$ N) V% K8 r
  3. {) x. }; [! L3 C
  4.         //类型安全转换
    2 H- U8 [7 t) [4 \7 F
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);& U' p0 ~1 h* {9 X$ }
  6.         *pResult = 0;' g" M* ^3 `1 F
  7.         ( l0 D  v: N5 `6 r' n
  8.         //指定列表项绘制前后发送消息5 o! W" j% C( a8 _* f& M& e2 a
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)
    9 j" o( B" q+ C1 u
  10.         {
    6 K) C+ U/ }4 a0 ^& ~6 ]& h8 v) ?$ Q
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;
    8 y7 Q1 {0 ^& c
  12.         }% K7 T4 ^" R! ?) C9 G1 V
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)7 A" m- }; n$ B: [! t/ G! @9 s* m
  14.         {/ t' j+ j2 ~6 S% @1 w$ P9 U5 g- f
  15.                 //奇数行4 \1 t' i7 J4 r0 f
  16.                 if(pLVCD->nmcd.dwItemSpec % 2)
    % K1 p% C6 H; d' R0 g
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);# i7 W8 I4 r  ~+ V- v# j0 [+ x! b
  18.                 //偶数行
    5 k% @3 S5 ^& w' T8 Z$ j
  19.                 else1 I' h  y# k8 e+ B' |9 T
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);
    : z' y- M% t% ^
  21.                 //继续
    . F. N( Q0 e) U% f+ D
  22.                 *pResult = CDRF_DODEFAULT;) M1 G: j1 @) g
  23.         }* \2 T; {( ]7 o7 u( ~
  24. }
复制代码
; k$ h& m. {& j; \1 q8 Y
  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。 * @4 o) \/ m! m4 Z  }/ E( n

3 q( Y  U1 x8 q  3.4 使用MFC类的虚函数机制
: j, G4 h$ G  Y1 a  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子:
! o( g  F% k( M9 I" F6 {

  1. & y" \9 K: s0 ~' f/ H
  2. void CView::OnPaint()9 h3 b# _. y" g& T/ h/ d
  3. {
    1 _3 |" ?1 I" M" T$ O. r/ O  `
  4.         // standard paint routine7 F2 y2 q: q& g, p2 W3 I
  5.         CPaintDC dc(this);
    2 I- p/ b6 V" {6 H1 H, Y. K4 C& D
  6.         OnPrepareDC(&dc);, z5 C: ]5 q% f7 ]9 b' V
  7.         OnDraw(&dc);
    / L! h9 U, v# A# X3 z
  8. }
复制代码
: f% q8 W+ D, c: g
  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。 ) n% @$ f4 m( D" h, B
  以下列出了与界面美化相关的虚函数,参数说明略去: 3 R. |2 h! k! a+ Q) s  b
CButton::DrawItem
; m  j, @8 x  g, v& i1 t4 _CCheckListBox::DrawItem : r4 N# R2 N$ L7 c
CComboBox::DrawItem 5 U' U7 Q( a2 I! ]7 G& r4 F
CHeaderCtrl::DrawItem
$ t! A0 H0 h" s. X5 l  I4 ZCListBox::DrawItem ' C% u  W- o$ y5 G
CMenu::DrawItem
$ U# ^% M/ S& x4 a+ _5 e. ^CStatusBar::DrawItem
1 R8 }# W5 s. b4 bCStatusBarCtrl::DrawItem # G( `) O8 a5 u# w' a
CTabCtrl::DrawItem
* x  S2 u' C7 F/ I- w8 v0 y, Avirtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );   f1 _% X+ K- J6 `$ l
Owner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。5 O  e) @' K1 M0 h
  限于篇幅,在此不再附以例程。
! ?' n' q* X8 T' q4 J
- E7 G: ~. ^3 b8 S  d参考文献
" Z, D3 m, K6 b, Z$ X# G9 K本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2025-6-19 09:23 , Processed in 0.017783 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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