找回密码
 注册
查看: 6743|回复: 0

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇" H( `- L3 f' o0 P4 _
  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。9 Z1 C* Y3 B2 {
  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。  c3 d. f0 R' R$ w! u
- l2 e6 |. M, A! ~( w
  2. 美化界面之基础篇
! v4 ^. X+ z/ c9 ^3 \7 ]; z" Z4 z  s3 Y  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……+ b; K# \! [% i# ?

* K4 u5 u2 s' ]- b- U  j" t2 c  2.1 Windows下的绘图操作
/ p# O. \5 O, _1 N4 \6 l$ r  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……
" @, s' \: Z7 S; n  M  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。
  z; @& o% X* f# V0 W% Z' T
% s6 T- {$ k# {9 z9 f! e( Q$ C$ [  2.1.1 设备环境类* P# Q) |& Z% M
  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。
7 e/ ?" Z$ p! c5 z: Q/ b  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:6 [5 C3 g4 }/ T8 D" D/ q/ X
  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式" N2 J) w0 D' Y0 ~. u6 z( E
  Mapping Functions:映射操作
+ [* _! ^3 v# L+ \. d8 `  Coordinate Functions:坐标操作" h9 {, E7 A+ I# z4 Y2 s
  Clipping Functions:剪切操作3 V2 O  L4 n& n: [& a, Y7 O3 R
  Line-Output Functions:画线操作. i6 v$ ]  a9 ^- @0 W6 t  Z6 z
  Simple Drawing Functions:简单绘图操作,如:绘制矩形框
0 x; t( Y# M, f  Ellipse and Polygon Functions:椭圆/多边形操作- }0 P9 Y/ L& P( K5 ~1 D5 b9 E
  Text Functions:文字输出操作, M# p; s7 o/ M  \- {  k! Y
  Printer Escape Functions:打印操作! p  `/ l. i: ?; x: p
  Scrolling Functions:滚动操作
4 z2 h% o* ]+ W! p' M$ {0 G  *Bitmap Functions:位图操作. `& j. o! A: C" r. A. }% {- b
  *Region Functions:区域操作
& E+ H- u( Y5 B8 C$ @! |) d  *Font Functions:字体操作
. i( k) C8 K  [0 ^$ b  *Color and Color Palette Functions:颜色/调色板操作
8 X# _1 P! \( S- D/ Q1 \  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。
) I6 U. e9 d, z* b0 E$ V; l; _# T
  2.1.2 图形对象类8 {# F1 E- D! c2 U) D& I
  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。/ u8 ?. _; V: X, O' m
  下面的表格列出了MFC的图形对象类:
7 S# d; t+ l% y0 n) q* v2 R8 b  MFC类 图形对象句柄 图形对象目的
& n1 L4 h' N* ^7 K$ N  CBitmap HBITMAP 内存中的位图. k8 S" U9 g2 Y# w: R( P
  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式
1 Z' Z4 B% x: H  CFont HFONT 字体特性—写文本时所使用的字体
2 o% x% j3 o# P' Q- b  CPalette HPALETTE 调色板颜色
, Q' G7 \6 @( M. v/ g- ^4 H/ P0 B5 W  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细3 k* ~: y/ b* s9 ~; ~/ w
  CRgn HRGN 区域特性—包括定义它的点" a/ H: e9 N6 X1 }3 b$ t
  表1 图形对象类和它们封装的句柄
% `3 h$ a4 a" N) P8 v3 [# L/ ~0 ]7 O& X* w7 z# x
  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面: 8 v! V/ y& F5 S' c0 z4 H
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:+ {) m7 h! a. l: }1 M

  1. & |) d4 @) f7 X1 Y/ ?9 v! T
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)
    5 Y3 s" \+ u+ T% Z+ u
  3. {  D) Q, {2 ]9 k8 ~' I; C
  4.         //设置背景色+ c2 R* L9 }# }  I7 P3 l
  5.         //CBrush CUi1View::m_Back
    1 V0 u$ t) p5 a  G2 t. N
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));7 m/ u5 e, F) E/ v1 F, x  O
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL); " b. ~3 G- ], g& _: |' M7 R
  8.        return CView::PreCreateWindow(cs);
    , u) f# J; ~+ P3 v* `: y; B
  9. }
    ( {5 d" i2 U3 c0 r) y
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
    * r' B  P/ u7 e& P, y
  11. {. f/ B) D2 }' Q
  12.         if (CView::OnCreate(lpCreateStruct) == -1)
    : J0 H2 m& e! I
  13.                 return -1;
    " O" ]$ a+ a, T/ I. s; u7 y
  14.         //创建字体# e5 Y& I" s6 `% N7 E* H
  15.         //CFont CUi1View::m_Font
    3 g# y$ P. n3 w
  16.         m_Font.CreatePointFont(120, "Impact"); ) y- V: E5 T. d/ C7 b. r
  17.                return 0;
    , P- C, x0 a* N8 p
  18. }
    9 A1 n( S9 d$ r/ D9 s
  19. void CUi1View::OnDraw(CDC* pDC)% ]' g7 e  S* A1 I: r
  20. {
    , C/ z; j' G4 g. g$ t' {
  21.         //绘制按钮框架
    - u! S: K2 W5 T
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);  W/ S5 x' p# |* ?9 ^5 r
  23.         //输出文字! m& u" e" R$ S
  24.         pDC->SetBkMode(TRANSPARENT);
    % T+ H( Q, I6 z2 i' g
  25.         pDC->TextOut(120, 120, "Hello, CFan!");/ i3 M% r5 [9 l" Y
  26. }
复制代码

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

; i) Z. k+ d3 ^0 l" L  3.2 使用MFC类的既有函数
* [0 Z& S2 F3 u! q  G) `9 r  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。
3 i; J  E1 J: g4 G7 w8 r9 z2 X9 e3 P. x( n
CWinApp::SetDialogBkColor% R! q" q! @/ G
void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );
1 J  m/ o# u" r# W9 U' v: q指定对话框的背景色和文本颜色。
1 P% Y* e, Z. s1 \: V; A8 W. R; B3 ?0 F! D2 {) w, [: @: T' C
CListCtrl::SetBkColor8 f! G; K) u( C# Y% [2 M3 B0 @2 ]
CReBarCtrl::SetBkColor
1 m7 o% E) v* ?- k  a: N; A% XCStatusBarCtrl::SetBkColor
- D- k  Z4 ?* ~1 }CTreeCtrl::SetBkColor+ D2 e: s5 @- o8 P; g$ ]7 r% \
COLORREF SetBkColor( COLORREF clr );8 |2 k2 P& K' {( K7 j- E
设定背景色。
4 G7 w5 {  B  C$ a5 B- Q( l' k% ]( x% m. r
CListCtrl::SetTextColor
0 k3 M# A$ p; a, D5 ~; l' D& UCReBarCtrl::SetTextColor! `: A: W* S+ d' z' a
CTreeCtrl::SetTextColor! d8 @: P' b* v+ q8 H5 E
COLORREF SetTextColor( COLORREF clr );" w; j3 z* c! w: \/ O
设定文本颜色。
$ n4 j7 ?* c* ~1 E: |5 n5 p- P% m2 n, }9 k: K# M: G
CListCtrl::SetBkImage
+ g# D* G& C9 x& dBOOL SetBkImage( LVBKIMAGE* plvbkImage );
6 t2 _4 ~. j4 @5 Y' \, r! J* q4 R$ wBOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);
5 u' [: O% ?$ W% ZBOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );0 b0 X, [" B  n9 `7 M
设定列表控件的背景图片。4 e1 v. Q% M0 G& K+ z% p2 Z7 G

- E3 q) O- d$ l0 LCComboBoxEx::SetExtendedStyle; {% h; {0 E7 {# b
CListCtrl::SetExtendedStyle6 ^- H0 m. d6 S" b% I% y
CTabCtrl::SetExtendedStyle0 c+ d1 C; W4 ]6 ^- F
CToolBarCtrl::SetExtendedStyle
; T% I; C& |- T. NDWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles );
8 V5 N  w# ~" y9 ?+ \设置控件的扩展属性,例如:设置列表控件属性带有表格线。( C* J& o( d8 P7 e
  图4是个简单应用MFC类的既有函数来改善Windows界面的例子: : `' a* S. ]7 Z6 c- l
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:
1 s4 t# p2 n2 w9 D1 k3 a/ |8 A9 K

  1. - ^1 S+ N: h( b
  2. BOOL CUi2App::InitInstance()5 r- g1 |4 x' Z+ M2 C$ b; J2 W9 U" Z
  3. {
    # c9 o6 [* R6 T; O& h
  4.         //…  n" ^# A2 B) D5 i) S' \- P" q
  5.         //设置对话框背景色和字体颜色: V% e5 C9 H+ P" E2 u
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
    + ~2 h" t# m  R: }3 B% K
  7.          //…
      f# x2 K9 K  C( m1 U# K0 m
  8. }
    ; J* {, o  e4 W) \4 k& L
  9. BOOL CUi2Dlg::OnInitDialog()' v' P$ r5 r* R4 [: a/ V4 ?8 T8 U* K
  10. {1 }3 c, E* u) @  Q/ ]& B, X- Z) o
  11.         //…
    : r. a& ~3 l; ~: X% J
  12.         //设置列表控件属性带有表格线
    " R! V1 m! r: r1 O) h* z5 p
  13.         DWORD NewStyle = m_List.GetExtendedStyle();( J7 i; O) c6 N4 y, v7 ~' a' i
  14.     NewStyle |= LVS_EX_GRIDLINES;& m  i& x( `1 o. ]
  15. m_List.SetExtendedStyle(NewStyle);1 m! X" S  _" d/ ]. _
  16.         //设置列表控件字体颜色为红色4 Y7 l! z( u* G& s% B
  17.         m_List.SetTextColor(RGB(255, 0, 0));
    , g9 g8 H; S" Z- e# a1 q, @' d
  18.         //填充数据7 L/ G3 m: ]8 m* M
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);/ ]0 z6 ~/ e$ k. {
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);
    : {! ?) I7 X# G& y, Q) b1 s! Y( p+ X
  21.         m_List.InsertItem(0, "5854165");: w& e6 s6 s' r' A/ W7 `$ F' Z
  22.         m_List.SetItemText(0, 1, "白乔");
    - t. Q/ g8 v) d' {9 ^) l) T/ W
  23.         m_List.InsertItem(1, "6823864");' V# C% p: k3 q  |
  24.         m_List.SetItemText(1, 1, "Satan");3 c* _" G8 C/ N
  25.         //…
    7 H" b9 i2 U7 c) c  h2 ?
  26. }
复制代码

. o+ l, n' X4 [8 S; h6 Q- y  嗯,这样的界面还算不错吧?
+ P1 p# k! A" _" W+ G( ~! ?; Q, J) w$ H$ d: g
  3.3 使用Windows的消息机制  B3 \! b. \* H+ i: g
  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息:
9 A, A) x3 a9 Z- ]WM_PAINT
+ j) L% P8 e6 e$ [1 tWM_ERASEBKGND 0 C  }2 Z! \$ `* C/ B
WM_CTLCOLOR* : ~+ t, t( z% ?0 Z6 ~( V# h. Y; M
WM_DRAWITEM* 8 q* Y; @5 s+ T" N
WM_MEASUREITEM* ) _5 p4 e( v* M4 }
NM_CUSTOMDRAW*
2 v7 i, k, M- i  E) ^. {& b  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。
! }' X+ X$ V1 C) N% o$ x  ~
8 E/ K% ~4 g  r! B1 E" S  3.3.1 WM_PAINT' ~# P" T) e3 L8 r
  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。  }$ Y" x0 W1 W; o
  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下:
" L  o; z2 ?- k# j  afx_msg void OnPaint(); $ s2 g) j+ Q: B  O
  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: ; i* U& [: K; A8 m# f- a
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单: # p8 u$ `* G' o/ \, S9 s' v+ c

  1. / r4 G4 I# w. }; a# r
  2. void CLazyStatic::OnPaint()
    . E0 ^, Q" Y4 l; J" f' b9 |
  3. {. b! ~+ l2 I6 \/ }( t
  4.         CPaintDC dc(this); // device context for painting
    + G' |- L, @" \! g5 w. W
  5.          ) ?6 |: Z. t4 Y. _, {
  6.        //什么都不输出,仅仅画一个矩形框
    ! g# R+ `( C* v- ^
  7.         CRect rc;; e, I# j, {3 f; ~6 [! F
  8.         GetClientRect(&rc);# w2 v- f/ a1 b
  9.         dc.Rectangle(rc);
    0 G' I2 P9 c$ S! [0 t
  10. }
复制代码
$ K! L: I- E- e# b
  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。
' S) {: n; V0 _% O! A7 A; G+ p$ b( K4 [/ V
  3.3.2 WM_ERASEBKGND / l6 Z+ X  T1 A3 }+ Q; l& U
  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。 5 Z% V9 W/ y7 I& U4 `$ b
  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:  i5 U8 i5 p" i2 }* y
  afx_msg BOOL OnEraseBkgnd( CDC* pDC ); 0 |7 a4 s: P4 y' _$ P4 H) w
  返回值: 0 E) B5 v6 ^+ c9 Q5 i: D9 r" y& }
  指定背景是否已清除,如果为FALSE,系统将自动清除 0 L' D+ w! h( v) E
  参数: pDC指定了绘制操作所使用的设备环境。
$ j& D0 s) }2 w. L& x  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景: # I9 Y5 `7 Q* L$ L$ R" P/ {  G/ S
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单:
1 I2 D2 }+ _: |. {/ a# E: I9 X

  1. ) |* }* U0 B& B" V1 s5 P1 j
  2. BOOL CUi4Dlg::OnInitDialog()6 K$ U: _( X( A3 H1 J
  3. {
    " m+ Q  Y0 J7 z* f
  4. //…
    + C  m& O8 e! A& ]. U0 r
  5.         //加载位图5 R% a* G0 x  L4 Y, J1 T6 k# t4 ?
  6.         //CBitmap m_Back;* z& j9 B, B- t- `& k$ W2 ]
  7.         m_Back.LoadBitmap(IDB_BACK);
    2 H8 U" E. J, O# s0 }
  8.         //…" s" u3 M3 L, ^3 `0 j+ M
  9. }! G% O' z# ]0 Z0 u0 R2 p
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC)
    / p) H, `5 P; n) b+ v" a) o7 D
  11. {1 Y/ w5 q# }! U5 x2 @8 N, E' S
  12.         CDC dc;
    6 K# }2 y# ]  ?
  13.         dc.CreateCompatibleDC(pDC);; l; m+ C, A6 w6 i
  14.         dc.SelectObject(&m_Back);
    & d$ D2 j) R1 Z; r7 M) Y; i
  15.         //获取BITMAP对象- t8 L0 e; s8 I. ^9 e
  16.         BITMAP hb;6 X! z% u; h' T& C
  17.         m_Back.GetBitmap(&hb);
    1 ~3 F8 h4 I/ w& R# i3 g* U6 E
  18.         //获取窗口大小
    $ o8 Y2 ]1 z2 `+ V# H& P4 S- \9 P
  19.         CRect rt;& ~- m9 B+ S' ?2 d6 u; H
  20.         GetClientRect(&rt);, m) K. `8 M$ p& v7 t
  21.         //显示位图; O7 z' N3 ~1 I0 v+ E/ O' @2 ~
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),) ^3 |8 e" A8 C8 g8 R! E" E
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);
    - F" G) {8 K* s
  24.         return TRUE;7 \8 q0 k8 T( M1 C. [
  25. }! @  _1 n! \7 ?+ s' c* u1 r
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) . q. U2 T1 K) S9 |
  27. {) H3 ^! e+ V' w3 f$ ]
  28.         //设置透明背景模式) m+ [7 J  D$ A* c6 g8 [. I
  29.         pDC->SetBkMode(TRANSPARENT);
    2 r' c/ {) a- y& n- a5 Y
  30.         //设置背景刷子为空$ n- L3 }3 m, `, V4 a9 N: j
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);+ ^% e3 t3 w$ z$ |  p+ ]
  32. }
复制代码

0 u6 }8 D9 A6 S1 |8 V' M  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。 6 M# ^! o( ]0 D4 D3 p7 {

5 M' t! ~' T) r4 ^5 U6 i% I8 T8 p  3.3.3 WM_CTLCOLOR / c4 Q* G/ U4 _6 ~  v
  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。 + s$ ]8 o6 D8 X' x( G; Q3 k+ g: }
  WM_CTLCOLOR的映射函数原型如下:
$ Q! F$ W: B. N! U1 T  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );; ^- I; v' k0 W% Z/ L0 B
  返回值: 用以指定背景的刷子 0 t" n. H8 s! X# g% m% j0 B7 i# z, g
  参数:
) t# c8 e9 |" x% t! x% w& ^  pDC指定了绘制操作所使用的设备环境。 % _: x& E; s; w: l* n1 }4 P
  pWnd 控件指针 ; p* s5 M" u9 t
  nCtlColor 指定控件类型,其取值如表2所示:/ {4 j& a* i0 z- k0 X6 b
  类型值 含义
# A: Y; y$ q5 S& z! t8 j6 TCTLCOLOR_BTN 按钮控件
2 ^& f+ r8 K9 GCTLCOLOR_DLG 对话框 / s& b+ C- v* p: y% r
CTLCOLOR_EDIT  编辑控件
  c, W0 p( V1 \* Y6 m  xCTLCOLOR_LISTBOX  列表框   h. M' Q8 s$ C! I& U
CTLCOLOR_MSGBOX  消息框
  K7 z1 d; ~; JCTLCOLOR_SCROLLBAR 滚动条
% G$ A: _+ u" JCTLCOLOR_STATIC 静态控件 + X% @+ q9 c, M! Y0 r
表2 nCtlColor的类型值与含义
! r* O0 b+ b+ v0 w+ r
" Y$ j" I. p1 U* h* }- H  作为一个简单的例子,观察以下的代码:
: E) h% c& f7 M# x5 k

  1. 8 B' k2 Y1 y& Y6 O; L4 U
  2. BOOL CUi5Dlg::OnInitDialog()
    4 p. Y+ J* x3 ?( d; q
  3. {4 f: }6 ~6 q6 x, H0 w& O4 x" C* s
  4.         //…4 U+ v6 g# p8 ?. V3 V1 D0 T
  5.         //创建字体; k6 O. \1 b" D7 z1 L4 |
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font2
    1 S$ P, W2 E6 o% [7 Q8 `
  7.         m_Font1.CreatePointFont(120, "Impact");  ?) E' \* P$ ?9 h
  8.         m_Font3.CreatePointFont(120, "Arial");: y" U8 L. v0 n
  9.                 return TRUE;
    ( l7 v( v' C# P2 L
  10.   // return TRUE  unless you set the focus to a control
    , k8 [; f4 v0 m% V1 L8 i, ~
  11. }
      x0 F7 Q0 f- x4 \2 v+ e1 r9 [
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    : Q' R* L1 G7 l/ a: \, B7 k
  13. {& @' K& `* a: p. N- S
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
    % f3 I, V* v. t6 r( j
  15.         if(nCtlColor == CTLCOLOR_STATIC)
    - o- x& I, B* w& ~9 U8 v
  16.         {
    ( l+ e0 }- T7 I1 K  D7 U2 D
  17.                 //区分静态控件0 _7 y4 D$ o% }$ t7 y: ]  i: ~
  18.                 switch(pWnd->GetDlgCtrlID())
    % n8 U5 p* g- B1 y8 \  `9 ?' v
  19.                 {
    7 m' n! {+ A0 s+ n
  20.                         case IDC_STATIC1:, i+ f1 H+ t: ^5 k4 \" a; D
  21.                         {! j, A  V- G0 f$ Z9 d" y4 T
  22.                                 pDC->SelectObject(&m_Font1);0 f- L9 S, P* y
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));$ p- j7 [, t3 M. S! a# V
  24.                                 break;
    ; H8 m) O8 l1 C3 t5 x; \! i
  25.                         }+ |, j$ ]! P& f- |" |4 h% |
  26.                         case IDC_STATIC2:" l* N+ U' |% E7 ]) J, D  E
  27.                         {
    / Y; L+ y- e. [" i3 ^4 i  m
  28.                                 pDC->SelectObject(&m_Font2);
    % J# i5 A; i6 {. B) B
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));3 d0 f: U! H  B7 p9 m7 P
  30.                                 break;
    : x9 Z" X# O0 ]- ^8 k! _) A: t6 x
  31.                         }; @6 e, i+ p' _5 d
  32.                 }3 |) s0 m: K/ O  ]; l
  33.         }4 v- Q% M0 q' i* N
  34.         return hbr;
    9 G/ C7 c/ p7 Q$ v) P- S% Z
  35. }
复制代码
2 l7 d# E' `+ K9 o( e( Z, z+ C
   生成的界面如下: , S% `( i" Y/ J. i7 a% }) |8 d
图7 利用WM_CTLCOLOR消息美化界面

. S: m: L( ^3 L: U5 }  3.3.4 WM_DRAWITEM $ K/ u. ]7 r" C; O  N
  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。
% _6 E1 Q! T  R; P" C6 p- h  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。 $ n# L& d% ]2 {
  WM_DRAWITEM的映射函数原型如下: 5 }$ L4 b( R/ l* A& V1 o8 x5 t
  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );
: o0 T6 R' C* q1 m  参数:
+ b$ P( ?7 t% I+ I9 y  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0
; P. E' N- M9 V9 N' c7 U. M  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下:
0 j8 t7 @0 X, d3 E& Y) q
  1. - x0 j) Z7 k& }9 O7 J
  2. typedef struct tagDRAWITEMSTRUCT
    4 C0 B+ i, x0 Q  k/ i
  3. {
    * N) [( v4 X( @" a
  4.     UINT   CtlType;
    0 }8 }  n+ f- G4 \6 ~6 Q2 P3 t
  5.      UINT   CtlID;$ T- ?, z3 s% E/ V7 Z
  6.      UINT   itemID;% t: D! F5 J* {8 y( R7 m
  7.     UINT   itemAction;' `# J# ^: Y$ o
  8.     UINT   itemState;
    % l! B9 t4 r4 X4 X
  9.     HWND   hwndItem;  }! J, ?; v) t
  10.     HDC    hDC;* Z0 S8 u6 [4 z0 i3 m/ q* _
  11.     RECT   rcItem;5 L4 }9 `/ R# K/ H# h4 ^
  12.     DWORD  itemData;
      [4 B) Q/ j" _4 P8 k9 W
  13. }DRAWITEMSTRUCT;
复制代码

2 M% x3 S$ d# o& pCtlType指定了控件的类型,其取值如表3所示:   t9 v) y" T, j' e3 u0 ^0 u
类型值 含义
8 t+ S" S5 N  {8 I3 r5 z2 I( s1 eODT_BUTTON 按钮控件
: J  @3 q2 N( zODT_COMBOBOX 组合框控件 0 T# K) a! h: P' B, Q7 Q
ODT_LISTBOX 列表框控件 ' _& ~7 I( S( H7 v, x
ODT_LISTVIEW 列表视图
6 E  y1 i+ C& s  JODT_MENU 菜单项 $ R8 Z: @: a$ l) j" n$ V% f1 e
ODT_STATIC 静态文本控件
4 e/ X; W1 U& J& x! o0 f1 a# d2 zODT_TAB Tab控件
* d/ {5 Z3 Q1 j& N# Z  \& O表3 CtlType的类型值与含义; U( ~5 J. a1 Y! {" y+ s* x: k1 Y

' l. J3 Y9 s% p  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
$ G. Y! y% ~; a3 r  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。
$ m+ x$ D2 {: v% ~( S9 ^- C  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:
/ V5 I1 W5 K+ }类型值 含义
; w6 o% _; S# \ODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。
0 M1 h, p2 p9 B& p; Q' i5 s2 xODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。 3 E' j" u+ E% w4 O# J7 J
ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。
7 b( Z( K7 I. _4 \表4 itemAction的类型值与含义2 z& X2 G# G3 f) Y4 _* y7 T
  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:" E7 G' ~, w9 _4 |, U: @
类型值 含义
/ E' a5 j; b6 F" {/ a' g7 _' FODS_CHECKED 标记状态,仅适用于菜单项。 5 x+ T5 @1 I, m% Q: a- ]9 ^; x4 z/ ]
ODS_DEFAULT 默认状态。 1 K  V  {7 e: F
ODS_DISABLED 禁止状态。 . \" M  x8 Q  j9 h5 i" O
ODS_FOCUS 焦点状态。 & c' {9 t6 Y1 w; _( s2 ?9 V
ODS_GRAYED 灰化状态,仅适用于菜单项。
7 ^6 A- S/ y0 e' g$ Y2 B3 jODS_SELECTED 选中状态。 - q& J' `, h9 l  r. U3 k# Z. P+ ^7 g
ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。
& @4 H% z' X$ Q; A, TODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 ) `8 }3 {: V! P, s. I' W3 D
ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。 ! a3 G0 K, B# _$ n/ d
ODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。 7 T3 @1 q$ R' x& p+ ~
ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。
0 o4 S7 u& f& W# y* i0 i表5 itemState的类型值与含义
: b/ _' ?# _0 Z8 }' d' l( _  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。 ! i. ?  N1 n2 k6 |1 y
  hDC 指定了绘制操作所使用的设备环境。
: o# P6 L  d7 s9 {4 z  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 4 h9 a/ W& L) e& F- m2 }4 _: J
  itemData 0 T* v8 ^  i; z8 p' Q
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
8 d, {; @) f; o: W  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 , P0 M" v5 U& W/ x% u/ J- {
  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。
: ]2 r- J* w1 H. Y) w5 N  图5是个相应的例子,它修改了按钮的界面:
; p1 r' r4 Z; W  L4 ^8 G! a& s
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下: . w) J! ?% Z( F+ t( d! o

  1. / p" x& l% g5 m5 r& f
  2. BOOL CUi6Dlg::OnInitDialog()
    6 _- h0 B+ X. h" d8 ^
  3. {
    " \5 x/ h8 F5 ?: w& |# z
  4.         //…. f* i1 U# U$ y; f; l) Z
  5.         //创建字体; N: T. ]) r4 g( w( l: l
  6.         //CFont CUi1View::m_Font
    . B! u. j  B+ X/ g6 P# s
  7.         m_Font.CreatePointFont(120, "Impact");& b4 y0 Q- o1 @7 [
  8.         //…
    3 w- C* P( r; T' s7 h
  9. }
      w9 J$ U; u) B* v
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
    & X! J" @- |( [
  11. {% k; @& f# @6 u3 y% I
  12.         if(nIDCtl == IDC_HELLO_CFAN)  l8 W- q! A! @& v
  13.         {
    0 u7 [6 m* c) Z5 a
  14.                 //绘制按钮框架
    . I# A) U7 z- g) K6 ^
  15.                 UINT uStyle = DFCS_BUTTONPUSH;
    # y) b4 F; k; t. r! X! r
  16.                 //是否按下去了?6 E" j1 Z7 [/ G' Y6 P- D
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)
    8 u" S3 V! a- q+ [1 U) ~4 |6 N
  18.                         uStyle |= DFCS_PUSHED;
    " g" x( M$ v+ W
  19.                 CDC dc;: B/ J) I4 x# i: ^8 {' g4 k
  20.                 dc.Attach(lpDrawItemStruct->hDC);/ U# {0 e8 J: s! s1 ^4 V
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);; h/ j! h" L3 A9 K
  22.                 //输出文字
    8 Y8 u! y  f: T
  23.                 dc.SelectObject(&m_Font);
    3 q( w, D* R9 @$ }/ m0 \' ^9 j
  24.                 dc.SetTextColor(RGB(0, 0, 255));
    " k; a8 `; k9 o& r1 H0 W8 @% a
  25.                 dc.SetBkMode(TRANSPARENT);
    # p+ D5 f$ {: u% V9 `$ ?& Y9 C
  26.                 CString sText;
    2 S7 M4 \; ?' Y
  27.                 m_HelloCFan.GetWindowText(sText);
    ( q0 T. j" F7 A. a6 n. J+ [
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);  a9 b( O; `; X9 V* f0 @+ E' S
  29.                 //是否得到焦点
    1 u- q0 {2 I  Q: T6 b
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)
    5 K# Y0 j+ c5 q0 M# z6 h% c
  31.                 {+ W: I8 F. Y/ H* ]4 z
  32.                         //画虚框
    ) k. E2 K; w# L. Y$ o4 n  E
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem;
    & K3 n1 k# v, d1 T5 s- M) I
  34.                        rtFocus.DeflateRect(3, 3);% t5 t) U4 `& O8 D/ P
  35.                         dc.DrawFocusRect(&rtFocus);7 F) q7 q' B$ b' K, ]: j
  36.                 }% ~1 F+ F6 b' c) \2 ?. a1 S
  37.                 return;
    7 Y# q( R1 s! o' z/ A& w7 i
  38.         }
    7 P6 [$ a/ R! n' f1 w! a
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);% M( K5 d. ?0 u* V. P
  40. }
复制代码

3 C2 t: W- ?; X' d+ I4 y0 W( Z  别忘了标记Owner draw属性: . _* [$ [6 d* G
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。 6 G  r% `: Z9 l

2 i# [( y: ^* ~  3.3.5 WM_MEASUREITEM
$ }) N  g! Z/ }: J  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
  r' u# m* Y/ D. M9 _1 l: ?$ D  WM_DRAWITEM的映射函数原型如下: 5 \+ h, l+ t& S# V
  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
: Q$ w( S$ o1 ^8 j% c1 i; ~& g  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l
) V( x$ A8 }; @  V1 b0 H0 y* |  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下: - k8 X1 M* I* ?1 d
  1. $ f0 l# _) N, W6 v" r! k
  2. typedef struct tagMEASUREITEMSTRUCT
    * s& w* l9 U5 z
  3. {
    6 v- t. |/ ^) p% }. e- E& ^2 G
  4.     UINT   CtlType;
      o  C/ X8 a9 D2 l
  5.     UINT   CtlID;
    0 d! D: g6 x1 r4 n+ S
  6.     UINT   itemID;5 l* z) g) u; z7 W2 I. K6 P
  7.     UINT   itemWidth;' B# e$ m! d# Y, I& D# ]
  8.     UINT   itemHeight;0 i. C5 @2 ?8 w
  9.     DWORD  itemData;" ?( ~: ]* U2 T$ Z( x
  10. } MEASUREITEMSTRUCT;
复制代码

  [5 G& G% P/ H' ^  CtlType指定了控件的类型,其取值如表6所示:
3 y$ ~$ d" p+ Y类型值 含义
& [6 d6 k& g$ z/ n2 a  ~& LODT_COMBOBOX 组合框控件
& {+ c, T" x' wODT_LISTBOX 列表框控件 6 @3 E% ?; F0 g7 }, J* S
ODT_MENU 菜单项 / C2 j* [* Q5 x2 P. |( X2 j/ o1 w
表6 CtlType的类型值与含义
. D! h6 x! N7 O  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
) b5 q* Z# z4 o  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。 ) J+ Z8 @; g; b" g
  itemWidth 指定菜单项的宽度
4 T  O5 C1 S8 r" ^( A  w  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255 2 H( p( Q3 Q2 r# h9 Q7 |( M4 S/ Q
  itemData
* s4 ^, h0 p7 k) n( Y! Z# L  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
+ Z+ ?( j# {  M: Y  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
9 I7 U* R- _0 a9 C8 W2 t$ z  图示出了OnMeasureItem的效果:
4 B, N) Z: S: n
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下: " q5 s' d# U5 h7 e& X

  1. " e: t+ ?6 o3 w, B/ t
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
    5 f! C$ t( s! a3 u
  3. {5 s9 Z. ?) e9 K$ L9 D  F- e
  4.         if(nIDCtl == IDC_COLOR_PICKER)
    ' f" U, `6 v& J5 ^" D7 X( [
  5.         {
    1 Y' Z! g1 @  T1 `, G
  6.                 //设定高度为30
    # d# @4 Q; P  p& H5 L& B
  7.                 lpMeasureItemStruct->itemHeight = 30;
    2 Q" C* ~! T; Y6 c* |
  8.                 return;6 w# d, w( t1 A5 B4 v2 f. m8 p
  9.         }
    , r7 W- h0 G7 r9 z2 P8 M
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);, m& ^  U' `9 O  ^4 ~9 Q" Q
  11. }
复制代码
7 N$ e% o2 i9 z4 [5 d% \
  同样别忘了指定列表框的Owner draw属性:
$ j5 h) C3 y: D5 ^( v6 w' Y$ W
  图11 指定下拉框的Owner draw属性  , x5 g3 L5 G$ S

6 J% e4 V. f. [* I/ j; N' V0 t  3.3.6 NM_CUSTOMDRAW
$ [7 z& f  h+ o) [9 {$ F& Y  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。
+ [" i+ [% V# S! O+ @9 U; L/ p% x) r  可以反射NM_CUSTOMDRAW消息,如:
, [% q) q. f3 O  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw) 9 j; a/ A( Z  m, Z' ~
  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult);
5 M( C9 `. x: H  参数: 7 B) Y! _# P9 s) g0 K
  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:
0 e" `* [# r4 O& ]9 y1 ^
  1. 9 o0 Q* L' b) G: [; k, ?
  2. typedef struct tagNMHDR- d$ [" h5 Q7 A( j
  3. {/ R) M2 m3 k/ G# ^
  4.      HWND hwndFrom;3 }) @4 u$ R+ U0 R, ~
  5.      UINT idFrom;
    & ?1 P# t8 Y0 @( s! C6 j, a
  6.      UINT code;
    : T/ m+ B( k7 `
  7. } NMHDR;
复制代码

. |1 ~4 D. M( R  其中: ' ^9 `4 c8 i7 t) e- H5 s6 [
  hwndFrom 发送方控件的窗口句柄
3 [) s5 i" w/ m, W7 ^8 R  idFrom 发送方控件的ID code 通知代码 3 |" ?2 d: i+ }1 h
  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下: + P* q3 D* e% o. ?
  1. 5 k3 \& K4 k6 I/ c7 h- @0 K( `! g" `
  2. typedef struct tagNMCUSTOMDRAWINFO& y, A' D( r7 G
  3. {
    ) y: N. C3 [) s" m8 }( S5 t  i
  4.     NMHDR  hdr;
    9 S: d  u# M. ?4 C  e
  5.     DWORD  dwDrawStage;
    + i  E& X. A) M
  6.     HDC    hdc;8 N$ k& u7 N3 J* W3 y
  7.     RECT   rc;+ c7 w: _& Z- ^, N' t$ N
  8.     DWORD  dwItemSpec;
    % F: f9 V6 F% S* q
  9.     UINT   uItemState;
    5 l. \# d2 y& e* e! h5 C7 B
  10.     LPARAM lItemlParam;
    ) t8 _& u! H* S( u; N, U/ v
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码
* F/ y! A8 h8 ]0 f$ d- l2 M/ W- {
   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:! [) e, o: Q1 G1 g( u8 L, Z" C/ T
类型值 含义
( D7 ]5 N' h9 N; x! ~CDDS_POSTERASE 擦除循环结束 + L* \- s  g$ R& Y0 P
CDDS_POSTPAINT 绘制循环结束
7 A7 k4 X* e* W4 dCDDS_PREERASE 准备开始擦除循环
! B3 d/ \8 E* O: i' ^3 aCDDS_PREPAINT 准备开始绘制循环 9 l1 b5 c+ i9 P9 R+ n4 ]7 h
CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效
- P; F! A0 c6 }/ TCDDS_ITEMPOSTERASE 列表项擦除结束 ) y8 d/ i# d9 ?1 l  d+ j
CDDS_ITEMPOSTPAINT 列表项绘制结束
: t9 o8 a; Z( HCDDS_ITEMPREERASE 准备开始列表项擦除
* h1 L$ V) l" y: HCDDS_ITEMPREPAINT 准备开始列表项绘制 ; b  j6 W5 s/ N3 }2 z' R, O; E" N
CDDS_SUBITEM 指定列表子项
- P& T5 {5 d4 M6 U1 j表7 dwDrawStage的类型值与含义
0 a) \& b4 Z: V: @  hdc指定了绘制操作所使用的设备环境。 2 l) a2 n  T" K: c9 _& d% ?
  rc指定了将被绘制的矩形区域。 9 g7 _3 j' j' J+ C1 V/ Y- Q2 g
  dwItemSpec 列表项的索引
- w5 h% S3 }) A9 U0 e  uItemState 当前列表项的状态,其取值如表8所示:
6 v$ @7 K9 \7 s- u: {$ ]类型值 含义 2 [2 y6 M4 s. |; x
CDIS_CHECKED 标记状态。
) O  A; L" w  J) A  UCDIS_DEFAULT 默认状态。
, p* u1 ^5 V# b/ s" t' Y" M9 a. |CDIS_DISABLED 禁止状态。 # k; G  J2 h- {" q
CDIS_FOCUS 焦点状态。
% M5 a: p& J2 W. C( CCDIS_GRAYED 灰化状态。 ! d: i6 m% ~* z" H+ ?
CDIS_SELECTED 选中状态。
4 ]( _+ w+ n1 W9 @! VCDIS_HOTLIGHT 热点状态。 * `9 v  l: ?% Q  {* v
CDIS_INDETERMINATE 不定状态。
+ u$ ?% \% u2 M* n$ X' KCDIS_MARKED 标注状态。
0 f- \* e+ k' U* C1 R表8 uItemState的类型值与含义
) o0 S8 o8 z, K: Y  Q# l* q7 o2 `  lItemlParam 当前列表项的绑定数据
" y; x2 j9 R4 V* U0 F( A3 I3 l+ S  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage:
5 p! a# o1 t# g( L; u! `. |% {0 s  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:/ X. e! j) @6 k. W/ P8 i
类型值 含义
1 n. d1 W% y) {! O0 F& VCDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。
, X* o8 K5 k" oCDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。
2 D1 j3 x, I, g3 \1 |+ O5 dCDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。
. w% [0 m) g5 ~/ dCDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。# Y1 H' W/ E8 h9 T/ B8 s+ I# x# z
表9 pResult的类型值与含义(一) * t. Q+ X4 F$ A( `% g4 y( [9 z4 `  e
  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:2 I, v$ l% l& _$ D: @
类型值 含义 ; P, ^, O5 P& V. K! Y! L! J! B
CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。
( d2 K/ `( P/ KCDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
: ~6 n! j" p6 hCDRF_SKIPDEFAULT 系统不必再绘制该子项。
& R- m+ _+ M& M# ?表10 pResult的类型值与含义(二)
2 Z7 w+ n" c0 `+ J; |3 Z5 W  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子: " C, z2 h- a6 @- L
  图12 利用NM_CUSTOMDRAW消息美化界面 0 f, ]0 {. }' U
  对应代码如下: 9 z: k5 _, t% {# v2 T7 J; O8 G7 D

  1. 4 k- }* f% W& x% C! w, P1 ]  m& F9 `
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)6 G8 T1 k- a! Z0 D6 }
  3. {
    ; c9 G$ U* J0 ?$ a2 I
  4.         //类型安全转换# l2 `6 z+ y8 |* K0 |% K5 t3 }$ w8 s
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);
    ' K2 m' [6 G+ G
  6.         *pResult = 0;
    5 F6 ~' F* n, j" U# R
  7.         
    + {+ V5 ^' G( B+ i5 ^, y
  8.         //指定列表项绘制前后发送消息/ w( c+ H' w. b( m: n( M! @
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)& {9 e; B# x: V7 x, W
  10.         {
    + \8 g0 |- X9 V
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;
    : V; y9 |" j* c- l
  12.         }
    $ X7 M1 _: w. V, x2 P( b. L- D
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)3 h' S0 n5 ?% r& J" H- ]
  14.         {
    2 w2 ]3 b5 {9 }$ Q/ X
  15.                 //奇数行# W6 S2 J' v& x5 G6 [" z
  16.                 if(pLVCD->nmcd.dwItemSpec % 2)
    3 n; V0 u! Q7 s6 \: L; D) y- Z
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);( p8 d; O. A# D) B5 G: I7 j
  18.                 //偶数行
    , y% f  Z: r; w2 k6 G
  19.                 else3 Y7 O4 ?3 I+ Q/ t0 ?
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);
    # p3 r- s7 M  H- e' S
  21.                 //继续, |! Z# }) x2 {& z& T" b0 d. o
  22.                 *pResult = CDRF_DODEFAULT;
    ; m( Q7 `) ?, L! i7 Q
  23.         }* L8 |4 w7 |( M& ^
  24. }
复制代码
2 m) ?" [) K- z; ^& L( ?! U
  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。 0 l3 O) K3 s' U" E

2 f# r. \& g6 c" y. R4 ?/ Q1 o% u; n  W9 V  3.4 使用MFC类的虚函数机制 - |! c0 V/ M* L$ ?' U1 c. A
  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子: 3 w% K1 q9 C8 M  N
  1. 3 r0 o7 a; O% H, n& j8 d& O
  2. void CView::OnPaint()
    " h4 E! D- q$ ^. c+ u
  3. {! u1 {- i8 `: }
  4.         // standard paint routine! c7 L; p  g  F( [/ U( K
  5.         CPaintDC dc(this);* B) k+ l2 J4 s; s. _
  6.         OnPrepareDC(&dc);# J) O; w3 N0 [
  7.         OnDraw(&dc);+ c' h( f  |* P; b" D3 p: q
  8. }
复制代码
3 @8 s$ S' v( Z$ u0 v' q1 w5 g' u  H
  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。
- b) w# y& P/ H" W: g  以下列出了与界面美化相关的虚函数,参数说明略去:
( [/ H  G& V) W  T) Z. eCButton::DrawItem
7 d( z( c; g( \- V2 Q- s( h/ ^# ^, ]CCheckListBox::DrawItem
& Q  i2 J  X* I7 e- Q5 JCComboBox::DrawItem 6 A+ {" s" p2 g* k0 e+ m( G
CHeaderCtrl::DrawItem , s4 A! J" _# s( r1 p/ |& Q
CListBox::DrawItem 2 [. Z$ n+ S8 P4 a1 H1 i6 u
CMenu::DrawItem # _% ]% y3 n6 Q5 I* C* A2 e
CStatusBar::DrawItem
. u1 x! f$ H3 y8 Y* A9 HCStatusBarCtrl::DrawItem
) \# O# e4 p9 K0 c0 g7 k- MCTabCtrl::DrawItem' a, c- ?4 D% G5 k' }! o8 w% L( L% |0 G
virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct ); 5 |! P% f% }/ z+ S  f
Owner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。
- L6 \9 w5 t. }  限于篇幅,在此不再附以例程。
& [2 M& a8 F2 R2 {
) ^5 q! [, Z3 o( p$ H参考文献
3 G0 X+ H* j6 ~3 v" N, n& K+ P5 A' |本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-10-2 00:32 , Processed in 0.020030 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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