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

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇* Q# F; h/ g( y4 O( D
  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。% N- s" \% v# |- D2 x1 x3 ]. L
  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。/ g7 `" ]2 S) l8 ^" K* A+ y9 j. S8 |% `
2 i1 w, D/ u2 W+ ]/ Z, g8 Z
  2. 美化界面之基础篇
$ L& [7 s9 S" D) q4 i  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……
# ~2 Q5 P; e" y* u2 ~' i0 }0 L1 A& `: v0 A7 Q' r
  2.1 Windows下的绘图操作
% }' a- z( O# n' n+ K( l; f4 ^  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……, _" V7 O4 C5 Q& P3 G# ]
  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。; ~& u" U! |7 W; W1 q4 b2 S- ~
3 m6 _5 b- C5 }7 O
  2.1.1 设备环境类
! ?! @9 J' b$ K( c% T  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。# c% u6 z9 H* |9 ~. D5 V
  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:
; c: j1 l- ^* `8 j8 [  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式5 ]9 q2 a9 j% h. ^$ D
  Mapping Functions:映射操作
2 L( p0 Y$ K2 m. h6 G  Coordinate Functions:坐标操作7 z; Z+ e+ Q- z  z* G% B
  Clipping Functions:剪切操作
( h+ x  ~/ ~0 H+ U6 y5 i, E! Z9 {  Line-Output Functions:画线操作
. I* f) ]' o; |, @  [  Simple Drawing Functions:简单绘图操作,如:绘制矩形框
# P( }+ e" I$ Z8 z, S* t+ Q, V) Y  Ellipse and Polygon Functions:椭圆/多边形操作
- ~$ o) ~4 A$ Q7 Y  Text Functions:文字输出操作
5 b& p' p2 R7 X1 c6 `9 J0 P  Printer Escape Functions:打印操作. z/ Z# d+ q1 R; S" k8 W7 r
  Scrolling Functions:滚动操作
( O& h4 G; e  I& s5 p  }  *Bitmap Functions:位图操作$ D9 W2 b% f, v3 Y1 z' t! W: W
  *Region Functions:区域操作6 N: d/ q+ m/ p
  *Font Functions:字体操作/ T5 v  K2 O3 g
  *Color and Color Palette Functions:颜色/调色板操作: T+ a' V2 d. d. N6 m" E: g6 _
  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。
! i6 K) j" e' D3 [. a+ L5 w# w( y0 y9 O3 H# m* R" ]
  2.1.2 图形对象类$ k8 g1 P# B, S: K6 K. ?' J- h
  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。0 [5 D  x/ |. i
  下面的表格列出了MFC的图形对象类:0 @. E; N5 F0 u& a8 K
  MFC类 图形对象句柄 图形对象目的
$ c7 W* F* v9 \3 X) w  CBitmap HBITMAP 内存中的位图
- L+ A& X. x" T: a, d+ U6 K  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式9 H  w; H) _9 \7 z7 r. V) ?6 c: ~
  CFont HFONT 字体特性—写文本时所使用的字体
3 W1 G) e7 I  h6 e) |. B  CPalette HPALETTE 调色板颜色
& p: o: h. b; c  u% V$ Q  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细
) o  l+ ?( D: r$ s9 I6 a  CRgn HRGN 区域特性—包括定义它的点+ t1 {6 [7 ]: K' u6 Q
  表1 图形对象类和它们封装的句柄
7 {4 P8 P* _3 A& n& P6 \; R+ h; ~- O) Z; S
  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:
' ^) @9 Y0 }( ^- \: s+ U' A
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:
  p$ U% _0 A. G  M+ Y

  1. $ |' D. S/ Q5 Z( X
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)
    6 R' U. P' g# o* I8 x% F8 ^
  3. {) a7 i4 t& z  I; q6 I
  4.         //设置背景色8 ^6 B/ [- B, K" R
  5.         //CBrush CUi1View::m_Back" n* K6 C; K' W& O
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));3 m- U/ r9 X7 O% B
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL); ) r5 Q2 h5 X: f1 R: t
  8.        return CView::PreCreateWindow(cs);3 @% A3 Q! B7 C4 u! r7 a
  9. }- |5 _: \/ E9 l
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct): H3 _- w. N4 o2 I
  11. {
    9 I' U; X/ N2 Y, ~7 A8 a* C, P
  12.         if (CView::OnCreate(lpCreateStruct) == -1)
    * A. E( k7 h1 H' ^' `$ H% p
  13.                 return -1;' f3 R% ?$ l: i$ ?, _
  14.         //创建字体
    ' S$ g) e0 A6 ?4 n2 M0 j/ M
  15.         //CFont CUi1View::m_Font6 b1 ]7 ]' W. L3 f" y
  16.         m_Font.CreatePointFont(120, "Impact");
    # J. W1 O) ?- [  |
  17.                return 0;) z- ^% }9 T! z- B- X* c, L+ N
  18. }1 x1 {: i5 j; a  ]+ u
  19. void CUi1View::OnDraw(CDC* pDC)
    1 _! I* y: \+ v
  20. {
    6 W  q" q- X% U1 ]2 _. C
  21.         //绘制按钮框架
    8 Y  X4 F5 p& }/ ^8 m0 \8 c
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);6 B6 [" P* b9 A& d. M0 S
  23.         //输出文字# z$ h! j& r) M2 N0 E
  24.         pDC->SetBkMode(TRANSPARENT);
    ; F& g% x5 r2 ^( u+ A% s
  25.         pDC->TextOut(120, 120, "Hello, CFan!");
    % I# @5 X0 w7 F& x7 i
  26. }
复制代码

1 \- P5 Z$ |0 u9 H% Y" O3 M  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。 0 j& }  H3 Q3 z' W
% \1 M* [0 n$ }/ \2 M" s' x7 m
  2.2 Windows的幕后绘图操作 . b9 B& h2 D: l4 `! x
  在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。+ C9 g$ e! z# K
  有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。
! I. v8 x: |8 V: o. @  所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……
! _; {8 n4 C9 p. p  有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。5 Q4 w% \+ C7 t; a; l6 ]4 z! ]
( f' B5 b6 }( S, F9 J( K
  3. 美化界面之实现篇' @" `/ @. r4 N
  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。
! J" c" b6 U3 c7 B  x9 d8 P3 d( K0 d0 n; E* @9 V
  3.1 美化界面的途径: F" N6 G: F4 w
  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:8 V9 ], y) v, |' Q! Q* W
  1. 使用MFC类的既有函数,设定界面属性;  |/ a- ^1 D- W3 N
  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;- b; H, o2 S9 H! i9 W
  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;& c7 ^1 I  f. i5 ]
  一般来说,应用程序可以通过以下两种途径来实现以上的方法:
) s! b+ ^1 _' v7 Z% r! v. {  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;
& e  U1 |6 G& I, H( Z' I  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。
6 A1 O/ J2 o1 i/ S  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:2 \7 f( d6 s; u8 |  U1 ?& ~
  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示:
4 P/ `# m* T" P9 ]$ q7 ?; v% {6 a
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;/ G& F+ i7 a3 y3 Y! Y% J* v& `
  以下的章节将综合地使用以上的方法,请读者朋友留心观察。
9 C! Z' X3 J, i9 j; \& ^5 B! |2 M3 q8 ~  {5 _
  3.2 使用MFC类的既有函数4 p1 l' {5 ]. `4 f3 z" n, z' x
  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。
( o6 P+ V0 Z# x- Y" h( a! p5 c
; [; h) X- `0 @, H! \, _CWinApp::SetDialogBkColor
. g+ ^+ f# p8 |4 |9 pvoid SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );
7 R; C# Q1 S# v0 G5 s指定对话框的背景色和文本颜色。" Q- l& f6 s- v- G' m, G% H

: t* H9 D1 r% F2 \& @8 }CListCtrl::SetBkColor
4 T& T7 D; |8 N; D0 D7 V4 e! l0 gCReBarCtrl::SetBkColor9 s5 f* m5 B5 f! ~6 e* X1 N# `
CStatusBarCtrl::SetBkColor
0 A. C& m6 l, ]' T9 H  GCTreeCtrl::SetBkColor
6 A: S- S0 ^1 U' r2 vCOLORREF SetBkColor( COLORREF clr );
( y4 {9 {4 F. {- b" d3 Z8 f7 I设定背景色。
9 b9 q4 m; J! G) G1 q
( N. ~. O7 t% R4 x" e# z3 mCListCtrl::SetTextColor
) L4 x/ J' p0 z: Y9 p7 Z. MCReBarCtrl::SetTextColor
& J, C+ U. `8 m' H6 JCTreeCtrl::SetTextColor
/ F0 g. c# W3 ^( ?2 C; {COLORREF SetTextColor( COLORREF clr );9 {2 {# U2 h8 {: X# \* ?" X
设定文本颜色。
* g3 \9 X0 ~  Z& N
  z3 `3 s3 T5 I9 J/ D% @, |CListCtrl::SetBkImage
6 B( ^" R: u3 \/ i6 \- qBOOL SetBkImage( LVBKIMAGE* plvbkImage );
0 O% u5 F0 d" |# ZBOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);
. \# r1 O. j5 X2 Q1 GBOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );1 [- T9 H# V1 l1 d1 a3 t
设定列表控件的背景图片。: B; Z$ A$ i9 O2 ^% i4 n

( T  G, e  |( h5 U; MCComboBoxEx::SetExtendedStyle# u" m; j! ]7 S. R" P' w
CListCtrl::SetExtendedStyle, u: \: P& f% n0 B7 [/ r9 R
CTabCtrl::SetExtendedStyle
! O, r4 k- h' u& g  MCToolBarCtrl::SetExtendedStyle
, L2 x/ A, F- k( K# @DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles ); # q  V, h! _# g8 `
设置控件的扩展属性,例如:设置列表控件属性带有表格线。0 `7 y6 T: \2 @# i: R7 L
  图4是个简单应用MFC类的既有函数来改善Windows界面的例子:
2 @) K% ?/ b$ z9 M" P7 i) {% n4 p& t# G
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:
! ]9 k& v# j* J
  1. 2 S* U( ~" |2 ]8 z$ X3 i$ _
  2. BOOL CUi2App::InitInstance()
    2 x4 U0 s$ X+ h2 W
  3. {
    5 h3 {  `* J7 P6 g: N3 b. M: |
  4.         //…( `( X3 k' h/ c# m% V
  5.         //设置对话框背景色和字体颜色
    & n, l- n1 x) {
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
    ( W/ e& a1 N+ o9 q  H+ \
  7.          //…, h% W6 j: _( v
  8. }
    3 t3 C% s. J4 E( O% F  K1 ?
  9. BOOL CUi2Dlg::OnInitDialog()
    " S3 G! ]5 P6 V6 F0 j/ V" `
  10. {; r# U& S: m) a+ ?3 N
  11.         //…6 a/ T; A" }8 X. w; e* ?7 D) n
  12.         //设置列表控件属性带有表格线
    - H) _* _/ m3 S4 R+ p6 F3 S# ^
  13.         DWORD NewStyle = m_List.GetExtendedStyle();
      W6 U3 n9 ^% ]5 N$ ^" J! Z$ ~4 E
  14.     NewStyle |= LVS_EX_GRIDLINES;
    - k" W. u! @; u: \: v" a; w
  15. m_List.SetExtendedStyle(NewStyle);$ a4 v! ]0 r! F# m5 J! k
  16.         //设置列表控件字体颜色为红色3 l- L+ g' U- W+ t/ B7 ?
  17.         m_List.SetTextColor(RGB(255, 0, 0));
    . V  o4 `) Y4 h) r7 @1 k
  18.         //填充数据
    7 N( `5 w9 P, d* M7 H" |( S4 c
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);, O6 z7 h- D; G' G
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);
    ' r" r% G. f$ L1 Z5 _
  21.         m_List.InsertItem(0, "5854165");
    " Z7 C9 G% _8 c. @3 I% s  I
  22.         m_List.SetItemText(0, 1, "白乔");
    1 }! K3 I/ G: U+ f" n
  23.         m_List.InsertItem(1, "6823864");8 |9 o) g$ {% b8 `8 t' |- r" t
  24.         m_List.SetItemText(1, 1, "Satan");
    6 Q7 F. h* T1 V8 y9 R3 Q; K& l0 j
  25.         //…7 _' {! s* V) n+ B' t6 i/ k
  26. }
复制代码
, e' m7 P6 a5 \2 w6 G) N% t. Z
  嗯,这样的界面还算不错吧? ' i3 r: b; C+ k  w0 G8 V  G
2 T5 X0 U) D. x, E( v. E
  3.3 使用Windows的消息机制
* b) v- R0 T/ ^0 i; c2 ^" f9 l  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息:
) W9 W! a& u* q# H! g6 W7 aWM_PAINT ) Z) K2 Z- z2 x  I! x7 |, E
WM_ERASEBKGND
+ X5 L" J3 w+ ]! v: U  XWM_CTLCOLOR*
; D0 U1 z* X8 T) [( _+ _WM_DRAWITEM* ) `$ w1 S" d* w9 q
WM_MEASUREITEM*
) `4 s9 ~' P/ x0 U+ r) f5 `NM_CUSTOMDRAW* * ~- l, M6 X% q: v! P( @
  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。* O6 k2 j' Z+ ~5 Q$ X
1 q% Z7 O1 L) u3 L( [8 T5 N( c
  3.3.1 WM_PAINT! D! T1 @* B& ~
  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。2 S9 x3 {' Y5 y' F0 d
  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下: 1 y  e, j1 V- A: W* W
  afx_msg void OnPaint();
1 a. B6 J& c( h# }9 E* }& a  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: 1 m5 u4 w- l6 }* ^( t
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单: " }4 P$ H- ?# m

  1. / v7 b- k- z5 C5 f- N% u
  2. void CLazyStatic::OnPaint() 7 J2 [8 |4 p/ P+ e( A
  3. {& G1 R, a1 b( S0 }
  4.         CPaintDC dc(this); // device context for painting" y9 h% t$ X, @" @- x
  5.          ' z& N) L. g) g+ u" i# g7 Q) O
  6.        //什么都不输出,仅仅画一个矩形框7 y  R2 Z/ ]) O
  7.         CRect rc;) K/ H. g( h- J9 h5 S/ ?/ v
  8.         GetClientRect(&rc);2 E' p! b2 O4 e+ N- E% D
  9.         dc.Rectangle(rc);
    , |8 h0 y; N$ Y( a' R. |
  10. }
复制代码
! H! n: H% L$ C" v# K0 Z9 N2 B
  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。
/ O3 }. p9 j& w( I; L& |2 W! C; J; v# B! t; T& C4 ~+ Y1 H1 A% O2 t" \* f
  3.3.2 WM_ERASEBKGND , C' }5 I# X/ I( ?& o. \: f9 T9 E
  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。
6 Z" M, K0 Y4 F3 }" u5 j5 G  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:
$ N" d7 W/ _. X- p% k" O  afx_msg BOOL OnEraseBkgnd( CDC* pDC );
3 ?3 I7 S7 i# C/ o7 A  返回值: & l4 ]1 e2 x! x6 ^2 t$ |- v( [
  指定背景是否已清除,如果为FALSE,系统将自动清除
& }, Y- S2 A5 z' y8 t0 g! q  参数: pDC指定了绘制操作所使用的设备环境。 " J& U0 K! s% R  u
  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景:
5 v- E- G# E; l+ v: |; p* t
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单:
2 h1 k7 L& i) w1 B0 t

  1. * T; }9 i! y# M# W' p; a
  2. BOOL CUi4Dlg::OnInitDialog()6 S* G" t% v7 w2 x
  3. {9 i5 Z3 F5 K# f  p* B
  4. //…, b% f% |9 D& ^% p9 E2 b# X
  5.         //加载位图
    " X4 {. t/ i, _, q/ s5 ]( q
  6.         //CBitmap m_Back;
    0 w# r" Q2 V& ]0 ?& f4 b, M, b
  7.         m_Back.LoadBitmap(IDB_BACK);  V; L8 l7 s9 o1 ~+ r1 C
  8.         //…
      N$ l) E5 y- t( v
  9. }
    & g1 Y9 o$ q5 ~3 w9 ?
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC)
    * n  G# O9 |3 q+ t* V) u9 Z, M
  11. {
    " F* d, T8 V) j& D' ]8 P+ h
  12.         CDC dc;  W4 k; g) D4 Y+ w' p# P6 H& \) d
  13.         dc.CreateCompatibleDC(pDC);
    4 \& k" m9 |$ `1 ?, o5 P
  14.         dc.SelectObject(&m_Back);% T4 D: W) R; X/ h
  15.         //获取BITMAP对象' T' i$ B0 U$ z1 Q4 G/ Y! x
  16.         BITMAP hb;
    & T( n0 B! x7 r: y7 U
  17.         m_Back.GetBitmap(&hb);
      Y+ v! a( G1 C( R, D( i8 P
  18.         //获取窗口大小
    * G9 O( y1 W4 l8 @$ s4 ~2 d4 g
  19.         CRect rt;
    ' S( v  y+ P. |1 X' {
  20.         GetClientRect(&rt);4 e% Z! ?8 ]' k" _& t2 _" [
  21.         //显示位图. q' M& G5 F0 C9 n
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),/ I* b0 l* \4 p: X% ?9 N) K
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);+ w; C0 M* A, y. o
  24.         return TRUE;+ d! G8 O& f+ u- s# s* q9 A# t
  25. }' Q6 ]2 D6 A6 W
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 6 y# I+ H; t+ e: T% {* r
  27. {
    0 ]& Z: `( N$ V1 D3 H$ o1 |# `1 {
  28.         //设置透明背景模式
      z3 V/ w8 {! ~5 X0 }
  29.         pDC->SetBkMode(TRANSPARENT);
    ; q0 ]3 g: L6 f! N5 k1 E3 O$ W# @
  30.         //设置背景刷子为空
    ' P" G& h* S9 Z3 g
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);! H' {+ y8 x8 _9 w# x% b
  32. }
复制代码
# s" x% }7 M2 H+ @* Y
  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。 + a6 |% F8 T$ l8 L* L  x

. T+ B- b  T8 l/ r) m) @3 P  [, @, ~; a6 `  3.3.3 WM_CTLCOLOR : M! @  y" {9 N- |4 K% ?8 h
  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。 ( T# P1 e3 P& ^' D5 G3 J
  WM_CTLCOLOR的映射函数原型如下:
5 G" T" ?6 R9 ?/ O  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );6 z3 {9 g+ ^# g5 T6 ?
  返回值: 用以指定背景的刷子 ' `: l' ^5 B* k# K& w9 r: n) a
  参数:
* T* g0 a- T0 Z' J; }3 Y  pDC指定了绘制操作所使用的设备环境。 8 Y, ?/ L( K0 x7 l4 k/ h5 z1 R) f
  pWnd 控件指针 : I/ m1 h% c4 o' A* T
  nCtlColor 指定控件类型,其取值如表2所示:; C3 s( t% K" @$ l2 i* D
  类型值 含义
9 W1 e" H, ?7 {" t& s* w- zCTLCOLOR_BTN 按钮控件 1 e( t' e) K1 f3 k
CTLCOLOR_DLG 对话框
9 i9 v$ M+ j& v% V& Y. wCTLCOLOR_EDIT  编辑控件 & C& `! v% a, k( q) q) t
CTLCOLOR_LISTBOX  列表框 0 g8 _7 _9 x: M9 d# }5 {$ ?; X
CTLCOLOR_MSGBOX  消息框   |" E; L3 [! r" N8 l
CTLCOLOR_SCROLLBAR 滚动条
& C/ z1 t% u/ K0 r, ~+ T0 H; aCTLCOLOR_STATIC 静态控件 2 e% g2 |8 L6 s) @( s3 s$ H9 i$ K
表2 nCtlColor的类型值与含义
& [- d% {7 ]% R: d: Z1 f# m9 z5 s
  作为一个简单的例子,观察以下的代码:
  v* K& x7 v+ `+ o. F" n

  1. - A- ?4 E: f% r' i
  2. BOOL CUi5Dlg::OnInitDialog(), K9 D" H9 r+ j& {
  3. {# s; L+ X) e$ v5 l
  4.         //…
    ( R5 J* z+ B0 \( l0 U) O: D. R: t
  5.         //创建字体
    5 r0 r7 p) d2 b* f$ O; E8 T
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font2& m  `( l8 o7 J/ w
  7.         m_Font1.CreatePointFont(120, "Impact");
    . S! E( ]( `; h5 F& U# n: A  \
  8.         m_Font3.CreatePointFont(120, "Arial");3 ]  r/ r" m. Q3 `
  9.                 return TRUE;: q$ b) p4 a. r9 F
  10.   // return TRUE  unless you set the focus to a control 4 L) V! D6 W1 ?5 p! F9 }
  11. }
    " n- Z& z# F) f, X- Q; \
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    4 @/ w' |! A. ?" w
  13. {# P1 N, q' h, l( g4 ^( A
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);8 s' Z8 V! A; A, n' h8 W) Q  j
  15.         if(nCtlColor == CTLCOLOR_STATIC)3 G$ S3 }6 M; B
  16.         {
    " m$ b# F: q# x1 U; M. e
  17.                 //区分静态控件
    8 d6 R3 {, U$ T
  18.                 switch(pWnd->GetDlgCtrlID())2 A4 p3 K; E# d; i
  19.                 {
    * l+ o6 g+ \! Y- K
  20.                         case IDC_STATIC1:
    * m9 V6 K6 C$ p% [
  21.                         {0 W" K+ M$ z, w
  22.                                 pDC->SelectObject(&m_Font1);
    : f' H. m" u/ V, a
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));0 b% {2 _8 I+ q% n8 O' M+ s/ n! ^
  24.                                 break;. M( ~' }* z+ E/ z& ]8 u& h( _
  25.                         }0 [  t( _1 G2 G- g% I& c! l# V6 u$ r0 J
  26.                         case IDC_STATIC2:
    ( \2 t) ?! ^- `% e2 R4 ?1 V$ V
  27.                         {
    2 D5 m. S* S7 h# F& `  |3 Z
  28.                                 pDC->SelectObject(&m_Font2);! F1 C- m+ C, w
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));
    " \0 U' F4 h% T8 y7 ^& P  C
  30.                                 break;
    4 `4 Y7 T5 }$ N8 w+ D. O( u/ k
  31.                         }% V" {( e  v1 J! X, X5 ?
  32.                 }9 \; i9 X" o/ Z7 ?6 w
  33.         }
    . Q0 {* {' |* _) s0 P
  34.         return hbr;9 O, ^0 K( g  p" L8 i
  35. }
复制代码

$ q/ N( @9 R$ i: Y$ @* _# _   生成的界面如下: # @/ Q& N, f# Y! u
图7 利用WM_CTLCOLOR消息美化界面

; Q$ w, e, f' D$ k5 O* }5 ?% I  3.3.4 WM_DRAWITEM
- M+ V% Y! {" y4 E  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。+ A( u5 U3 d( q" `* N
  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。
& M) h; S  M4 M9 Q  WM_DRAWITEM的映射函数原型如下:
: _- M) \  \4 ~( G, T  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );1 C8 |+ B9 W: h6 H; B9 w
  参数: ) Q( u0 w: P; j) Z; w
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0
8 R6 p$ z+ k* u/ ]- s9 M2 F  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下: 5 C9 h) Z; j! p

  1. 6 K; t% h8 o: p' U8 s- L' h
  2. typedef struct tagDRAWITEMSTRUCT
    ( y0 ^) f5 H3 x7 W" k+ D/ t
  3. {2 L( [3 D9 f: w, D0 b, \8 C( n9 S
  4.     UINT   CtlType;
      t, v/ b" X9 {0 v9 O
  5.      UINT   CtlID;# O) i5 b' K+ M
  6.      UINT   itemID;  A/ P' V( g$ F) n8 f
  7.     UINT   itemAction;
    & a$ A* d% K* k1 m5 h
  8.     UINT   itemState;
      |# p" p' D% `* e$ K  z
  9.     HWND   hwndItem;" k4 B8 }' W3 a( y: w
  10.     HDC    hDC;
    4 z" H3 ]3 Q2 Q/ R
  11.     RECT   rcItem;$ ?1 k; D: J1 U; b  A
  12.     DWORD  itemData;
    9 j1 C( P$ y( c6 ~5 I# Q
  13. }DRAWITEMSTRUCT;
复制代码

! k) X$ a. T3 l. H! ZCtlType指定了控件的类型,其取值如表3所示:
) V5 {! ~5 q; j5 q5 M/ l; C+ d类型值 含义 1 H5 D; |* _0 c: O* A" t; v6 w+ d
ODT_BUTTON 按钮控件 - M3 f! Q3 k6 D& _
ODT_COMBOBOX 组合框控件
. _! n! S( Z& \ODT_LISTBOX 列表框控件 0 m+ U+ m! F. z& H$ ]% C7 e1 ?* G
ODT_LISTVIEW 列表视图
* f2 a& A5 u9 G' c- q1 t$ F& nODT_MENU 菜单项
$ Y. Z9 E: s1 a- m8 L; ~( FODT_STATIC 静态文本控件 / T5 W1 w' f/ K7 d
ODT_TAB Tab控件 9 g  A: k; R+ u) [
表3 CtlType的类型值与含义
* x+ p5 H$ r5 q9 k: H0 v% [/ j/ [& _2 F$ Q+ c
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项 ! t7 H# C% Y5 b5 H, \: N' e- n
  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。 , k0 p# d- i* N4 i& p- i
  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:6 O2 E7 n, F' {; X% r- r- U
类型值 含义 ) S+ R% k! q6 n5 N
ODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。
; {2 i* j! s7 `9 S8 c& MODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。 . K$ ]  I) k3 _* x- y& z( ^: p
ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。
: l" I( f1 L/ K; [5 o+ c' R表4 itemAction的类型值与含义
7 G$ Q( I/ J7 r  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:9 p% H: f4 [- S, c, {# O2 P
类型值 含义
1 r; c, b4 q, {) J5 ~% s+ CODS_CHECKED 标记状态,仅适用于菜单项。 ; p. o: B1 j- Q% z( E3 l8 ?
ODS_DEFAULT 默认状态。
7 z. v/ ~+ Q2 l% d7 n: N8 z! AODS_DISABLED 禁止状态。
; r  @3 ?- ~( x* C3 y2 v5 |/ U* VODS_FOCUS 焦点状态。
5 a5 v2 X" {& `( C  o  BODS_GRAYED 灰化状态,仅适用于菜单项。
2 S% x8 v+ ]+ l* K+ u9 MODS_SELECTED 选中状态。
, ~+ ]  j% C: @ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。 9 m" p" m3 b1 u/ e* ~' b% c
ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 " m% x( p1 z* |2 o; B9 _7 R" Q
ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。 * o; V8 R$ x. ]! Z1 I5 B
ODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。
# E' ?! w1 ?9 V0 a. v0 w1 Q( R2 XODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。 ; ^5 B  S2 s( ?& {8 H8 a. ~/ Z
表5 itemState的类型值与含义
; e: h, L! N2 {. L3 y5 {, O! ?- G  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。
' p  C# D' _5 N7 L4 L  hDC 指定了绘制操作所使用的设备环境。
. J+ Q' @) A( Q' @+ l  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 & I2 f# F. z# Z$ g) @
  itemData 5 R, \- J$ A$ D
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
2 u0 c* _' M; G9 Q+ k$ f  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
/ n/ F( m& g" d6 a$ i! x+ l9 W+ ~  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。 6 _; b9 N4 s% D
  图5是个相应的例子,它修改了按钮的界面: * P3 w! J3 e. \* I; g# |4 F
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下: + [* [/ h7 Q/ ?0 c7 B. F1 u

  1. 3 {  X5 d' o/ ~! J6 B  d
  2. BOOL CUi6Dlg::OnInitDialog(): |0 `" W2 W' ]+ Q2 a
  3. {
    3 F/ S, O: @+ b# J
  4.         //…& C( ~, k- Y, m9 [
  5.         //创建字体
    1 u; |6 F- O8 P: W# D; Z
  6.         //CFont CUi1View::m_Font
    ' ?% ^0 T4 C) }1 G% U  |; s' ?
  7.         m_Font.CreatePointFont(120, "Impact");$ f& l. P& G" p& i& @
  8.         //…
    $ R9 T, _( n- ]* D2 u: J
  9. }
    1 A* P5 [& P2 m5 H
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) ) t2 C. \9 D; @4 B
  11. {
    0 z3 P& n( t4 P
  12.         if(nIDCtl == IDC_HELLO_CFAN)) f+ P  u% n8 t# b
  13.         {
    * c7 Y7 a, Z" g' O- d4 C$ J
  14.                 //绘制按钮框架
    6 `5 P2 d4 E) f2 H( }8 I+ |' g
  15.                 UINT uStyle = DFCS_BUTTONPUSH;8 m" D8 g& ^  o% }/ G+ ?
  16.                 //是否按下去了?
    4 ]: Y, ~; B9 s/ B" P
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)
    $ W# w6 W# \7 }& v7 W
  18.                         uStyle |= DFCS_PUSHED;
    - E0 H5 J9 ~6 v6 S
  19.                 CDC dc;$ F) t$ c, z6 b8 \8 L
  20.                 dc.Attach(lpDrawItemStruct->hDC);
    + j! T2 Q9 O: L( L% h; o
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);
    . \% L! E' t' q& W# |) A! r
  22.                 //输出文字( t) n4 V2 O! F8 X* r  t* C, Q) u4 @
  23.                 dc.SelectObject(&m_Font);
    5 n5 e% m1 w7 x  h- Q7 r
  24.                 dc.SetTextColor(RGB(0, 0, 255));
    9 w- [. g/ O0 t. P) D; \) g2 a4 H- ^
  25.                 dc.SetBkMode(TRANSPARENT);
    . {& J  ?7 E" o3 Z# g7 k
  26.                 CString sText;
    3 v5 S! \9 H  D
  27.                 m_HelloCFan.GetWindowText(sText);
    7 u: i1 O7 S% ^
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);& _& [/ }* h3 s% f1 a( U
  29.                 //是否得到焦点
    % \/ \( t. z$ }$ ~& a
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)
    / G) E' @* y# i2 i/ K, ]* I; g' e
  31.                 {; e3 \- H2 B7 Q* X: u  |- i; S
  32.                         //画虚框8 y; m) I! u$ O( P  J
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem; 0 c/ a" O, r+ t
  34.                        rtFocus.DeflateRect(3, 3);; P! ]0 h6 ~8 R& V. \5 v1 d
  35.                         dc.DrawFocusRect(&rtFocus);. X. v6 r  F. @: h4 t7 a
  36.                 }
    4 T" ^+ L8 a9 ]9 f. t
  37.                 return;' O2 ]0 b# ~1 M0 f& H
  38.         }2 N4 j7 P) A; L3 u, p
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
    7 K- {0 F% [# G- D4 _1 O3 c
  40. }
复制代码
$ _3 T4 D7 [) j( @
  别忘了标记Owner draw属性:
7 k, t8 z% ^/ V1 v1 \0 k
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。
  Y7 r8 N: o9 ]. ~6 ~$ v
* ^; _0 t. a1 R9 q% x. m" S  3.3.5 WM_MEASUREITEM
" f8 B5 H3 Q( M0 v  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
* D  ]6 f8 M+ d/ C- d0 o+ {$ M0 u% F  WM_DRAWITEM的映射函数原型如下:
2 t$ f/ L; B/ @9 B+ G- n  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
) S6 W  h8 e( @( Q3 j: C  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l8 R: ^$ `/ q# R- D
  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下: " s& r; J3 e5 g  ]# j

  1. ) s; ?, d$ g, C+ B- V+ o" C/ V3 t
  2. typedef struct tagMEASUREITEMSTRUCT5 ]: @! j: j+ U1 h6 q  ^% g
  3. {+ u$ N% ]. l2 _. v
  4.     UINT   CtlType;
    , \, T' O& @7 a7 F0 W0 @) ?$ f
  5.     UINT   CtlID;1 F: |0 H2 a" o! c- v) G, ]2 K
  6.     UINT   itemID;
    # t  B; C/ V$ i# @1 Y
  7.     UINT   itemWidth;/ x+ e/ y$ ~' }/ h5 R% k
  8.     UINT   itemHeight;
    5 y/ L& a0 H% Y4 i
  9.     DWORD  itemData;
    * T# u0 c- v  E$ A0 g$ l9 m
  10. } MEASUREITEMSTRUCT;
复制代码
" o; z4 v9 H& ~2 i
  CtlType指定了控件的类型,其取值如表6所示:
- o* B& c# c9 D类型值 含义- s2 b9 J- F6 o; c$ m% I9 |9 d& V
ODT_COMBOBOX 组合框控件
+ ^5 Y- a6 X/ I, w3 c* \: EODT_LISTBOX 列表框控件 4 G( c) a% [% p6 X" a# |: P
ODT_MENU 菜单项
: M0 N$ v2 Y9 O: Q; r1 f表6 CtlType的类型值与含义
; A( Z9 z, V5 m9 Q/ Z- k- |& C: y* ^  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
0 |4 ]+ ?) E+ h. r! d( ~1 v  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。 " d- {9 ]/ N" E; h, j3 E
  itemWidth 指定菜单项的宽度
7 a( ~3 @: I6 W  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255 8 N' |' K) L# F9 t- Z- H
  itemData
$ \( q/ i! n! a) p  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
2 w+ y" B7 h! `) j  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 : s; n, s, f) U
  图示出了OnMeasureItem的效果:
0 B7 B5 ~/ a3 e& E' V6 ?5 l) r3 _
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下: + s2 v8 D+ [% Z3 f2 e' a5 a# U3 @
  1. & y  z+ E: W- F2 P$ ]! |; z: s
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
    ; o5 ?& N. u8 X; [$ ]1 S6 `
  3. {
    # p$ N. Q8 M3 U( u: M+ x: z
  4.         if(nIDCtl == IDC_COLOR_PICKER)
    5 h* r7 u- ?% z6 g) ?; c- T
  5.         {
    7 M; Q5 X3 m$ H) m( N) ^4 W% e
  6.                 //设定高度为30
      n5 M* O! t+ `. v# C/ Y
  7.                 lpMeasureItemStruct->itemHeight = 30;) p3 J) ?: C/ F, f5 r5 M  R
  8.                 return;
    / S+ A% S: T  u% t: }6 i. n3 x" U
  9.         }( v( ?# b$ g* x$ b: q- ?9 s& t
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
    ( w+ w0 @* M4 B" c
  11. }
复制代码

- K& m2 _( ]) ^$ J  f; q  同样别忘了指定列表框的Owner draw属性:
9 ^! x" W, o% R& Z2 q; e# _3 V
  图11 指定下拉框的Owner draw属性  % }# y* P) z( l/ B# U( ^

  \# s2 v, {7 I+ x7 T  Y  3.3.6 NM_CUSTOMDRAW . @. e& r7 Y+ v/ M2 @1 a
  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。
; k: R. y1 n* K+ L  可以反射NM_CUSTOMDRAW消息,如:
+ x0 q; [, @* Q4 k  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw) 2 W, }; }* _7 c$ V+ x* V# C9 Q
  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); % D: f5 z- c; }" [
  参数:
# M1 ^2 F& J  d6 k! r  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:
' C" j7 f0 O& ~3 i. V

  1. 8 y2 f, j5 H6 m5 q5 d8 n) [1 k+ x* K
  2. typedef struct tagNMHDR6 u8 y8 A! r0 \( R
  3. {
    9 H5 _% v- O4 a! k8 K! @. m# z! m7 F
  4.      HWND hwndFrom;
    & s& s1 M: x; d& R0 j; R" l
  5.      UINT idFrom;
      u& m+ P6 |: ]
  6.      UINT code;
    1 l; {: J; {5 X  ]4 G
  7. } NMHDR;
复制代码
& D# m8 |/ V* K4 Y1 I
  其中: * A+ M0 O2 L3 R. X, m, l: T6 l; a. K
  hwndFrom 发送方控件的窗口句柄
! `; X) A; t% t7 `( j+ h) I  idFrom 发送方控件的ID code 通知代码   N( i8 W$ x5 x: z
  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下: ) x" N8 E; O+ d9 N7 ?& O+ o, [0 X

  1. 6 U. @, Z, y5 Y9 A! o8 w) H
  2. typedef struct tagNMCUSTOMDRAWINFO
    4 H. R& v8 L/ g9 ^; A& [2 Q8 L6 e
  3. {0 D1 N& {4 D- P1 T; Z
  4.     NMHDR  hdr;4 J4 z4 R$ X; i- s7 o
  5.     DWORD  dwDrawStage;% y) W+ J- H& x# V
  6.     HDC    hdc;( |- m; o, d# R8 q) Q
  7.     RECT   rc;
      j8 D5 v, K& h
  8.     DWORD  dwItemSpec;; k3 k; U* a% j/ o$ [
  9.     UINT   uItemState;
    : ~( ?+ o$ S- \9 j
  10.     LPARAM lItemlParam;* H* a) k) S) S0 o
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码

5 p) [: k1 S- U* X8 S/ O$ ~& \$ O   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:
% `' i" u: {5 H; j* X, i/ c: r类型值 含义
' L- ]8 O0 K  q7 O# s7 m7 \% pCDDS_POSTERASE 擦除循环结束 3 O& z! k( z  ^2 q: m
CDDS_POSTPAINT 绘制循环结束
6 ~5 U4 s* K- ~' PCDDS_PREERASE 准备开始擦除循环
' P: |! X4 Q3 J3 X* j, SCDDS_PREPAINT 准备开始绘制循环 + Q% K6 U' c3 c. y7 E
CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 ' r. i% i9 d5 k# k( b  x
CDDS_ITEMPOSTERASE 列表项擦除结束 $ C  N4 I& ^  ^6 X
CDDS_ITEMPOSTPAINT 列表项绘制结束
  U7 ?' t$ Z- q( {+ eCDDS_ITEMPREERASE 准备开始列表项擦除 9 y" s0 K& ]* }6 D
CDDS_ITEMPREPAINT 准备开始列表项绘制
2 Q) o. E7 l. V, u5 n" Q" g1 ZCDDS_SUBITEM 指定列表子项
, c/ V' z( N0 I4 v3 |1 ^2 D表7 dwDrawStage的类型值与含义
: a( I5 I4 Y1 G+ c  hdc指定了绘制操作所使用的设备环境。 ( m& G) P# x8 y! {6 l+ ~5 z
  rc指定了将被绘制的矩形区域。
2 g  y! [4 }( C* H  dwItemSpec 列表项的索引 * Y% c3 Z6 X) }; |
  uItemState 当前列表项的状态,其取值如表8所示:
2 [% Z; C3 b/ w$ B: d) i类型值 含义 ( `/ a7 J9 ^' n4 }7 d( j0 w. L! ~
CDIS_CHECKED 标记状态。
# p! }, x# O! ?# Y2 I2 W! O! I* UCDIS_DEFAULT 默认状态。 + D$ e# m( K' F: M5 D
CDIS_DISABLED 禁止状态。
/ ?! U4 q. r, [. j( QCDIS_FOCUS 焦点状态。 - g6 ^' H1 H# H% V* S
CDIS_GRAYED 灰化状态。
6 ^' t% `- u5 a2 Q5 v2 p  `: `CDIS_SELECTED 选中状态。
" j" k  ^4 X: ^1 O- E6 Q4 VCDIS_HOTLIGHT 热点状态。 ' y0 u9 L/ u+ t/ K4 g3 m
CDIS_INDETERMINATE 不定状态。
$ e; x. _$ a3 H) R$ t7 jCDIS_MARKED 标注状态。$ H( q& g+ i3 e9 p& q, X
表8 uItemState的类型值与含义
3 E! H+ e8 c4 K- |& L3 V  lItemlParam 当前列表项的绑定数据
( k8 \1 N- H( Z+ o  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: - k! S. `) Z8 t! b
  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:
! p4 d( ?  x$ w( u类型值 含义
0 i( T) ]* ~9 J3 O: }* `( YCDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。
8 G" k5 b6 j: ^$ S4 ICDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。 7 H. q3 N% g) s3 y1 x; r4 X
CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。 3 v! g  p2 P$ D
CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。
" j. i! ^) @  E4 R表9 pResult的类型值与含义(一)
! s" [" S5 `6 U  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:
# V& U/ \% g* X7 |% Z! e1 o. i类型值 含义 3 P& |  d6 m4 W3 w7 S( E
CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。 8 _- y: Z0 Z* H, u) {
CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
3 f/ ?& D9 b6 f+ U4 z. A* WCDRF_SKIPDEFAULT 系统不必再绘制该子项。6 b' ^& O& ?6 g$ q# {2 v' F
表10 pResult的类型值与含义(二): ?5 v) T# w  {/ a
  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子: * B  q8 Y5 a9 T5 r- M
  图12 利用NM_CUSTOMDRAW消息美化界面 $ j5 n# R. N  X- ]  b5 y1 j0 U
  对应代码如下:
$ d' D/ _# y' G. {7 d0 I* R; X
  1. % e8 D# M( @# X4 Z6 e/ b8 [
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
    2 C0 p, d& w8 @5 h- j) d
  3. {2 O8 L2 w7 p! F
  4.         //类型安全转换
    7 M" K% `- `1 v4 H6 e! T2 `( P3 `
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);
    $ `: j* I% ?) S7 G( Z* [: T
  6.         *pResult = 0;# A. |  d' {8 N, y. I
  7.         ) S- l3 F. q. Y6 k
  8.         //指定列表项绘制前后发送消息% K$ e' R% K% H' S, e
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)
    ; }: m/ M( j! M8 Z6 ?
  10.         {( f. d# [, U3 R
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;6 a8 r# L7 d* k( x" _& Q5 U8 T
  12.         }
    3 ^8 E# j4 k9 K0 @2 Q0 ~8 ?: }. ?6 o
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)
    ) E1 R6 |6 h) I) L9 x; }
  14.         {
    . _2 N- E. n3 S/ p% }. W
  15.                 //奇数行
    9 {) w7 |# w/ J  Q$ H) z
  16.                 if(pLVCD->nmcd.dwItemSpec % 2)
    6 B% [7 t7 d. i4 Y. r9 u
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);. y, T/ L5 Z- Y5 w9 _
  18.                 //偶数行
    4 v! @3 M# k2 n8 k) C) M
  19.                 else
    3 M+ f. w( k1 C& K- C3 l' E
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);
    " J7 f, m; j+ C$ g. h( m
  21.                 //继续
    ( |% V' S' f/ d. p0 K3 r3 o/ m" a
  22.                 *pResult = CDRF_DODEFAULT;5 v1 e2 {: d3 r! j& _! ~, P
  23.         }( w7 {6 D+ m% I! |. l5 _
  24. }
复制代码
; D% Y& A3 d$ X! w
  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。
( H0 k1 V& t& M( w1 q* E! }# e) q# ^, p/ s: t+ ?) `( I3 @* a
  3.4 使用MFC类的虚函数机制
2 v) G/ Q$ g% h2 [6 w) @  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子: 9 a4 U. \' N8 L8 v

  1. $ v5 z, j, X1 W" R+ A! N
  2. void CView::OnPaint(). x  E% q1 o: T0 N
  3. {) x" m. q1 S! A7 [/ q( H* p3 K* y. X
  4.         // standard paint routine
    $ o; ]) J: J# Q  D1 Y
  5.         CPaintDC dc(this);0 d" k6 A/ A8 }* [3 J
  6.         OnPrepareDC(&dc);' @: @5 o* e; G! Z3 M9 F
  7.         OnDraw(&dc);2 Q- n+ F, X  `2 W' s7 K" e
  8. }
复制代码

' Z0 T: c& @8 n. A, }  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。 " ?6 c" n) Q( [1 i; \- E4 q
  以下列出了与界面美化相关的虚函数,参数说明略去: - q9 h! I6 c: I% Q
CButton::DrawItem % D& n: z4 x) L* p, [# _
CCheckListBox::DrawItem $ E/ y; m0 r& D3 i2 z0 u# |
CComboBox::DrawItem 6 F5 G9 g3 K' f+ d9 R: M
CHeaderCtrl::DrawItem
7 u0 X" ?+ Z. w# S0 m4 r3 R. DCListBox::DrawItem 9 w0 i9 J! P) ~7 _4 a/ Z. d
CMenu::DrawItem
, T! _4 Z  \( \6 j" W# n9 X, m4 `6 mCStatusBar::DrawItem
- f6 t; G  X& G7 e/ B4 d2 kCStatusBarCtrl::DrawItem 6 k0 g; V# @2 V  ^- X, u
CTabCtrl::DrawItem
4 R' x2 p: G$ g4 ?* e, w8 m, Tvirtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct ); - a4 _7 O& X& R. O" s. D7 Y
Owner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。
* c( X- ~* n3 P  _& A/ I  限于篇幅,在此不再附以例程。
, F' a: p, T) Q' R: D5 V& J2 K2 p' `' l0 W7 f& z
参考文献6 Q. D6 x5 t! S+ w# T+ R: A
本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-6-18 10:10 , Processed in 0.023824 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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