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

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇
8 s! a6 a2 \3 B9 s5 X( k  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。
: t( s6 n) [5 D4 r! R  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。5 K! U. w" D& f5 I: W
! F/ l- A& G9 v  t! d' M( f
  2. 美化界面之基础篇
- {$ M5 X/ ]2 k  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……: q6 M4 K9 n# Y1 |2 J0 a. E! T

" q' E6 g+ G6 ~# p  2.1 Windows下的绘图操作6 H: @" E/ X) c& [
  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……
0 ^. H$ \6 A6 u$ p7 m2 w, Y( o  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。0 Z; [/ l& s# H& J) D3 N. t% S; r! H
$ a, y$ a# h% \8 k- ^" [* C; f+ {
  2.1.1 设备环境类( D6 m) b4 P' }# s9 h7 ~  h; J, ]
  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。
1 z. h* x' x3 i- _  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:! s& Z% a0 d1 @. l' \( l
  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式' r9 i) O% X* ^/ i
  Mapping Functions:映射操作* s1 L/ W# v* S, R; I2 ^
  Coordinate Functions:坐标操作
5 S( I; V& H8 ]" n- _! u  Clipping Functions:剪切操作
) a0 {( R% R$ S  Line-Output Functions:画线操作
( W# m/ v6 _5 e  Simple Drawing Functions:简单绘图操作,如:绘制矩形框
& {6 K) y* l) D1 U/ l( f5 Q" U  Ellipse and Polygon Functions:椭圆/多边形操作, |& x& g( f: z" C* Q2 g
  Text Functions:文字输出操作
# M% W4 q# n: ~( |& ?, G  s  Printer Escape Functions:打印操作$ I. V& {& |& Q7 L) k7 u1 G( d
  Scrolling Functions:滚动操作
+ F% M2 {; h2 U# \) X4 n' t  *Bitmap Functions:位图操作5 A) o4 e5 ?8 u  }, }3 w
  *Region Functions:区域操作2 ]" r+ A4 [- w  c- @( x. P" J
  *Font Functions:字体操作
! ^# X/ j, N# d4 k: z1 ^( O% w  *Color and Color Palette Functions:颜色/调色板操作: B1 j: x7 e2 Q  m2 |7 m: F
  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。
( W: c  f9 A2 ^- A0 X) L5 P) _8 o# ^0 R  T1 V4 x9 Y$ G
  2.1.2 图形对象类
0 j) e6 B, m( K) s3 ?3 ?& y  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。
: a, w; q& X" ]( F: d% f- o  下面的表格列出了MFC的图形对象类:
) Q" p) |) e' t* t; L  MFC类 图形对象句柄 图形对象目的
0 }9 k! s% R& t' U. ~1 h# ]- M0 u! o  CBitmap HBITMAP 内存中的位图
! J; D' m9 ^/ a  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式
# A+ q: l( o: d% x) V; C  CFont HFONT 字体特性—写文本时所使用的字体0 \" J+ n8 [2 t; `8 `( o: p
  CPalette HPALETTE 调色板颜色
9 J0 f& p+ h& l5 l  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细
: d5 i+ g9 r  a  X  CRgn HRGN 区域特性—包括定义它的点0 a2 v5 I  t, X; n$ y4 q
  表1 图形对象类和它们封装的句柄
& p: D" P- m3 h: ^& T  j8 e$ K7 H3 \3 Q; R  G7 D$ j
  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面: : g9 ?5 I) U% v! z/ x
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:
- y  w+ H# r( ]1 c

  1. / U5 h" Z' q$ `
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)  z/ l/ a3 H6 ]- O
  3. {0 U2 E) b& Y5 Z9 T. G/ v
  4.         //设置背景色9 H3 S7 T3 W! v: U; s
  5.         //CBrush CUi1View::m_Back! v; J, }0 K) x5 R$ O: k/ ]
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));$ ]' z3 T  s/ d" h2 j, K
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL); 9 W% F7 a+ v& |& s( E  k9 u' k
  8.        return CView::PreCreateWindow(cs);
    . r7 A- K) D7 @8 o3 ~: w! l
  9. }
    " `3 g1 T& {  G
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)1 X% h/ S- Q* ^  A* x+ N3 f) P5 l
  11. {% Q9 m: e& l9 B/ o
  12.         if (CView::OnCreate(lpCreateStruct) == -1)
    - H: ^/ @  i; z5 m  {: z6 ]2 w
  13.                 return -1;2 T* g5 W3 V/ W6 p
  14.         //创建字体
    6 b8 j- }9 @; r) r1 r9 y
  15.         //CFont CUi1View::m_Font
    9 o2 n! D" S- |. J2 j2 f. I  _
  16.         m_Font.CreatePointFont(120, "Impact");
    . S) b8 V3 }' K, w) s+ Y
  17.                return 0;
    7 _3 L' A2 B2 x8 \6 z  q: g5 ^
  18. }
    3 r8 G7 `7 ^5 U
  19. void CUi1View::OnDraw(CDC* pDC)
    ' ]' w4 R$ ^! H, y6 p; ~
  20. {
    0 V5 ?4 I5 ^* M& n6 a
  21.         //绘制按钮框架) x$ {; l% ]: ^# U
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);
    # s4 s. K6 L% `$ v# E8 Y1 ~2 b
  23.         //输出文字7 K3 o# ]0 z; ^! f6 X
  24.         pDC->SetBkMode(TRANSPARENT);
    0 `4 X( P8 c; }
  25.         pDC->TextOut(120, 120, "Hello, CFan!");
    ! l$ Y7 `, z  a- s% \
  26. }
复制代码

+ {: R# f% W- Z4 \$ v/ A) _; l  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。 ; [' e! ^: I+ F
) l- x# L4 R. a8 g) N( R- I' M+ t
  2.2 Windows的幕后绘图操作
  @* V: N2 I, F) A  在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。
( _& F3 `: p9 I1 B; R  有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。
5 A8 t+ q6 S; G& j% A! d# Y. D  所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……, A2 y* M4 i  M) A
  有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。
, k# D6 `- ]2 Y# k9 Z, E9 ~
# ]+ j% t9 I7 c, a( i# g  3. 美化界面之实现篇
& d7 r/ s, ~% k$ E) ?  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。
0 Y4 K9 p2 a' u9 [6 t
* x7 x% r" l, A; M( g5 V  3.1 美化界面的途径
' v" a0 _9 m4 K  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:/ }( ?9 a- j  E8 c5 _
  1. 使用MFC类的既有函数,设定界面属性;
: M* @. [9 r- N. `  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;, S9 T  J3 `9 j
  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;% p) q+ ^, w% H2 y
  一般来说,应用程序可以通过以下两种途径来实现以上的方法:2 O; B" Q0 M1 e9 j  g& s# P
  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;9 p: v' W9 P( ^3 K3 m9 _  d
  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。% f/ `8 z; w. |' c
  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:
4 J+ p9 U, i" ^( `; w0 `  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示: ; F7 \% O  ]; i
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;
. [: R* E% Z+ m6 c  以下的章节将综合地使用以上的方法,请读者朋友留心观察。: f* N/ _8 u% A9 h3 ~7 L4 R

* j: f4 U* b1 Z$ K  3.2 使用MFC类的既有函数
# G7 x7 C% N; s4 ~' G9 V  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。% d! h/ u; c$ v0 h

5 B% @7 B* i9 mCWinApp::SetDialogBkColor
3 [% q( a1 o4 z$ Wvoid SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );
( E6 R/ D5 N9 i7 ?8 v, H指定对话框的背景色和文本颜色。2 I$ F; [1 ?4 R
- N% f$ n$ `: e$ {/ ?
CListCtrl::SetBkColor
9 o* b; D9 p( P) j& fCReBarCtrl::SetBkColor: x5 X- ]* c" q; m/ e
CStatusBarCtrl::SetBkColor
2 B8 X: S7 {2 y! I0 ?' wCTreeCtrl::SetBkColor
* j3 |  V) u& NCOLORREF SetBkColor( COLORREF clr );4 ~/ m) S; g; a/ Q- O& I- ^
设定背景色。) q% I+ r. _! m. J4 C& v9 Q

! B6 ~) k0 m! U. _6 nCListCtrl::SetTextColor
6 {+ c4 r9 b1 K' q9 s5 q, }4 hCReBarCtrl::SetTextColor5 Z* ?6 q0 c, L& q
CTreeCtrl::SetTextColor/ A5 b8 ~1 f, V4 t  ]
COLORREF SetTextColor( COLORREF clr );- C. M0 s8 d; }) h
设定文本颜色。
: I& X$ Z3 g' R/ @4 e# |( ?) Z5 X/ r0 I1 h$ G  J* D
CListCtrl::SetBkImage" E) v. F! ^# ?9 g# ^8 y
BOOL SetBkImage( LVBKIMAGE* plvbkImage );
+ y/ c5 R$ w5 y% _BOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);# r! g% n$ y. u' b
BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );3 d3 ]" N  U6 f) p
设定列表控件的背景图片。7 _. C1 s7 Q! \/ q
  o6 X: c3 [$ [. \) [- o2 p
CComboBoxEx::SetExtendedStyle4 C- Q/ |3 J& E" m$ p- ]
CListCtrl::SetExtendedStyle
6 e9 ]6 r# S' [- n" tCTabCtrl::SetExtendedStyle5 X8 \6 c3 C+ Z! Q9 y
CToolBarCtrl::SetExtendedStyle
) g7 ]& s8 M4 ~3 \1 cDWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles ); . v% K6 @% X9 o7 {
设置控件的扩展属性,例如:设置列表控件属性带有表格线。# G6 r* y3 i3 `8 @$ c1 E. B
  图4是个简单应用MFC类的既有函数来改善Windows界面的例子:
/ ?' w( K5 c- Z) y
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:( ^. O- A2 P$ M4 s. W8 r9 x+ Q
  1. ' x' O" f$ n! K
  2. BOOL CUi2App::InitInstance()
    9 K/ b7 X& q( @6 J' g# G* a
  3. {
    7 X/ Q3 E. n" |5 z5 g
  4.         //…
    & F% M6 c0 _% Z. E* r/ x6 }
  5.         //设置对话框背景色和字体颜色
    $ u* Y5 Z9 Z  K
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
    + s5 N9 F# Q% k8 S1 C' I1 e' a
  7.          //…& n  O$ W, L; j3 U. o; L3 K1 g
  8. }
    8 J. q8 j. ]' K: b0 r
  9. BOOL CUi2Dlg::OnInitDialog()( Y" u! d. h! e. Z
  10. {3 E& Z3 n7 g/ O, P  I( Y% a
  11.         //…6 m* K6 I: g8 p
  12.         //设置列表控件属性带有表格线
    ( X# X5 d% r5 S9 T
  13.         DWORD NewStyle = m_List.GetExtendedStyle();
    5 x* ?4 O  e5 x
  14.     NewStyle |= LVS_EX_GRIDLINES;% l' L4 P( c! _2 C# [
  15. m_List.SetExtendedStyle(NewStyle);3 ~! E  g6 N2 c
  16.         //设置列表控件字体颜色为红色" t5 Q9 t0 j; u
  17.         m_List.SetTextColor(RGB(255, 0, 0));
    " i. i/ j6 u1 E$ ?! r2 x9 T
  18.         //填充数据9 d" P& O! P8 c6 e
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);  P" @( U. C! Y2 r8 C% L. L: T9 p, I" M
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);6 M  a/ l8 t" N. A( a) P, D
  21.         m_List.InsertItem(0, "5854165");: I6 s1 l0 K/ V7 j, @/ G4 m) U5 \
  22.         m_List.SetItemText(0, 1, "白乔");) w2 U6 C2 n  @* M" G
  23.         m_List.InsertItem(1, "6823864");
    * q! F3 g- K! u0 r
  24.         m_List.SetItemText(1, 1, "Satan");
    , Q) ?, a) p+ x! i% m; }0 Y+ f
  25.         //…: T, p1 P: k' i6 U
  26. }
复制代码
) ]4 S$ j) s5 w8 ]
  嗯,这样的界面还算不错吧?
* D8 H# M$ V1 f4 B5 ~3 q% C
6 t0 i1 J" q+ |" E0 U. C  3.3 使用Windows的消息机制
2 o3 S* u: p2 I9 P: \) \+ Q  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息:
6 M9 I% Y9 l7 v/ }3 K! `3 AWM_PAINT
: Z6 S3 k) k4 eWM_ERASEBKGND 1 ~1 J, o# j4 a
WM_CTLCOLOR*
% q" R3 L- n6 f9 ~WM_DRAWITEM*
: S3 V+ [) `' ?+ E! kWM_MEASUREITEM*
+ }9 P8 c' {( K$ Y9 @NM_CUSTOMDRAW*
# Q0 O! x; ^3 s: Y& S- K$ q. V& E  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。
. A& k+ I8 L$ N6 Z# v" D( Q, b" w* m7 v$ R/ ?, F
  3.3.1 WM_PAINT
7 f4 R8 D+ p& [( ]  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。( X0 M3 R# u9 ~, A
  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下: : u% F7 p$ E5 n" n: {5 H
  afx_msg void OnPaint();
# l  y/ I/ P7 V3 Y" O2 M  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示:
8 }9 e& Y6 [+ Y; X! b
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单:
) {6 x! L4 L: [

  1. " }$ L$ a6 N3 u! a9 I% ]& x1 H
  2. void CLazyStatic::OnPaint()
    8 X5 X; y" N* x' s' I( J& G3 Q1 R
  3. {
    4 J7 }" k  V$ h( x+ j- z
  4.         CPaintDC dc(this); // device context for painting
    $ d# q: L6 B- w) a' h& r
  5.          : `; M) \' R! Y. H
  6.        //什么都不输出,仅仅画一个矩形框) w1 _/ u* S6 Y
  7.         CRect rc;
    $ h# Z1 H- s: w+ o4 v. x: v
  8.         GetClientRect(&rc);& k1 f: f6 M9 H" [! y
  9.         dc.Rectangle(rc); / t) Q. N) @3 I" ?* Z6 K
  10. }
复制代码

# c/ q( C9 j9 `# E8 ^  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。, m* u+ v1 z) D- E' k& j
* [+ U6 U1 y3 A  w- R1 f
  3.3.2 WM_ERASEBKGND
" z+ M6 `5 m& K4 T0 o  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。
4 q" H0 Z* a; [4 T9 ?( \4 b  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:5 e7 S9 ~# p+ w7 r0 g
  afx_msg BOOL OnEraseBkgnd( CDC* pDC ); ) ^6 k4 ~% ~4 |* K& v/ X1 y/ E% X& c
  返回值: 4 B( i. u' R$ V1 C* x
  指定背景是否已清除,如果为FALSE,系统将自动清除 ! N9 R$ a3 ]" @! A* v8 J% |
  参数: pDC指定了绘制操作所使用的设备环境。 ; h4 R* U: O" X( p9 X) v/ W
  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景:
/ n: |$ r* c5 x
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单:
: D+ O( p" X. V% k" t

  1. $ ~; k( Z2 d: V# L
  2. BOOL CUi4Dlg::OnInitDialog()
    " b' Q0 f, L, c" ^7 z% J$ U
  3. {
    ( m& M5 ^, I! K/ K0 d% e5 L( D1 W$ ?
  4. //…9 O# m3 h: _: z% Q. K6 F5 R, c  M
  5.         //加载位图
    8 M# C5 ~+ E+ g3 P( N# ^/ _
  6.         //CBitmap m_Back;
    / J: x( T& k# U0 O) w4 q2 Q
  7.         m_Back.LoadBitmap(IDB_BACK);
    ' g7 a( z5 @* O) A+ m
  8.         //…6 l' P; I! k2 h# V0 \
  9. }
    . O3 H% C9 k' }# i) V0 n
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC)
    . i1 I5 E2 z; Y& A! J
  11. {
    ! }/ d+ Z& g* I/ O' Z  K& S( p
  12.         CDC dc;% x: F; o  E% s# l, h$ E$ d
  13.         dc.CreateCompatibleDC(pDC);5 P: z" M! D. s& C; @
  14.         dc.SelectObject(&m_Back);2 x5 y6 D( |. T- J
  15.         //获取BITMAP对象
    # _3 J2 i+ ~. l8 O
  16.         BITMAP hb;8 v4 t2 C* _. t, u" j
  17.         m_Back.GetBitmap(&hb);
    . G: O) [9 e- _' `! z; X2 Z5 X) p
  18.         //获取窗口大小
    : f( X5 I$ j2 h% w$ B
  19.         CRect rt;  Z. |0 `5 O% O, @& U
  20.         GetClientRect(&rt);' g/ U- a! p2 [, K. O' V" M5 Y
  21.         //显示位图
    6 W# [* I: q' U
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),1 w$ J3 u- s9 H; i( n
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);7 R7 w$ U# |/ ^" r& m3 ?) L' l9 w: `
  24.         return TRUE;
    9 M# R" w0 X0 \9 d6 b
  25. }
    ) n2 C/ i3 S# w1 V# T. J
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) % l0 p+ n' j9 z' Q
  27. {
    ! B' ?/ n) C, A) `& P* H, h
  28.         //设置透明背景模式. U+ S) l! n# j, t+ I( r5 g
  29.         pDC->SetBkMode(TRANSPARENT);
    - y, J" l8 C3 L7 {% }) r; K5 F
  30.         //设置背景刷子为空
    4 t8 ^8 E* D; ^' p; z, h7 e
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);% Z! c3 X0 v  N' s
  32. }
复制代码
0 Y1 l6 a5 S, Y' O" Q
  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。 6 b* K, X8 C! C" a% m$ X7 G
4 U7 w0 V7 c: @0 f4 A9 i
  3.3.3 WM_CTLCOLOR % j$ d  t5 I8 X. g' Y1 `) }$ a, r6 I
  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。 0 }, n1 H( L/ s
  WM_CTLCOLOR的映射函数原型如下:
3 b( ^2 z& K9 j. A' I& b! M  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );
( j$ j' T2 C; H' }* W. b( s5 t  D  返回值: 用以指定背景的刷子 0 W. M" B/ Z0 q% W9 h# v% `4 u
  参数: 8 \- M6 U  p' G% m
  pDC指定了绘制操作所使用的设备环境。
/ i# _5 z% J1 x. Y  {2 R3 W) @  pWnd 控件指针
5 c# L& J. ~& A' |5 n  nCtlColor 指定控件类型,其取值如表2所示:! P; D5 j' B& u
  类型值 含义 1 \6 I5 Y- ]( G3 U
CTLCOLOR_BTN 按钮控件
! d0 n# {5 G5 i0 o8 J/ |' c5 ~CTLCOLOR_DLG 对话框 ) w+ Y: b  Z  U) {! ?6 E0 {4 k
CTLCOLOR_EDIT  编辑控件 ! v' Y) @) D) s9 ]8 H$ A& Q$ J, C8 q
CTLCOLOR_LISTBOX  列表框 $ ?- ^% S: Y% x2 g2 {
CTLCOLOR_MSGBOX  消息框 ) M1 D$ L) o9 s. y& p+ M7 [
CTLCOLOR_SCROLLBAR 滚动条 ; z4 n* w+ n) O0 F/ j; h
CTLCOLOR_STATIC 静态控件 * ~! B5 }% i! W" }$ t
表2 nCtlColor的类型值与含义
- F) g& J# ~/ E$ ~) q7 W! |% h# b: s8 c0 s" G( e, x+ W
  作为一个简单的例子,观察以下的代码: . _" [/ Z% l7 v, `: c2 J/ U+ l

  1. ) s0 Y' }) b! S
  2. BOOL CUi5Dlg::OnInitDialog()
    # c# c* n8 A3 ]/ J8 ?
  3. {  K. o: f% z) S2 w9 B
  4.         //…
    4 h" _% {8 P2 O% ^- d, x( i
  5.         //创建字体# a/ E) n; j  T" m- s" e
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font29 Z% B- n* W% W
  7.         m_Font1.CreatePointFont(120, "Impact");
    8 R8 [- c6 f7 i6 M0 f2 H. I  k5 V+ E
  8.         m_Font3.CreatePointFont(120, "Arial");" b8 ^# Z: U) q0 N
  9.                 return TRUE;
    ' a: W) P3 y1 U* [
  10.   // return TRUE  unless you set the focus to a control ; J( H! Y7 p# Q% W  [* ?8 H
  11. }
    1 i" r9 e7 U+ B: \1 e
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    2 Z7 v" f' X& ~& ^
  13. {
    ' a5 e' {0 f2 J
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);" J* s+ n0 F0 w1 w& W$ X( i
  15.         if(nCtlColor == CTLCOLOR_STATIC)9 \# Q: H4 f4 x) R# j8 O6 z
  16.         {' u0 L, K1 o0 {8 ^1 S) Q
  17.                 //区分静态控件( w0 Q  A& t' Y( q, _- b
  18.                 switch(pWnd->GetDlgCtrlID())
    5 {: ]" E8 u5 z; H! o. G
  19.                 {5 M% U: |9 C; X1 k3 T
  20.                         case IDC_STATIC1:+ ?! M, I# P1 z
  21.                         {
    ( ~8 N7 h  C  l+ `. I7 o% Y
  22.                                 pDC->SelectObject(&m_Font1);5 G2 `0 i7 a. Y* f7 q3 ]
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));
    0 w: a  W3 D. a* ~
  24.                                 break;' X' v# J' _1 I2 p! \2 ?" v
  25.                         }! v/ E% U, k/ ^# p
  26.                         case IDC_STATIC2:" P) p* E7 r, ^$ q! v
  27.                         {0 c; k7 B6 u' L: ~+ C
  28.                                 pDC->SelectObject(&m_Font2);# K( b; g8 g' i0 F. P1 A8 v; c' Q
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));, N8 `$ a( N* l
  30.                                 break;3 n1 {9 Y0 |& n" ~' O- ]
  31.                         }; }, }& o' h* ^4 t2 h! S
  32.                 }
    ' |# `+ I$ F5 {8 ^$ O8 g7 ]
  33.         }
    5 m# Z) D" n% U1 y+ C7 ~/ |
  34.         return hbr;# G  I) C6 a) G  M: v
  35. }
复制代码
! s9 ^/ o8 t+ v
   生成的界面如下:
$ z: d, F5 F1 v; g1 S
图7 利用WM_CTLCOLOR消息美化界面

" t% E' B+ n" A3 n* m  3.3.4 WM_DRAWITEM 1 `9 {  l& i: e" x0 x
  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。
% x6 [. y+ g. H, I! h) q; b% C2 E  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。
8 j& R* \$ W9 C( u% d" e  WM_DRAWITEM的映射函数原型如下:
; I) o% N" w& Q2 [: i  L; p: |  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );
7 W: ^( p5 m$ G5 [7 ~  参数: 6 {& G' f( e; T7 z, ?3 C
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 & L* W# K/ z/ A# F# k+ H
  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下: - v- Z" }' V- K! h

  1. ! M0 }/ S) z' s% ^2 v1 T& E) m6 b
  2. typedef struct tagDRAWITEMSTRUCT
    1 `/ Z- d/ C& w/ m7 v& ?) _
  3. {
    7 {/ g( L5 |1 z
  4.     UINT   CtlType;& _- i; m" v1 Z  x
  5.      UINT   CtlID;1 S2 c9 D7 y( s( `- ]6 \" \
  6.      UINT   itemID;
    , S2 c2 D, r4 u! L5 c
  7.     UINT   itemAction;
    ( M2 v1 m8 F8 ~. ?0 e
  8.     UINT   itemState;6 f4 U/ A& e; q( a
  9.     HWND   hwndItem;
    # G0 s6 ^0 T' U9 w( G/ _
  10.     HDC    hDC;
    : M! K4 M5 ]4 J7 R8 |
  11.     RECT   rcItem;8 R$ v" s0 C3 \; L1 y
  12.     DWORD  itemData;- {! g& i$ r2 l1 D" X$ Q
  13. }DRAWITEMSTRUCT;
复制代码

0 M6 H0 i- B  Y5 U3 f& U6 ?CtlType指定了控件的类型,其取值如表3所示:
. V, u: t+ u! l4 V类型值 含义 7 e& N, O  [$ w) |
ODT_BUTTON 按钮控件
% }, X6 L! f6 Z: M7 |6 Z( W, ]! ]ODT_COMBOBOX 组合框控件 & G! \1 L- ]. y! R% r9 [
ODT_LISTBOX 列表框控件 2 x8 e0 v6 {& ]0 b, N
ODT_LISTVIEW 列表视图
/ K7 y1 }! o  J% N# QODT_MENU 菜单项 - i5 w( ?, L7 R6 r
ODT_STATIC 静态文本控件
( v$ R/ x2 N# e& B$ k! `ODT_TAB Tab控件 % L3 o. Q1 M) m
表3 CtlType的类型值与含义
6 i: v1 T* f; C* U: _4 a/ r% l2 l% w3 V7 ]5 _" U
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项 5 U% x( L% ?& }2 S$ Y
  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。
7 X" ~& `% ?' \3 ~8 R  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:( @. S/ M* z- g$ H
类型值 含义
, E. m/ q( U6 z1 qODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。 % ~( Z1 Z! z+ K0 ~" A# ]8 m
ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。
6 ^- y" j2 g" p4 JODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。
9 o  ]6 g4 q# J& F表4 itemAction的类型值与含义7 r* ^# |' Y. A9 V
  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:
8 k) [7 Q, T4 c. I" l: r类型值 含义 + }" ~( U3 J4 W  P0 ~2 o
ODS_CHECKED 标记状态,仅适用于菜单项。
" h) }2 j: z  e- o0 gODS_DEFAULT 默认状态。
7 {8 p$ [( ?9 z+ n: t) p/ G" v4 S- i$ zODS_DISABLED 禁止状态。
& m; j/ C3 b  f) N0 M1 k6 t4 X* AODS_FOCUS 焦点状态。 ( O6 a+ R7 k# x: x/ ~. y
ODS_GRAYED 灰化状态,仅适用于菜单项。
+ D. S- j; V+ x; B; `: _4 PODS_SELECTED 选中状态。 " g  C' m: X' q2 j" }& i0 j
ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。 9 T8 ~  u/ `7 `! @
ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 3 h$ U4 t$ u* y/ ^5 ]
ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。
0 j9 d! q5 G3 Q, rODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。 # s. I$ c8 w8 `2 e" o' u# D: _2 e
ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。
+ [" w" }9 o6 ?+ f% _, r! z6 N1 v表5 itemState的类型值与含义% z# z1 @% E: \# Q' c5 r* V7 g
  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。
, m8 ~( Q! a1 b2 d! {; Q' a# v  hDC 指定了绘制操作所使用的设备环境。
- q/ c6 _  \# ?4 a3 O- m+ u/ L$ v6 y  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 " L" H5 U( g) c) V" |
  itemData
5 D4 x; `! \* p0 H% Q6 A$ e! o  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
2 U; h7 `# i% X* j0 W6 c  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 . d9 ^1 s$ i$ L8 K) M4 P5 t
  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。 % Z* o8 z9 J0 ~) l/ n$ ?
  图5是个相应的例子,它修改了按钮的界面: " ?2 |9 _% c# h- e! U
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下:
, X& R2 U3 D% ?7 n$ f( _1 }- f
  1. 1 U! g; h, U8 H; X4 h. q" |
  2. BOOL CUi6Dlg::OnInitDialog()
    4 A( C* R9 z. F! s4 G# |6 h  S
  3. {
    / K: ]: C# j: _. Z
  4.         //…
    - ~& }1 l( G. L1 V
  5.         //创建字体3 y( c0 x, R+ I# ^5 [: \
  6.         //CFont CUi1View::m_Font
    8 l( @, r( x/ o
  7.         m_Font.CreatePointFont(120, "Impact");0 |; Z) |3 W" J) q5 `7 I+ P
  8.         //…" B% O- m' m( n, s! f
  9. }
    ' Y9 g& p" q5 R& X0 X# ^3 p, @0 t
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
    ; x% I& q; j) ]9 \- ]/ N
  11. {
    9 ~6 Q( M+ ~, j! A  W- w1 X
  12.         if(nIDCtl == IDC_HELLO_CFAN)" c7 C8 J! c0 r9 S2 O, f; `- R6 P
  13.         {* N" M# h% i2 R1 J4 s7 h% E
  14.                 //绘制按钮框架! S* X4 K( ~4 b( j+ y/ y3 W
  15.                 UINT uStyle = DFCS_BUTTONPUSH;. q+ j9 ~1 m( z* h3 i" Q
  16.                 //是否按下去了?
    ' m5 T% T+ T  W& A$ D7 }
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)# ^0 n: O& u& e, w2 u
  18.                         uStyle |= DFCS_PUSHED;; Y% O; c+ G# P
  19.                 CDC dc;1 z& N$ g6 I$ U$ b* m
  20.                 dc.Attach(lpDrawItemStruct->hDC);
    + ^( c# C/ a: M3 _2 L, B8 L9 B' Y
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);& T) c6 q" ?1 Y5 J
  22.                 //输出文字! {* |6 \/ |' D" G& \) G
  23.                 dc.SelectObject(&m_Font);
    ' j+ Y2 Z* {6 C5 w
  24.                 dc.SetTextColor(RGB(0, 0, 255));
    : G, o! [) b! e
  25.                 dc.SetBkMode(TRANSPARENT);: L5 n3 X  j1 @3 w9 R
  26.                 CString sText;
    & l  }& K: C! s# C* N
  27.                 m_HelloCFan.GetWindowText(sText);
    + |0 _& }5 C' A; |) e& M9 k, V
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);
    + w; v4 Q5 X1 v2 t3 J+ z: R& M) F
  29.                 //是否得到焦点
    ; }4 \/ \. R0 L
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)
    ! _* Z; K1 V. k! k: O9 S, t
  31.                 {9 Z. _! z- Z' W" }1 `3 r
  32.                         //画虚框
    / U9 [: t, c  h* N, [. G! E
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem; 8 [1 z; {; D# x1 ?( K3 i
  34.                        rtFocus.DeflateRect(3, 3);3 \1 a# B& g* I, H4 [6 T
  35.                         dc.DrawFocusRect(&rtFocus);
    ( l8 l' s' q9 ]9 ^+ F/ l
  36.                 }& O  X! a9 R0 W0 g
  37.                 return;
    - q) b8 |; T7 Z* Y
  38.         }
    & ^2 K5 s8 [" d! f! q
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
    4 f: H. c7 l" r2 o* O
  40. }
复制代码

. L8 h- [- {; |3 d0 l  别忘了标记Owner draw属性: % A- |7 t0 T5 ^6 \: {
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。
* }+ W# |/ B" r' ~, O  {' ?9 w$ u
( ]. a7 q8 I5 P# c$ Q, X3 G  3.3.5 WM_MEASUREITEM 1 z8 U2 c+ D3 N" h$ _+ S
  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
' b9 u1 E- l& c4 _% \  WM_DRAWITEM的映射函数原型如下: 4 G2 @) k; u  `1 \: ?
  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct ); " @' H' J) p- X& c
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l
2 V' e& p* w  r% V4 @4 \3 n  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下:
+ ]3 T  R$ ^( t8 J3 W' W

  1. 1 b" [$ a& b6 o9 e% D& l/ }
  2. typedef struct tagMEASUREITEMSTRUCT8 U9 f) u6 c+ a; ?) e% T& L0 W) c
  3. {
    , R( c; Z, \: h: s9 D- t  {
  4.     UINT   CtlType;9 y. Z5 p: Z& p2 c5 {
  5.     UINT   CtlID;
    & X& }4 t5 g9 K0 N7 T5 N8 |
  6.     UINT   itemID;: X9 ~0 o/ ?$ W% f% I
  7.     UINT   itemWidth;9 l# @) X& v  |# x4 U& l3 e
  8.     UINT   itemHeight;- j# t% w9 x8 D! c4 b
  9.     DWORD  itemData;: B; H* z; x) v* d) ~" Z3 V
  10. } MEASUREITEMSTRUCT;
复制代码

, d* d7 P' |" G) z& H" M  CtlType指定了控件的类型,其取值如表6所示:8 `. b3 B; n' k
类型值 含义+ w1 D6 I& t& T7 V5 h% H0 ~$ Z: E
ODT_COMBOBOX 组合框控件
0 p2 e/ @% i& V! E5 u+ AODT_LISTBOX 列表框控件
* q: a' c) a6 p3 kODT_MENU 菜单项 7 p% J4 f/ E: d; s
表6 CtlType的类型值与含义/ k4 s; k" Q" o* T3 x
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项   X0 Y  m' J- m  l/ O+ d1 c
  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。
0 \1 l# [% N) z  ]! Q7 N' ]$ D  itemWidth 指定菜单项的宽度 . t( |1 C! ^( Z. H& F9 r2 k& g% M
  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255
& X; m) I! W+ x  itemData
7 u/ E9 X2 w& W" H- E: m3 `  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 . [4 r3 t4 \7 R/ E" S
  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
) Y+ n5 C4 v8 E- V6 Q  图示出了OnMeasureItem的效果: 2 J" X3 z$ _% C; o6 q. M! L
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下: & y. I+ `" b( m% s/ H! Q0 z

  1. 6 w# k, O0 F' }* `
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
    7 g/ K, V* o. w- }1 A
  3. {0 e, N. ^1 u* Z' z0 V4 d+ G
  4.         if(nIDCtl == IDC_COLOR_PICKER)8 _: G! S& l# |3 c7 Z! \
  5.         {
    & q2 f# x& z, c
  6.                 //设定高度为30
    * t) x: g4 U* Y! b
  7.                 lpMeasureItemStruct->itemHeight = 30;
    : g1 L* }% j$ v% l: i; A
  8.                 return;) H1 C: R3 l: n
  9.         }7 |8 e/ W) c; K, A
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
    7 O3 i. C! L: D4 b
  11. }
复制代码

- {% G8 X' @$ j, Q  同样别忘了指定列表框的Owner draw属性: " x8 s$ ]0 z# G! W8 v
  图11 指定下拉框的Owner draw属性  , [5 U3 l' N, k# ~$ M0 v
. T& u$ u+ d# Q3 c# L$ w0 L3 [+ S
  3.3.6 NM_CUSTOMDRAW 4 b" \7 X1 |; a0 {$ T/ U  U
  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。
9 W5 S! d! h" p  J' f: O  可以反射NM_CUSTOMDRAW消息,如:
- \5 V% x  M" @. C; F3 A, E6 K  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
/ Y/ D5 b" u& E3 p  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); + \0 X; A7 H$ r, i
  参数:
& F% P$ b( p3 Z/ }2 ~  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:
9 {6 j3 G7 l8 d* Q. X& f3 P$ O( Z

  1. 6 O3 G2 t: X! E
  2. typedef struct tagNMHDR& K8 p; Q# x# ?" ?9 e& k, `$ h# A
  3. {& z1 f' k$ X3 e6 R1 N
  4.      HWND hwndFrom;9 V1 s2 K# k% N4 G0 W
  5.      UINT idFrom;
    ' M0 r7 Q. ^+ G3 l
  6.      UINT code;
    . m  N  X. T( u$ K5 j
  7. } NMHDR;
复制代码

3 N. K' C4 h+ x3 j4 H; G  其中: 9 \0 ]; P. M1 {! ~' E: F
  hwndFrom 发送方控件的窗口句柄 9 X0 j. p$ K! w5 b
  idFrom 发送方控件的ID code 通知代码 * I% e; p- J) [3 D8 Q1 f
  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下: . ?* l7 }! J6 Z
  1. / \& T/ J& Y5 y
  2. typedef struct tagNMCUSTOMDRAWINFO7 z6 w5 b) D/ U
  3. {3 s+ ^5 J& [/ Y4 j$ ?8 Z
  4.     NMHDR  hdr;3 s; \0 g) R* D3 B5 F! ?% c
  5.     DWORD  dwDrawStage;0 n- q& M) E- ]3 p
  6.     HDC    hdc;6 M6 t) n8 R2 p: s% r
  7.     RECT   rc;, D) I6 z* ~$ y/ b! `
  8.     DWORD  dwItemSpec;
    5 ~+ k0 v( x' h( ~) d' w
  9.     UINT   uItemState;1 Y  ]1 R" r: g. z
  10.     LPARAM lItemlParam;
    $ u! y5 T2 ?9 Q. q$ T$ o
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码

8 @$ M9 v1 _: g   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:+ u2 M6 {* P7 r. A; n  m
类型值 含义 # e4 ]* Q: [: U$ k- m3 R8 g
CDDS_POSTERASE 擦除循环结束
/ X: D6 H' j: p# v( N: z' oCDDS_POSTPAINT 绘制循环结束
6 V- r1 F9 T6 l, X- ACDDS_PREERASE 准备开始擦除循环 - j% x* o# [! r- n3 _3 X
CDDS_PREPAINT 准备开始绘制循环 . ?' g# H& Z% o4 x4 s1 j9 P
CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效
3 |* j( N0 P3 {) A- \CDDS_ITEMPOSTERASE 列表项擦除结束 9 U. ^, a+ l- P0 i0 ?
CDDS_ITEMPOSTPAINT 列表项绘制结束 + G/ P+ T; T5 Q# C2 d
CDDS_ITEMPREERASE 准备开始列表项擦除
- Z/ x0 Q) {* k) O" aCDDS_ITEMPREPAINT 准备开始列表项绘制
7 n) X- r4 l: \1 D! l+ @CDDS_SUBITEM 指定列表子项' f& B$ ~/ Y& j& x8 ^6 r
表7 dwDrawStage的类型值与含义
: ~6 ]6 ~6 P% n) V) r  hdc指定了绘制操作所使用的设备环境。 % ]: ?6 A# B: O# H; ^
  rc指定了将被绘制的矩形区域。
: z' e' N: M, \; x& N( d5 B! J  dwItemSpec 列表项的索引 " x+ l; @" F3 d  L+ w
  uItemState 当前列表项的状态,其取值如表8所示:: Q5 Y  m4 k% ~7 O" {4 O
类型值 含义
! s0 v4 i- p( S. P3 j2 qCDIS_CHECKED 标记状态。 + K! _' M/ d) C+ |% Z
CDIS_DEFAULT 默认状态。
& O5 E+ [' R; F6 m8 GCDIS_DISABLED 禁止状态。 * r! E6 b, F7 ?1 n! {
CDIS_FOCUS 焦点状态。 ! U" U5 |1 c( _0 |) w& ?; W* A5 A
CDIS_GRAYED 灰化状态。
0 Z& |2 \  A9 H5 v2 s5 ?CDIS_SELECTED 选中状态。
( j" l8 H- r( J% @; h: F5 Z3 jCDIS_HOTLIGHT 热点状态。
$ ~( Y9 h% _0 t; y* _! u, Z  MCDIS_INDETERMINATE 不定状态。
' X, B. m. m. uCDIS_MARKED 标注状态。* D0 m* T+ A! G
表8 uItemState的类型值与含义7 {$ z9 {7 o  ~( I
  lItemlParam 当前列表项的绑定数据 . b5 b6 i* w. d
  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage:
( Z, y) r  y% j! W  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:
* E$ A' q0 J" k6 A类型值 含义
: u& h" H2 R+ B. T1 {CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。
% p( _" y2 w4 Y5 kCDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。 . u- i; @* p6 i, O9 I+ w! m+ {( ^* V
CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。
! c+ _4 @* g( B6 b9 h+ E# z' |CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。
8 A3 d& Z' F: E; f表9 pResult的类型值与含义(一)
0 E% l1 E  W& Q) _. s% u. q  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:
' I! G3 G0 C: {* i# C6 v1 |类型值 含义
2 M5 D* Z  Z1 a# @. }CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。
" M9 N/ t6 {: T0 M8 }% ]CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
/ e3 ^5 d- e6 X" t. R  ~CDRF_SKIPDEFAULT 系统不必再绘制该子项。
% d9 [5 B# G& r表10 pResult的类型值与含义(二)
8 @2 |  o0 J( m8 P) ^7 ~% U  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子:
3 G3 B5 k6 {2 {! G' z3 f+ b* {
  图12 利用NM_CUSTOMDRAW消息美化界面 ' Q3 L) z$ D1 y
  对应代码如下:
: g: `$ u. g+ V/ ~. j

  1. % i" {$ y% t2 C" o
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
    ! ~+ s" x! |' D' C6 n( o# N
  3. {
    / _0 K" a2 I4 n) B4 b+ |
  4.         //类型安全转换  j9 g9 S8 v7 S% V; p: [
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);# f1 k5 V* M7 |/ f' O, Q2 ?1 o# M
  6.         *pResult = 0;
    0 p) k6 p4 G: @, C
  7.         
    & G8 k7 C3 g% ?7 }9 t/ e
  8.         //指定列表项绘制前后发送消息1 |- Q1 K) j- T6 V
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)
    2 F( C  A9 W0 o  d% ~' T4 m$ x
  10.         {$ {- A( Y. _6 M; n8 e* b; h
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;  R; P% q  P/ Q, ?
  12.         }  i7 O& t  V) f8 ^
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)9 R* e) Y% p! [! Y5 g5 b6 s2 u! y1 F3 `% a
  14.         {
    % d( f2 L% p0 _5 N$ w
  15.                 //奇数行
    ( J- t0 z% S8 R; e5 v
  16.                 if(pLVCD->nmcd.dwItemSpec % 2)
    & r0 a- L3 _* q- \' u. N" k2 P
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);
      t7 r. V# n  C- p1 F
  18.                 //偶数行$ T9 p( N* @$ b! D& ?
  19.                 else
    & l, L$ y* @+ x" t, ?
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);- z$ w4 U! x8 J( |% _, v$ c
  21.                 //继续
    . D* w* H' ?% f$ [+ b7 _
  22.                 *pResult = CDRF_DODEFAULT;5 a$ g  D' D6 Y! Q
  23.         }  ?/ i4 x( j/ _* A- G
  24. }
复制代码
3 n% X8 R2 h" y
  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。 * b) x9 E( ~3 W4 J; k

/ \: M5 E/ o8 A0 i6 ^  3.4 使用MFC类的虚函数机制 ( N8 D; i" ~; D
  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子:
' @: @+ k5 p% b  y

  1. # m% `. V. l; i8 X
  2. void CView::OnPaint()! S+ d  Y% h# b. ]1 L, ]
  3. {
    9 S6 W" ]  ]* P& k# C
  4.         // standard paint routine
    9 G4 j  N' {$ O
  5.         CPaintDC dc(this);" s' s/ `' ]- U, Y! L2 `
  6.         OnPrepareDC(&dc);
    2 M( {* K/ R5 D7 a( W; S" T
  7.         OnDraw(&dc);
    : u3 T5 X) s4 H6 w" V2 j! y# y
  8. }
复制代码

+ F) L' q6 d$ N9 H) _7 m) t( u0 l  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。
+ U6 m7 w, W6 A* E, D  以下列出了与界面美化相关的虚函数,参数说明略去:
% `6 N% L& |+ |4 H2 B1 Z; a9 SCButton::DrawItem & U9 H6 a! X; }1 N3 z9 L
CCheckListBox::DrawItem + ?' e8 I& v' H
CComboBox::DrawItem   x- w. y1 e  S9 e# {, @
CHeaderCtrl::DrawItem 1 F! j' O3 x4 @2 f* T4 N" ~6 @. R6 M
CListBox::DrawItem
/ x. p( v4 z! U0 M9 l1 F  {  YCMenu::DrawItem ( {  }8 G+ I/ _& L
CStatusBar::DrawItem & f  N7 k! a; \. f4 P' C
CStatusBarCtrl::DrawItem
9 M8 D0 y4 [$ {( ^) cCTabCtrl::DrawItem
/ x8 r1 {& W+ }7 ]0 fvirtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct ); 4 E' b: j4 `/ u
Owner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。
/ _1 ~9 V/ O) [0 F  限于篇幅,在此不再附以例程。 2 M* f9 A  y* P9 f+ V, i7 C6 m

2 [$ h; E: c' G参考文献
/ ~* ~( u0 }* J/ D. k  L本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-8-14 01:06 , Processed in 0.026897 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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