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

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇
( a% g- L  U: A' S* F# ?6 w! G  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。
9 {6 l( U& v# r  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。4 P* I# }% I1 [6 {3 K1 Z

" m& I8 Q5 _5 ^' @$ |  2. 美化界面之基础篇
: e& x' }5 _' e. K. [! `# V) |+ g  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……6 H& o) L* T) g6 q
0 _) d8 ~( u& r3 h. x
  2.1 Windows下的绘图操作- L! u1 n" x& v6 Y
  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……
) n! `8 H, v( o  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。  i$ E- E- w' T6 r3 K5 a% ]% t, v
% n; e5 F( b6 [3 J. x
  2.1.1 设备环境类
5 c$ ]: @  b- n. h) [! ~! c: c  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。" i7 [7 d+ e: C8 y
  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:0 F; L0 n' Q6 I  t
  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式
& e) G5 L6 A! ]: |  Mapping Functions:映射操作5 O, ]. I6 K, l
  Coordinate Functions:坐标操作
# e5 }& J. F! ^2 S' Z& |- c" [9 N  Clipping Functions:剪切操作9 \- Z# r6 E; K0 c- A" \# @
  Line-Output Functions:画线操作: w7 M, s& @* }- }' {* H3 u6 y* ^; l
  Simple Drawing Functions:简单绘图操作,如:绘制矩形框
$ g% ~5 F# @0 a, K  Ellipse and Polygon Functions:椭圆/多边形操作6 D: n: L6 J" w  i+ Q% A# x% x3 t
  Text Functions:文字输出操作
; C. `. E. G. h2 J  Printer Escape Functions:打印操作# R$ f9 N2 t' a" Y
  Scrolling Functions:滚动操作$ l* N5 O4 k: W0 d( M4 G
  *Bitmap Functions:位图操作
0 @5 m9 G5 d# Y6 C3 @  *Region Functions:区域操作
' Z3 v9 V1 l. @+ \$ ^% q. @  *Font Functions:字体操作8 v1 e2 ?- r9 m* q/ w) `
  *Color and Color Palette Functions:颜色/调色板操作
* f- f" L+ t: u% ?  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。7 o) n& @3 t" P( H( o# [/ p
: T) t8 C4 X: J. Y6 ^* W
  2.1.2 图形对象类
2 @1 j( k& j. }4 j8 c  d  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。1 h7 b, i4 Z* i' X: \' [8 K
  下面的表格列出了MFC的图形对象类:
' E0 u2 g( M) T" x' s0 \+ t  MFC类 图形对象句柄 图形对象目的
( U4 a% L6 l/ X( f- M5 E# P  CBitmap HBITMAP 内存中的位图% J' s+ t1 j) Q  T0 d! P: U/ P
  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式
% A- A: x. C* H  CFont HFONT 字体特性—写文本时所使用的字体; h1 o7 a% z0 [0 {! }
  CPalette HPALETTE 调色板颜色$ M* a+ B/ ?7 D' Y- ?
  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细
, j( C* I2 \3 w) W( b  CRgn HRGN 区域特性—包括定义它的点0 f/ K& j0 A- i! A: B
  表1 图形对象类和它们封装的句柄
! d  |; ]8 H$ m4 D
: Q$ \" G6 s7 o9 ~- R, c  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:   u* D  Q( d2 @# E+ X
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:
' V% z5 I6 E- N0 I( T% z3 @
  1. " i' q# i+ A' h1 h! s2 N
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)
    ) [9 B5 H1 p; s) K% b" }
  3. {9 M, b  r  b( X2 ^0 W' h
  4.         //设置背景色3 Y! Q+ b  r4 K
  5.         //CBrush CUi1View::m_Back
    - E4 o9 A  ?, J# ~* T8 f. y7 f" F
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));; f; t/ r: g: c6 m
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL); ! ~9 P+ @8 `+ |, i
  8.        return CView::PreCreateWindow(cs);
    6 z' }: d/ W6 W6 M$ g1 w+ Q+ T
  9. }% T% W' a+ l2 O, Y( K4 _1 X% O
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)4 B" w: [1 [0 v' o0 p& k/ Q
  11. {. i  `3 l: g3 C% f7 Q
  12.         if (CView::OnCreate(lpCreateStruct) == -1)4 O+ z+ C% h1 g
  13.                 return -1;
    / `7 H" \. h+ p
  14.         //创建字体; I8 M, f4 h1 V3 @! E- B' Y+ T
  15.         //CFont CUi1View::m_Font1 i( _5 h, z  W0 Q, h5 E( V
  16.         m_Font.CreatePointFont(120, "Impact"); 3 W, M6 K+ j# q, E8 ]- `: x
  17.                return 0;0 ]7 \, @$ h3 |3 `: I; F' w6 v( R
  18. }/ d  @! o! @7 B
  19. void CUi1View::OnDraw(CDC* pDC)
    . N2 H6 z  p% T9 G
  20. {$ Z& O1 B  A: @" s/ o, N6 p$ g  E
  21.         //绘制按钮框架* P: Z3 x, |5 a' j! k$ C+ _) B) I
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);1 v, \* i1 }$ ?# U1 a
  23.         //输出文字
    7 G! F1 ]1 |( t( H5 J- C7 Q
  24.         pDC->SetBkMode(TRANSPARENT);  n( T5 @2 c' e; ?& `
  25.         pDC->TextOut(120, 120, "Hello, CFan!");
    & l' ~7 {# O: ]# s
  26. }
复制代码

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

3 x( R1 h' k8 B$ C1 W  3.2 使用MFC类的既有函数1 e  [5 l4 ^) o2 u8 Y& s
  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。) V$ x, U  i6 v2 i& h
" o0 H( Q! A% J+ h  g
CWinApp::SetDialogBkColor' g! @% B* D% ]; i; d
void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );  i: X' m6 l$ r! t
指定对话框的背景色和文本颜色。
6 l7 e! k5 q, z" B4 Y" X
, X' \( H5 f1 N$ mCListCtrl::SetBkColor! l, C7 h( t  V0 z0 V+ H, ?/ f( z% ?
CReBarCtrl::SetBkColor: A: b3 K  J7 l7 r% J. A9 E
CStatusBarCtrl::SetBkColor+ v( G% l# p* @. w8 g: C- u
CTreeCtrl::SetBkColor
. Q9 |/ l! ]4 E, a% q- O: OCOLORREF SetBkColor( COLORREF clr );
( Y4 X) R4 S) T' Y* o设定背景色。
1 \5 e5 C7 Y  h! Q/ P1 K
0 N5 ^! M2 M$ B. @/ x( L  [CListCtrl::SetTextColor) h* i# O) C& ~
CReBarCtrl::SetTextColor
7 [6 ~2 A$ _9 x0 V) U; HCTreeCtrl::SetTextColor* u* o5 w* }9 ^' `/ C! {
COLORREF SetTextColor( COLORREF clr );
6 i% b. h4 P( t2 _) V设定文本颜色。( Q5 Y* V2 s: ^5 s7 z

; s. z; o. J+ @( z/ ?CListCtrl::SetBkImage8 {  ^* M, N8 @: G  K
BOOL SetBkImage( LVBKIMAGE* plvbkImage );
  a! I( ]- L* V; U7 @  gBOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);
" e; @2 f9 s, X% V1 pBOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );
) Z3 j8 j# e* r% U3 |% Q5 F7 ^8 f: u设定列表控件的背景图片。' m7 Z6 E6 Y0 j7 D3 b/ S# {

1 |7 Q7 Y# v5 \3 ~/ S" V# D- iCComboBoxEx::SetExtendedStyle( h" g1 G) s; [; J7 z1 @+ c
CListCtrl::SetExtendedStyle1 z( k) d3 a$ t' H6 X  d; v
CTabCtrl::SetExtendedStyle
0 ^1 r( B' L# y- [3 L" D7 dCToolBarCtrl::SetExtendedStyle 4 R7 H( ~( Z6 Z. Q
DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles ); 9 V8 [( _: j) d; ]& ]
设置控件的扩展属性,例如:设置列表控件属性带有表格线。
! m/ S, m" y' R6 [) U( _  图4是个简单应用MFC类的既有函数来改善Windows界面的例子:
7 x5 p& z# f, O
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:/ }$ q+ N3 o' R- w
  1. . g  H  N/ p% M$ C9 G
  2. BOOL CUi2App::InitInstance()
    + w; P: ?7 a8 t: y5 V
  3. {/ x) |9 U, _% L4 T% o! }
  4.         //…
    % {9 m+ ?8 }: y1 W
  5.         //设置对话框背景色和字体颜色$ ]5 n$ J. z  P% e
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));3 E; d3 A% W( |' d  b6 w
  7.          //…
    5 g4 b7 v' Y" l
  8. }
    4 n% n' j) ]' O; k+ m# U, Y2 V3 J
  9. BOOL CUi2Dlg::OnInitDialog(). H0 \% k7 E, Q9 ~
  10. {) s5 {) z! B& `
  11.         //…
    , u2 G8 M7 V% y4 h% I( F
  12.         //设置列表控件属性带有表格线6 c# b6 b0 {: @
  13.         DWORD NewStyle = m_List.GetExtendedStyle();( b3 R8 r% J5 j0 t5 ^+ f
  14.     NewStyle |= LVS_EX_GRIDLINES;# m# \: A, a  Q( ?% e
  15. m_List.SetExtendedStyle(NewStyle);) v8 p4 _- ^  M5 t+ E
  16.         //设置列表控件字体颜色为红色
    + |( C9 I, T% ^) [4 C; l; t& k
  17.         m_List.SetTextColor(RGB(255, 0, 0));
    ; }9 z- c, a4 h5 t4 ^
  18.         //填充数据
    " R- `% O' x) X% l1 u
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);8 k0 T# {% G7 }$ l
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);
    6 `6 T* L, V( l
  21.         m_List.InsertItem(0, "5854165");3 T/ t7 c% u# E1 ^7 L
  22.         m_List.SetItemText(0, 1, "白乔");$ e: F: ]9 |% F7 C
  23.         m_List.InsertItem(1, "6823864");) k1 O* ]8 t$ W5 d$ b
  24.         m_List.SetItemText(1, 1, "Satan");
    * U4 t  P. A# y$ j" ^$ @3 O4 V
  25.         //…
    1 t4 p) u- a7 e& {
  26. }
复制代码

' n0 b5 y; A- l2 R8 ?  嗯,这样的界面还算不错吧? 2 q, c+ w3 A! A( I$ m" [; c( N

4 @! y% m' p" {$ }' C2 H  3.3 使用Windows的消息机制
2 c! u) z8 Z4 w/ O& ^) H& C: U  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息: 4 n6 D/ R! ~8 ?2 h; l$ B
WM_PAINT / \' p' c' d! M) U3 K
WM_ERASEBKGND 2 c. m2 U, D5 S% Z
WM_CTLCOLOR*
2 {& ]/ N2 ]3 r5 V, Q) ]9 B. eWM_DRAWITEM*
. L* ?! k! l: q+ OWM_MEASUREITEM* 9 H1 J1 g1 c* X' M
NM_CUSTOMDRAW*
6 I6 B. `3 y% B' _9 D  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。
5 f% B- I( y* u5 e- |  k* c/ ^9 x: N! |' {/ C% x$ E* m
  3.3.1 WM_PAINT" D3 q  f  w  ]
  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。
2 h7 O% K+ W% J, I3 |) y  A' Y  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下: ) h: g1 }6 H: X9 S# i
  afx_msg void OnPaint(); & |/ Z% P' b7 O1 @. ~7 t4 X2 Z0 `3 G
  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示:
+ v6 x, R/ t/ x$ Y
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单: 4 m& E. M3 K/ P7 O8 w
  1. 2 e0 z6 M) l9 h  w0 ~8 y
  2. void CLazyStatic::OnPaint() 4 u+ O1 {8 `2 |; ^7 W% x1 O% V
  3. {$ O" [( _$ }- B( n  a  y
  4.         CPaintDC dc(this); // device context for painting4 E7 c  U9 r1 A5 Q) u
  5.          7 G8 v  E+ o0 ]+ y% Q
  6.        //什么都不输出,仅仅画一个矩形框; A3 {" @+ [3 E5 `4 O( i# _
  7.         CRect rc;
    ) C; _0 a& h! r1 h8 M
  8.         GetClientRect(&rc);
    9 o4 \+ V( K2 |8 U
  9.         dc.Rectangle(rc);
      G  ^% l2 P# {: u% k
  10. }
复制代码

/ O" A* o; B$ s  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。
/ [* p5 R2 n& {1 a/ A+ Q1 G9 J
, U" r/ |' Y5 t) d0 S: E4 w  3.3.2 WM_ERASEBKGND 7 [5 k7 \3 U& r& j7 B% F0 E
  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。 9 K1 c7 d( P" [: {4 r- o
  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:
5 ~! `% |2 c- j$ y9 Y! _  afx_msg BOOL OnEraseBkgnd( CDC* pDC ); " z" n- n5 U7 [. P1 I
  返回值: 1 F6 p# A2 \3 T% k5 c
  指定背景是否已清除,如果为FALSE,系统将自动清除
2 z- q# Y  I% o; B/ N  参数: pDC指定了绘制操作所使用的设备环境。 " W. Z; r# n* Z/ R% c
  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景: 2 O6 x7 a4 _0 q
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单: " }1 I* S3 k4 V$ _  H* L

  1. ( X3 A, f. v1 ^0 }  y9 C/ R
  2. BOOL CUi4Dlg::OnInitDialog()
    & H8 S% c3 f7 X# Z& X3 \) ^
  3. {
      N: a. L) A0 a1 |: E4 V
  4. //…" J: `  ~1 D9 \; j6 S" a
  5.         //加载位图& |4 n5 f9 K3 \* K# W
  6.         //CBitmap m_Back;6 A0 q2 [* X+ G2 E" Z
  7.         m_Back.LoadBitmap(IDB_BACK);& y5 G* d. q  r
  8.         //…
    : ~! J$ v7 B! V2 |& z: L6 T
  9. }$ T, G; h8 h2 [3 p- S4 h2 p
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC) 7 d0 d- J- H" M* J' y
  11. {
      M8 B, f9 |) C: f+ B( ]- a
  12.         CDC dc;
    9 F* |0 {0 w; M$ m
  13.         dc.CreateCompatibleDC(pDC);
    6 T5 n; E( d, `& s+ e- c
  14.         dc.SelectObject(&m_Back);
    3 M+ ^+ T  H( c; S  T6 @
  15.         //获取BITMAP对象8 w. Z8 E/ k4 z1 }, H" g/ ?1 h! u5 Z
  16.         BITMAP hb;
    * p+ ]' _9 S8 m; r4 i; d7 [
  17.         m_Back.GetBitmap(&hb);
    7 c& v$ ?& H6 t* g- F3 `( g! _
  18.         //获取窗口大小/ ?1 p8 a: x8 M: _; S9 e$ d' V
  19.         CRect rt;4 U: h$ G. |4 _* F9 d0 ^& X
  20.         GetClientRect(&rt);
    ' G- _' l, j* _' P  H+ u2 G9 z  v% R+ M
  21.         //显示位图6 ^* L5 u$ C' y8 l5 i
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),
    9 f& b! }! [" L
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);( Q* j; H, X. P' m9 L) A, J# P
  24.         return TRUE;6 b  q; R& j- M  }8 y7 g) O
  25. }
    & _) @6 g8 m: t9 S+ Z
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) ! G# @; s$ I$ Y
  27. {, a  G/ g7 p5 e  L3 y
  28.         //设置透明背景模式
    ( e* J4 T0 z5 C3 L8 g" @
  29.         pDC->SetBkMode(TRANSPARENT);
    - ]3 F& E. `7 ?; @* W
  30.         //设置背景刷子为空5 J% k- E  k1 K( j  P5 {" `# C4 _1 C
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);8 H: S7 w. m& C+ N+ {" x" V
  32. }
复制代码

; X# R7 I: n! a/ S! L  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。
( c4 L3 K. x: B9 Q: j9 O; O/ k
5 n3 G, T5 Z5 G9 l) J( h5 ~3 ~  3.3.3 WM_CTLCOLOR
: k# }  v1 u/ S( K( @  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。 % U5 x* V2 w  X8 D
  WM_CTLCOLOR的映射函数原型如下: 2 X4 E& U9 i- ^. T/ b. q
  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );  I# z/ O4 s5 h$ L
  返回值: 用以指定背景的刷子 . `' S9 r) t8 t
  参数:
- _! O: z& F# N9 }8 M  pDC指定了绘制操作所使用的设备环境。 ' ]+ M: {$ e6 N, V
  pWnd 控件指针
- Z/ s/ d" L+ D  g1 `  nCtlColor 指定控件类型,其取值如表2所示:. v5 L( w) L; _  D! j) _
  类型值 含义
7 R2 ?! w' d4 q. e6 v& _6 }CTLCOLOR_BTN 按钮控件 " l. e4 @- F/ O8 v" u  V) E
CTLCOLOR_DLG 对话框
+ y! T* d5 y  Q; `& b3 g8 p2 UCTLCOLOR_EDIT  编辑控件
3 B* N! E) i" v; S% \8 |2 }6 s4 RCTLCOLOR_LISTBOX  列表框
. w' v0 [2 k- P: E9 ~& ~' T4 Y  QCTLCOLOR_MSGBOX  消息框
1 A" K* h4 }0 h, D; G; d  yCTLCOLOR_SCROLLBAR 滚动条 5 i6 }1 k; n' K; o# G
CTLCOLOR_STATIC 静态控件 0 X! x5 N1 f5 C2 ?6 @
表2 nCtlColor的类型值与含义+ A5 M0 X& l5 k; p
6 b' n0 L, w$ q5 }
  作为一个简单的例子,观察以下的代码: $ e' ?' K1 J1 B

  1. ! B8 ^9 _6 X/ u5 Y$ l9 o5 t0 @. G' }
  2. BOOL CUi5Dlg::OnInitDialog()
    6 v! |0 J1 O1 v
  3. {8 S3 r2 N( \! N1 A; x2 |
  4.         //…; R7 O; F( I" v( X1 T% y5 @: c
  5.         //创建字体; P- j7 {6 v" l/ }" ~4 c% J' d
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font25 r* z2 l7 X, s3 ^5 @. E2 w
  7.         m_Font1.CreatePointFont(120, "Impact");) f* O$ R) E9 P
  8.         m_Font3.CreatePointFont(120, "Arial");
    / Z  R* g) z1 G6 m% L
  9.                 return TRUE;
    5 k! r# S% W5 ^# |9 o: c
  10.   // return TRUE  unless you set the focus to a control
    ) D( T9 u7 e/ ^
  11. }, f. q! s; X# G
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 2 R, b) [* M/ ?0 P6 y4 y9 K
  13. {
    - m* a+ Y( y. p3 K6 S' S  U
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
    4 C* `" g; {1 M# T' R4 S2 M/ q
  15.         if(nCtlColor == CTLCOLOR_STATIC); S, T0 O$ M, c. V
  16.         {! ?' [# S; b0 \
  17.                 //区分静态控件. z& `$ N8 }6 L- R/ I! W1 X8 v
  18.                 switch(pWnd->GetDlgCtrlID())
    , N" {8 e  C. ^8 N
  19.                 {
    0 q! O' u2 z3 p3 O5 W1 W
  20.                         case IDC_STATIC1:0 ~# R- T% ^7 t
  21.                         {8 t, L; a9 w; h& F' G; Z3 G
  22.                                 pDC->SelectObject(&m_Font1);1 W  f# M9 r) N: |  }! I
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));
    3 W9 V; p. r1 l! [/ Q
  24.                                 break;1 D$ z0 ]9 F( l( ~
  25.                         }. N: T% M. m4 k2 s+ }7 I
  26.                         case IDC_STATIC2:
    & L: X1 l3 k: \# l2 Y0 K
  27.                         {) V. t$ K0 L# L5 }
  28.                                 pDC->SelectObject(&m_Font2);7 i7 _! @# ?; N1 `) Y9 }! G6 M3 j
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));
    : g- _9 r3 W5 t/ |) o, ]2 t6 Z
  30.                                 break;7 R6 Z3 J( P9 Y6 ~
  31.                         }
    : N. T# B, s- I! |" A
  32.                 }
    : d, g2 C4 x& f7 `: ^& L" v- a# s3 P
  33.         }2 x/ g7 r( I# o+ ?5 J9 ?9 t' Q
  34.         return hbr;
    " d6 M3 Y3 h. q/ }$ _
  35. }
复制代码
" P/ R" b7 L0 b  Z3 o6 g/ q
   生成的界面如下: ) @4 c# l. T. M: c6 w
图7 利用WM_CTLCOLOR消息美化界面
1 T+ a4 m$ h3 }7 e, |
  3.3.4 WM_DRAWITEM 8 K' M: J* k  @7 I( r/ s; C' @
  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。$ P3 Y$ U0 p- Z
  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。
/ u) K. i1 J, t4 \; e( k  WM_DRAWITEM的映射函数原型如下:
: w2 T& A: A) ?" ]  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );# P6 {# ~; b% [0 X2 R9 R7 S
  参数: . j5 `! q. x+ q
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 7 m0 j2 s* ]# _( a) Q+ J
  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下: 6 O; D6 n- R2 y/ R7 i: c9 J/ B
  1. 5 Q- }1 |# i1 E2 O! B3 O
  2. typedef struct tagDRAWITEMSTRUCT) ~" r! G4 I" Z
  3. {
    - [# C7 D! o3 f/ [1 Z0 @7 e8 j
  4.     UINT   CtlType;
    3 l8 U3 m7 k) y. T% `" F- g4 p
  5.      UINT   CtlID;1 o+ s! Q3 ?) Y, o$ a
  6.      UINT   itemID;# K% |1 B  r" f: f1 y) N
  7.     UINT   itemAction;( Y; R  t4 v! k/ ~4 ?" w# n2 W
  8.     UINT   itemState;/ K! l, o) h; B9 z9 r. t
  9.     HWND   hwndItem;
    + V: P+ z' ~" @8 l+ [% Y
  10.     HDC    hDC;8 [5 }6 g' c: x
  11.     RECT   rcItem;
    , w' B  W! c. h' k; q: S  d% ?5 |
  12.     DWORD  itemData;
    . ?/ |: w9 D' R4 p0 e, A1 s
  13. }DRAWITEMSTRUCT;
复制代码
0 J4 f7 z# g0 G4 Y8 C
CtlType指定了控件的类型,其取值如表3所示:
$ C) D4 i2 [9 H/ ~' N3 L类型值 含义 ( Z% _8 S  l3 X  ?9 e
ODT_BUTTON 按钮控件 % e1 f7 p  B. g" A
ODT_COMBOBOX 组合框控件
, i. T' q# f  GODT_LISTBOX 列表框控件 5 g0 d( B9 ^) `" u( a
ODT_LISTVIEW 列表视图 . M3 J( e2 A4 c1 N. Q( ?' @
ODT_MENU 菜单项 ( I: r! n  D; \* W7 i" v
ODT_STATIC 静态文本控件 . F; `) h2 z" b9 \) t' b
ODT_TAB Tab控件 ( i, L* c. }9 X/ h4 f, q
表3 CtlType的类型值与含义
6 a1 Z& {/ @0 {8 n! a: _: X3 |2 ?+ I* f( q, ?4 y
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
- T( A0 e* U  `( Y. V  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。
% L( }/ O6 D$ m* M" a% p+ `  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:
- O4 R, O5 X0 w% W/ Y+ O类型值 含义 . O( J  Y- U5 D( V. ~
ODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。 1 o! D! e4 P8 @4 t* D2 q( V
ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。
: B5 U9 J1 A- u0 r1 M* p  OODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。
8 w2 ~! z7 _3 v/ A( M! q表4 itemAction的类型值与含义
- \  c' z% b6 @) T  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:& b' }0 N& Z# A  ]# R4 Z
类型值 含义 # h% R: u% ~: S# k! ]
ODS_CHECKED 标记状态,仅适用于菜单项。 2 P+ d( l  ^7 R+ q
ODS_DEFAULT 默认状态。 5 Q: `" W+ S5 [' \  J& x
ODS_DISABLED 禁止状态。
" Y/ T( ^8 n% I' MODS_FOCUS 焦点状态。 + \: Y9 W& |3 Z
ODS_GRAYED 灰化状态,仅适用于菜单项。 % m' D: t/ X& z1 z7 Q0 i9 j. B- p9 t
ODS_SELECTED 选中状态。
& f( W3 b) a& x6 e7 CODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。 ' R) R$ S- S. x- T1 P) F; w
ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 * l* W0 x; C/ [; A+ V- B: {
ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。 4 \' O) w6 W5 X% w8 C+ `9 P
ODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。
7 |' e) Z4 q/ K! \ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。
; z# A2 s) ?2 P8 i* B表5 itemState的类型值与含义% l* p0 V6 _  k& i/ E- U5 C3 k+ y
  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。 , M% T1 Y  w/ c& [
  hDC 指定了绘制操作所使用的设备环境。
; V* E" {$ I! @5 a3 {7 d  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 9 G( z& j" P- n
  itemData 2 x7 A% K. k) w
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
% \* L" K3 X% e  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 " v) P( U4 @- W
  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。 . V( t; P1 a# L# b; `+ t- w
  图5是个相应的例子,它修改了按钮的界面:
  X9 s7 i" m% k( _- Z( ]3 n
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下: + d1 u; w6 A, Z/ x( U6 J5 k

  1. / o  L# m- X: E4 \# i
  2. BOOL CUi6Dlg::OnInitDialog()4 s& |6 I# |' B  K" U5 }) b
  3. {
    4 U$ V. e, O# }% F& K& J
  4.         //…8 i1 n3 z; [  X
  5.         //创建字体2 a2 f9 Z0 _6 @. |7 f$ G
  6.         //CFont CUi1View::m_Font
    / d! F8 w/ I2 H9 J5 l9 j9 z2 \
  7.         m_Font.CreatePointFont(120, "Impact");6 l& N9 d1 p  [6 N, ?0 I
  8.         //…
    ; ^4 d+ W( `/ f; B' X6 \
  9. }9 f1 ~. \- d) p) ^% M6 S9 q
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) + E2 i& ]" m5 o1 [6 D5 J# c
  11. {
    ; q4 t2 ^% y& @2 \
  12.         if(nIDCtl == IDC_HELLO_CFAN)
    : J) q) z" L8 l( |  P! M
  13.         {
    % Y& S$ V# y; I9 {7 }; q- |
  14.                 //绘制按钮框架
    0 l' O/ r+ I2 w5 x% |/ c5 Q
  15.                 UINT uStyle = DFCS_BUTTONPUSH;. k+ S( {3 Q, l3 T$ _+ z8 O
  16.                 //是否按下去了?
    ) P0 j1 D4 l  U0 b
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)
    : v2 |  @/ O5 M# G+ C
  18.                         uStyle |= DFCS_PUSHED;7 j" O+ T1 l& @5 R
  19.                 CDC dc;
    # ?% T+ }* I0 g# v
  20.                 dc.Attach(lpDrawItemStruct->hDC);1 \9 T7 `/ r9 L1 t" ^$ N- a
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);
    9 L; N- ~/ }  e3 t
  22.                 //输出文字
    ! B9 e, m1 o7 I" c+ F
  23.                 dc.SelectObject(&m_Font);8 g3 O4 w4 O1 r7 M9 g8 i  T
  24.                 dc.SetTextColor(RGB(0, 0, 255));/ {7 q, e  i1 y% P/ \
  25.                 dc.SetBkMode(TRANSPARENT);7 P* F$ u9 \$ y. t; R
  26.                 CString sText;
    8 @/ v, N' g) Y9 U0 R
  27.                 m_HelloCFan.GetWindowText(sText);
    / Q/ Z. [( P6 F0 g/ u! N" y* F
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);2 X3 p# l" E$ U
  29.                 //是否得到焦点
    8 [$ j8 I% S1 Y5 A) p
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)- ]' b4 V" t+ a# D# h+ A2 s
  31.                 {6 n* K' g( |7 p" E7 ]7 u
  32.                         //画虚框
    ' s/ r9 y5 a- J6 w, [7 }
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem; " W+ L0 K) i, Y8 _
  34.                        rtFocus.DeflateRect(3, 3);, e9 d, ^2 P1 m- a4 V: t& K' L0 }' b
  35.                         dc.DrawFocusRect(&rtFocus);% A1 z( K4 o' f% Q6 p1 _
  36.                 }7 v' w2 i- _0 ~: y- N- E6 T
  37.                 return;
    6 z6 N/ R4 `3 f0 @2 b
  38.         }
    ) b5 ?. {9 T$ [0 h& R% u3 L
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
    9 ~9 f9 s3 n* L- K3 `
  40. }
复制代码

" x9 B$ u' ^- ^9 A9 Y  别忘了标记Owner draw属性: . U2 K: H7 b/ L% _6 _
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。
, G+ p  A5 N' c  h  L* a" H: M6 D* y( i0 d& k, O4 B" X
  3.3.5 WM_MEASUREITEM
. z3 V' J% w5 Z  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。 & e! I+ q( G9 b) V4 u3 m, S
  WM_DRAWITEM的映射函数原型如下:
+ ^. [0 ^+ y9 j6 i9 W  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
5 ^3 c' Z5 A! A& N0 _: B  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l
! o3 I3 @7 V# o% m- v  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下:
. c9 [% M2 t( `6 N' R
  1. 4 _- }, R- L/ S- Q3 K( f
  2. typedef struct tagMEASUREITEMSTRUCT+ _3 j2 B6 [$ R( p+ A$ ]
  3. {2 Z% |0 l4 r4 ]9 z7 r
  4.     UINT   CtlType;
    8 M7 i6 H. ?  F( v8 t* g$ V
  5.     UINT   CtlID;7 _5 T, C/ f3 ?: W; V6 U
  6.     UINT   itemID;5 U' N, ^9 [% D6 w) P2 V7 J" H4 ?
  7.     UINT   itemWidth;0 f' C' D/ c4 C9 t/ ~6 N2 G4 C
  8.     UINT   itemHeight;1 f$ k6 m& P- X# j
  9.     DWORD  itemData;
    ) w; r: S, H8 \6 ^
  10. } MEASUREITEMSTRUCT;
复制代码
# C$ S+ d- P5 n& w  s8 ^  C
  CtlType指定了控件的类型,其取值如表6所示:
  I  e  G" ^; J5 N% w9 N4 }类型值 含义
+ B" G8 t( u! MODT_COMBOBOX 组合框控件 $ D  a7 ]  o& @
ODT_LISTBOX 列表框控件
$ q2 \& x5 r) J$ G8 @* y8 ZODT_MENU 菜单项
: e8 r; t. `! p8 Y: E. o表6 CtlType的类型值与含义
2 \: O8 M+ A- _9 P/ X. K  CtlID 指定自绘控件的ID值,该成员不适用于菜单项 - I: b2 Y2 x7 J7 @( R, `0 P8 J9 K
  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。 3 E# w% M. D8 n. b/ d
  itemWidth 指定菜单项的宽度 7 [3 R- o7 M3 m3 X& \# c
  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255
5 D4 A; E( q& N( P4 e  itemData 9 h  b+ H# F& F
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 5 S# H# D3 e7 ~2 [
  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
1 I- t1 H$ a, ~, l. ]8 @; e  图示出了OnMeasureItem的效果:
, p% @. _; F. r! A" q
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下:
. a0 Y. z5 b9 D5 T$ H# F* e

  1. 1 Y( Q$ M+ g; \$ L- `
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) ; W$ t4 W7 s! t5 c8 o* a
  3. {7 F/ A- C+ j; J/ E
  4.         if(nIDCtl == IDC_COLOR_PICKER)' \9 D' _0 a9 \, D6 G
  5.         {' X! l: x3 s  n$ z$ X
  6.                 //设定高度为30
    5 W% G0 q1 t/ l5 Y
  7.                 lpMeasureItemStruct->itemHeight = 30;
    . ?& ^! h; W! s
  8.                 return;
    % y3 c) \/ m) S. m2 T- C
  9.         }0 c/ a6 J$ N5 L6 D$ ]% @8 E
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
    5 m' F7 }  ~, V* B
  11. }
复制代码
! I( C: b1 N: K; s
  同样别忘了指定列表框的Owner draw属性:
! H' i' d3 U" a. R0 e4 q  d. I
  图11 指定下拉框的Owner draw属性  
4 P% }" X& {6 s% `
: r4 d5 o% e0 V  {  3.3.6 NM_CUSTOMDRAW ' E% p' a  T. U6 T1 H: h
  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。   z1 W5 H" j/ P3 S6 F
  可以反射NM_CUSTOMDRAW消息,如:
5 \5 d# j. j" f2 @3 B8 X; b3 _) C1 x* \  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
+ }; D$ j' L! ~9 Q) \  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); ! Z% y1 V* Q5 I6 a4 x
  参数:
1 z5 L2 J3 U( L4 q7 O  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:
. u, ?( J% }9 H4 g9 I$ \/ v* v

  1. ! O+ N0 [5 L7 O0 [9 c. _* Y- [
  2. typedef struct tagNMHDR7 q4 @) S; ~* a, O
  3. {
    / c+ D! ^  H! Z% N0 o+ j, u
  4.      HWND hwndFrom;  l; |3 ^% _' r0 H8 ?
  5.      UINT idFrom;
    ' I8 r3 d1 l5 ~1 D0 a* I4 O
  6.      UINT code;
    7 O" l5 B% K% w2 w0 ~
  7. } NMHDR;
复制代码

( G/ {5 ?0 a+ k0 Q' p7 y  其中: - W+ [# K; `8 [6 a3 W. ?$ E5 S
  hwndFrom 发送方控件的窗口句柄
5 S# d2 X1 _4 B6 m  idFrom 发送方控件的ID code 通知代码 ' v/ z, \" ?6 [( Q  Q# r
  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下: $ s: T6 o* m, A6 S

  1. ! f" s1 R8 P# L6 P
  2. typedef struct tagNMCUSTOMDRAWINFO
    / [( y# C0 C' ^0 P  W
  3. {8 V- i  B7 c% |1 c
  4.     NMHDR  hdr;& q/ {# i& U" I( j' v" i  p% U
  5.     DWORD  dwDrawStage;
    3 J/ Z" j- Z7 r5 ^. V7 Z' G0 Y
  6.     HDC    hdc;
    8 u4 B3 k! O0 d7 q6 ?
  7.     RECT   rc;' |" \+ z" X8 g- r% D8 Y* p
  8.     DWORD  dwItemSpec;, }) r6 F+ q8 }# m" E; E: y0 b2 h" @
  9.     UINT   uItemState;
    ; y, C' R- }$ P
  10.     LPARAM lItemlParam;& T  X$ X! W; p( P' w5 [
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码
1 R! F0 T2 [- y6 W' e& q
   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:
- c* w" p: h* K7 s8 X7 L类型值 含义 " W% h4 J+ N; y2 f# l; I
CDDS_POSTERASE 擦除循环结束
; `( ?6 W: M" i/ l: J: _: TCDDS_POSTPAINT 绘制循环结束 , N! b& _( Y9 X; u6 R, W
CDDS_PREERASE 准备开始擦除循环 ( \, u2 I% X4 b+ h
CDDS_PREPAINT 准备开始绘制循环 6 B; Z8 ]8 i; W0 q
CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 ' W4 P. R# i0 k' o( c+ ^
CDDS_ITEMPOSTERASE 列表项擦除结束 $ ~! l- ]- O% y: z
CDDS_ITEMPOSTPAINT 列表项绘制结束
2 D2 P! x5 x! T* g4 aCDDS_ITEMPREERASE 准备开始列表项擦除 2 I" ^6 Q0 H  {! C  }7 m: H$ C
CDDS_ITEMPREPAINT 准备开始列表项绘制
( {. `& }. M4 M: _" g( [# _  S# Z3 h  D4 fCDDS_SUBITEM 指定列表子项5 u% I6 G* k% L% M3 E
表7 dwDrawStage的类型值与含义
2 l" k8 C7 }  I$ E5 T( T. ?  x  hdc指定了绘制操作所使用的设备环境。 , A" [, q/ ]% q6 ^8 @
  rc指定了将被绘制的矩形区域。 0 r3 J  z( O! b1 ]  T
  dwItemSpec 列表项的索引
) D' \% }) k7 q3 \0 O- b  uItemState 当前列表项的状态,其取值如表8所示:
# x2 G" B" f" j9 M* x类型值 含义 ' m: {  X$ ^0 j2 l0 E. S$ Q
CDIS_CHECKED 标记状态。 # ]! @* N: i! P% v8 l6 M/ F
CDIS_DEFAULT 默认状态。
4 ]# m- |" e0 A. x2 M6 l6 T3 tCDIS_DISABLED 禁止状态。
: @7 ~) \) B$ q0 BCDIS_FOCUS 焦点状态。
" l1 v- f$ p$ L3 `3 rCDIS_GRAYED 灰化状态。
+ Y: I; v' b4 [: e; F3 M: cCDIS_SELECTED 选中状态。 4 V! L7 X' d& V: f$ L+ i& T- S
CDIS_HOTLIGHT 热点状态。
) D& y4 ?/ p/ b" RCDIS_INDETERMINATE 不定状态。
1 l& m/ V* I# e+ u* f% l) {7 VCDIS_MARKED 标注状态。
* V$ `! l- d2 n* Q8 u& I表8 uItemState的类型值与含义
. B8 e  {" c! q! }/ x  lItemlParam 当前列表项的绑定数据
; z. K, J1 O+ a% K$ a. ?1 L  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: 0 I9 L. u; F( c1 `
  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:4 k3 j5 H  g. t/ ~
类型值 含义 ( J! M( T$ M3 t9 k0 i
CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。
; ?6 @: p( Y  Q2 W  LCDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。 - n* S+ X+ N1 n) [) d* L
CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。
. ^2 y" R* H" l; a2 cCDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。" T$ N: B: n* v+ u( P
表9 pResult的类型值与含义(一)
& B0 ]+ p9 H+ m7 Q, i' K  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:
( _8 v$ L0 v) q4 P类型值 含义
- B/ e/ N! m& f' K6 {$ s$ NCDRF_NEWFONT 指定后续操作采用应用中指定的新字体。
8 m, ]4 ?) J# }; xCDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
6 }  U( L, m' K: g- Z$ ZCDRF_SKIPDEFAULT 系统不必再绘制该子项。: C0 c6 t" j+ z% G" `
表10 pResult的类型值与含义(二), E7 E/ R5 \9 [; d: Y
  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子: 5 s0 S+ E( W( l3 S2 [
  图12 利用NM_CUSTOMDRAW消息美化界面
+ m% {3 }1 k/ f/ R2 x$ k- x& j  对应代码如下:
# P/ X8 v! ?' |
  1. , x# D( e* q0 g2 N; z+ I
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)) n+ s5 t3 z! w/ R1 E( j& E
  3. {
    5 Y# ]) d, ]: G; x7 O
  4.         //类型安全转换# M- F2 @. |1 F; m! V
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);
    ) G# x! _! |, U
  6.         *pResult = 0;
    / L' g8 E& T! O
  7.         6 a' f+ Z4 l+ [1 }6 R6 f9 B. b% i7 M
  8.         //指定列表项绘制前后发送消息
    . i# C" H" f8 m9 N) c% `- P- }
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)( w3 H( B0 W( R1 h
  10.         {
    * l# j" L/ `1 c6 g
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;
    5 ]9 j' K: R" w9 k$ y5 E  d
  12.         }% Z- L1 r( i6 }! w
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)
    # w; U" K2 L7 Z$ q4 ^8 P
  14.         {2 d- g1 g5 P) S2 ]- Q0 t
  15.                 //奇数行  s/ o8 l% k) d7 t
  16.                 if(pLVCD->nmcd.dwItemSpec % 2), K1 w6 j; C6 d+ x
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);$ {: F  A/ U) w5 J( I" g
  18.                 //偶数行
    5 |! ^- [' O: Y* y/ T, _  A9 H
  19.                 else
    ! ~( X$ t0 u9 V$ E% Z9 O5 o8 Y
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);
    ; b2 G. ]: i" Q5 N2 n! E
  21.                 //继续
    3 h: l% S) a. v, T" @1 x8 G
  22.                 *pResult = CDRF_DODEFAULT;
    : y/ G# i: b. ?+ g
  23.         }0 ?+ t  b2 X0 X' H7 _
  24. }
复制代码

8 L9 I' g. I: N* }6 k3 R  e3 \* p  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。
8 }% [6 y( v/ ]& {( p% t& g2 g6 }. q5 C, s. c! ^
  3.4 使用MFC类的虚函数机制 2 B" U- d/ t5 d$ Z- d2 ^/ y
  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子:
( N- O$ f+ h& T9 l- _( J6 s5 f  p
  1. % g% J+ H, A* {; V
  2. void CView::OnPaint()- U# p$ W" D& i# E/ Q1 `
  3. {
    & z0 p% r, |4 D: U& ^
  4.         // standard paint routine
    ; ?9 w$ \) d- }! x
  5.         CPaintDC dc(this);. D6 C, y4 x6 O- O  f; n
  6.         OnPrepareDC(&dc);% [+ q  B9 ~. S: _  c
  7.         OnDraw(&dc);
    - \4 c, y# T+ n1 u" s; T4 }
  8. }
复制代码
; T$ X* t. q" O8 y" A# Y8 \9 {5 ?
  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。
$ d9 c, D) V* u  以下列出了与界面美化相关的虚函数,参数说明略去:
" L# [2 y: f* U# @4 c! T, A+ `8 t* m6 f! FCButton::DrawItem
$ O1 i" A! g" A- i( X$ rCCheckListBox::DrawItem
$ B9 f4 v5 p* }( }CComboBox::DrawItem
9 C2 e5 }7 h' p/ q! `CHeaderCtrl::DrawItem * e# H: }  @) D9 E8 f# G. n6 f  ~& ~
CListBox::DrawItem ) N* {! Y$ _( E& `
CMenu::DrawItem 5 ?* L; s; z  u5 H7 m* [
CStatusBar::DrawItem 3 W% u9 m( t0 f2 Q( t, v- a6 N
CStatusBarCtrl::DrawItem 6 \, [! Q2 l& ?3 C6 G
CTabCtrl::DrawItem
/ W. }9 p' [* i& |' C1 t. V4 M$ gvirtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct ); ! M  K$ M. i/ y, Q: h* X# P
Owner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。
4 i- b$ q1 s7 ~1 O$ ]' ^; |  限于篇幅,在此不再附以例程。 * h2 [# o) R3 j& j' s0 n

1 x" U% \1 T. }- f. z- ^! l参考文献, T* C9 c, w/ P, X' z7 q$ A6 C
本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-8-14 00:05 , Processed in 0.022860 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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