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

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇
1 a" @/ b) u" l  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。
" X- E, L6 h) W4 Y& H: v. |" x, Q" g  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。
0 \* _5 J; y. j9 I2 B$ T% a( K: \# D) R5 U
  2. 美化界面之基础篇: C! ^8 `- Q& s: s
  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……$ E5 v; M) C7 h, y: v! d+ q
* h0 h0 M' o8 ~) v5 z: E7 F9 [+ w$ u
  2.1 Windows下的绘图操作
& i2 G! Q# x3 g/ C8 i  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……% [" K9 c& P+ I3 [- H; M
  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。, M; [7 ?4 h% h& s3 t# j; `

% f: A! M: m) ~, V; s3 J' K  2.1.1 设备环境类; }1 b- U# O- n1 o# v; ^, r
  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。
4 ]5 ]5 p* m4 r  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:) t& h* P5 W4 I4 ^$ i! ]& d
  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式
* e* t. g- {% f( E6 y  Mapping Functions:映射操作
; d( H+ w+ v( z1 L  Coordinate Functions:坐标操作, ?: `* o8 G# K8 ~& u& _- d
  Clipping Functions:剪切操作# |& I0 L9 a  _! h
  Line-Output Functions:画线操作, J- F. h0 ~4 e  q8 D/ @
  Simple Drawing Functions:简单绘图操作,如:绘制矩形框8 P8 i* F8 }; W; O2 b) W
  Ellipse and Polygon Functions:椭圆/多边形操作
: Q* N/ u6 E4 c$ z5 k) a3 ~  Text Functions:文字输出操作$ ~8 R" r2 n+ C3 o4 H
  Printer Escape Functions:打印操作
' y( Q7 E+ K6 \. ^  Scrolling Functions:滚动操作+ H! X& i, b( D7 g/ J/ `. s) E
  *Bitmap Functions:位图操作; |6 ~- l  c. C/ P
  *Region Functions:区域操作
8 F: M0 A# \7 c; b( U5 n3 ?  *Font Functions:字体操作
: O* B" ^, t' M% y0 k& M  *Color and Color Palette Functions:颜色/调色板操作6 L& t4 H, G0 P7 Y, t0 f
  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。/ d: G% L& B  [
' n/ z6 d8 i6 B8 W5 S% l
  2.1.2 图形对象类4 n+ b8 r5 ?1 k5 R3 }2 g
  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。. i- T& W# S% u
  下面的表格列出了MFC的图形对象类:
) W! B3 F0 u9 \  C  M  MFC类 图形对象句柄 图形对象目的
! E% S1 D* w1 V# g* g4 n; t  CBitmap HBITMAP 内存中的位图
4 c1 p4 ~& S+ q5 Q1 w  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式/ V8 p( i% O! y( X/ @
  CFont HFONT 字体特性—写文本时所使用的字体' k- Y! }! F' @) A
  CPalette HPALETTE 调色板颜色
$ p8 \  I" l" |  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细, W. @. Q8 I  [) v" Q
  CRgn HRGN 区域特性—包括定义它的点
( u/ w4 h1 H  a5 y7 L. H1 n  表1 图形对象类和它们封装的句柄
7 k& f! p% l( M7 ~
* U2 s3 u+ I$ V% @, P  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面: # P) m( o6 y: x# R4 E
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:
  C2 R+ b' z* S' l8 C) m, L
  1. 8 M9 e- [: K& w( ~
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs): s% V# _2 J1 [% o3 [+ H
  3. {1 y! s3 U; Q" i. h
  4.         //设置背景色& c; ?) S' S% S& v3 L5 n* [
  5.         //CBrush CUi1View::m_Back
    + o7 i( \5 e9 @
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));
    9 y" w6 f' Q! O" `* O! {; C2 F
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL);
    1 T3 B% n# v+ }
  8.        return CView::PreCreateWindow(cs);
    6 O* G5 Q5 |7 q% B6 g
  9. }
    # T3 F6 N+ a; t+ c! f
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
    ! ]' Y& T- m! N' @" I1 c6 j( {, g
  11. {
    ( N- ]6 Y( w  i. d1 M
  12.         if (CView::OnCreate(lpCreateStruct) == -1)+ U! X6 I& H- y; ?
  13.                 return -1;
    , p* P6 k, _* }5 t  V) x& ]
  14.         //创建字体& [7 q+ r/ p% A& y6 j, F
  15.         //CFont CUi1View::m_Font
    5 }4 p0 Z8 P7 M0 U9 U1 V
  16.         m_Font.CreatePointFont(120, "Impact"); 8 Y/ N* X' t! a, m. M  B" A8 R' w
  17.                return 0;5 t+ \# O% l" o# S4 h% z
  18. }& H0 g4 y+ M: t. J6 y
  19. void CUi1View::OnDraw(CDC* pDC)- ~+ e" L$ k/ [  _4 O. A
  20. {1 s! }# W  H8 P7 [" D
  21.         //绘制按钮框架
    - C4 w+ z1 I( C3 g
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);  v: ?& ?& U! M1 x
  23.         //输出文字( i4 O# N. R* S6 m4 i6 x  @
  24.         pDC->SetBkMode(TRANSPARENT);
    ) P! ?" V3 C. p
  25.         pDC->TextOut(120, 120, "Hello, CFan!");
    0 ]# D9 M9 D0 f" g, W4 k
  26. }
复制代码

2 H, Q& _8 d  x( Q  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。 0 }- I0 x0 C5 U) N, ~  H; d, \

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

, ^6 U1 n5 ]% F* i  A  3. 美化界面之实现篇
0 N, c0 I- s- H# l  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。: }' _8 X3 `/ l. a+ t: g  m
" p9 B$ R. F% Z
  3.1 美化界面的途径( J" _' d: h7 P$ v. D8 f
  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:: U/ [4 i, \+ j3 X
  1. 使用MFC类的既有函数,设定界面属性;: c/ |2 f' ^' {& u& [: w
  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;
! Z( L8 v- ]5 I4 O  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;
. j. c# w- I* `. ]- d# \8 P' \  一般来说,应用程序可以通过以下两种途径来实现以上的方法:
4 M, _- Z) f# i5 d0 N; o6 O  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;& h0 h& q; j! c! D9 Z8 `, @
  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。/ v% @# S" X5 T8 G
  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:& Q0 f3 I+ R9 w2 W  g  T" j8 `9 B4 _
  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示:
9 W5 A7 N  Y% M8 @# d# m1 P# b# s
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;
2 J. V% M$ N/ R/ E2 v  以下的章节将综合地使用以上的方法,请读者朋友留心观察。5 b0 _3 ?" G6 D6 g0 B. ^* W

- e- G; V( w; j8 ]% f5 u/ Y. d  3.2 使用MFC类的既有函数
# S/ g) t4 b) U+ I  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。
8 ^6 E' m" S4 ~
+ P: I/ m' ]" L) T1 _  {! B- dCWinApp::SetDialogBkColor. u+ k7 [% x+ o
void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );
% g/ I9 f% c" R9 ~) c1 y. f  P* U指定对话框的背景色和文本颜色。
' g4 M3 F( C, x4 E3 }* s
0 B/ g8 Y, [% ]+ m3 e, zCListCtrl::SetBkColor" Z# T; P9 I" ?; m  m2 D  {7 V
CReBarCtrl::SetBkColor
! a7 X# a- ^& a1 c* o; K* iCStatusBarCtrl::SetBkColor
  E7 l% m! d$ J8 g' Z4 S7 uCTreeCtrl::SetBkColor
2 H+ a+ j$ t8 e$ A% G- ]0 A2 tCOLORREF SetBkColor( COLORREF clr );8 h, i1 E6 z7 I& \+ O+ L$ @' T6 a
设定背景色。
1 }  A3 v5 d. S
0 S& ~% U, V/ m8 o6 p3 D- uCListCtrl::SetTextColor
( f% m' m) I9 x; XCReBarCtrl::SetTextColor# {5 c2 H! U: k1 ?3 p
CTreeCtrl::SetTextColor8 J7 R" K% ]3 N  T" [
COLORREF SetTextColor( COLORREF clr );
% F3 K) V( I5 w+ G8 [- \设定文本颜色。
: K  [" O7 S# k: j3 ^2 m6 W) `0 e8 |
CListCtrl::SetBkImage
5 M6 {" D4 i9 Y$ U% kBOOL SetBkImage( LVBKIMAGE* plvbkImage );6 q" w3 [* `# V  _: p$ @" k' K- r
BOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);0 W4 P# |- Y9 b/ N4 D  z+ g
BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );
( l: |' O9 U. l, r2 F1 E# g5 G设定列表控件的背景图片。
; d7 e3 b- F$ _: a/ V. [
6 A" f$ z0 B& B) E: T$ dCComboBoxEx::SetExtendedStyle4 E4 S! [5 d% Z) M
CListCtrl::SetExtendedStyle, }  e! N; T9 u) F& X) i2 w
CTabCtrl::SetExtendedStyle
* M( v6 y2 n7 \9 b6 I! {CToolBarCtrl::SetExtendedStyle
# m/ X7 z% r7 ]4 FDWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles );
1 V, i7 G. ~4 ?* `% u2 z设置控件的扩展属性,例如:设置列表控件属性带有表格线。
2 z# [. O. v( n5 U( s1 x; U  图4是个简单应用MFC类的既有函数来改善Windows界面的例子:
0 ~$ b9 m. X- w+ ]
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:+ X+ N- @: u8 I' v

  1. # f! o( b5 x8 n. |6 F/ T, Q
  2. BOOL CUi2App::InitInstance()
    - k$ |) w6 p( a" }- j: k
  3. {
    ' t8 n0 k7 p: @! {* U; m7 b
  4.         //…* ]3 v0 w  y+ z. r; E
  5.         //设置对话框背景色和字体颜色% j4 X6 t  I. ?+ A
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
    5 G  |) O/ \. B/ O) p& k
  7.          //…
    ' A( v& z2 D  {4 j; y
  8. }
    1 H6 k" _2 Q% t; P" {
  9. BOOL CUi2Dlg::OnInitDialog()/ M1 I; n  y- b, `. u5 N8 A
  10. {
    : Q9 T2 |" c2 p& t3 b3 Y
  11.         //…2 E2 i/ `# v# G. ~: z
  12.         //设置列表控件属性带有表格线
    + m* e" w/ M0 Q  J% U1 X" \, D
  13.         DWORD NewStyle = m_List.GetExtendedStyle();! b! n* Q. m9 E4 ~/ w5 Y2 u( m
  14.     NewStyle |= LVS_EX_GRIDLINES;8 ^6 e* m( n; [) D8 s
  15. m_List.SetExtendedStyle(NewStyle);
    # f9 O: k# x1 r0 T1 b7 |  l
  16.         //设置列表控件字体颜色为红色
    % L. i4 p" b, r* D( n
  17.         m_List.SetTextColor(RGB(255, 0, 0));
    ( p& P3 U; y9 c/ q/ v
  18.         //填充数据! O; P, f: t$ g" g  |( N8 n6 A5 Q# O
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);
    & D, B/ e  `: ?: P& K
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);
    # g  p$ J* S- r* r$ D; g! {# v3 h
  21.         m_List.InsertItem(0, "5854165");- b7 y+ ~0 S* _7 S6 x8 t
  22.         m_List.SetItemText(0, 1, "白乔");
    " T' Z) T' b% M) e2 i+ ]) u
  23.         m_List.InsertItem(1, "6823864");
    - L8 ?6 y5 g! y2 Z! e* O7 n* O- U
  24.         m_List.SetItemText(1, 1, "Satan");1 Q8 C" p" z, i2 v6 ^
  25.         //…0 v' Q6 Z, T! }8 F5 Z) a/ x8 d
  26. }
复制代码
8 }' u" J) A8 v
  嗯,这样的界面还算不错吧?
, c1 \$ W" w$ ]0 R7 ^$ w* Q8 Q9 m0 r* b+ d: u
  3.3 使用Windows的消息机制* X7 U9 E+ @9 u, @
  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息:
) N" F% }% {2 \8 nWM_PAINT
2 i6 k0 S+ ]; ]9 _0 ^WM_ERASEBKGND 5 u, E4 w: M' N$ Y9 ?- U
WM_CTLCOLOR*
9 W# x0 M* }, Y( HWM_DRAWITEM*
; A4 P, e; V5 x, |' a, R6 }WM_MEASUREITEM*
( w  X: x) k3 l, L8 U6 i2 N2 YNM_CUSTOMDRAW*
# N; z" l1 x! C+ U  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。, x$ y2 f/ ~* N% b3 u4 b+ |3 z

: {5 e9 W5 H1 I9 `  3.3.1 WM_PAINT8 \! w! z/ c3 ?" Z" V! k7 ~
  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。
9 ?7 q9 @8 T; A2 _- F/ A  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下: 0 U4 {. j* d' ?% w/ K- V/ G+ l
  afx_msg void OnPaint();   E: `9 \+ v4 s8 y7 R% l
  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: 9 c* j- I% @# h" M( K# N- z- I
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单: ( z/ `: t3 s/ C# U

  1. 4 n9 H1 c6 i' o+ n5 p8 B
  2. void CLazyStatic::OnPaint()
    $ W# z6 w- y" i1 [5 l
  3. {
    2 R0 L1 @4 h8 u5 y. y9 l" N: ]
  4.         CPaintDC dc(this); // device context for painting
    ' b) J3 ^. _( d, s3 Q
  5.          , v$ ~& {# J+ a0 t
  6.        //什么都不输出,仅仅画一个矩形框1 ?0 E8 X6 L, c# d7 h" v. a
  7.         CRect rc;
    5 L3 F3 P, F6 y. F4 o
  8.         GetClientRect(&rc);; `% L' n2 P3 a5 T% ?2 j
  9.         dc.Rectangle(rc);
      Q2 y7 q2 l* n* f1 h- H) k6 E! j
  10. }
复制代码
& Z& j2 J! C% S5 @  i  Z7 G6 S
  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。& S9 c4 g1 i: ]! D8 l& O0 f5 A
$ f+ p4 C0 t: s- }* X
  3.3.2 WM_ERASEBKGND 2 u$ b" U2 @, y/ r
  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。 ' k0 e; p% v/ T6 K
  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:
- `9 Q3 X$ |/ q3 c: l  afx_msg BOOL OnEraseBkgnd( CDC* pDC );
6 \- Y& j+ r3 _" U) V5 v  返回值: # m2 F' ?3 c: d" W! S) W
  指定背景是否已清除,如果为FALSE,系统将自动清除
" S3 \1 f! e: ]3 U: C  参数: pDC指定了绘制操作所使用的设备环境。 , g- D9 _0 C# {( r7 [/ w6 q3 ~
  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景: ! G- Z) ?1 c2 W" T8 p' i* D
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单:
. \0 S3 w1 g- p

  1. ' w% S. [. E% d* _5 r, k
  2. BOOL CUi4Dlg::OnInitDialog()
    * S5 }# o5 \$ ^* [) W, s
  3. {; |1 M  N8 l; c: h: k
  4. //…, y6 e; Z$ \; p) f+ y" K# \* q
  5.         //加载位图# b6 ?1 T. [. K  n: C
  6.         //CBitmap m_Back;
    6 G9 Q$ o# O. `6 B
  7.         m_Back.LoadBitmap(IDB_BACK);
    - R/ g% }8 C& O7 L9 P
  8.         //…+ C1 D( L) N$ Q1 A8 J1 _
  9. }2 T3 {# S2 f  N$ |5 O/ M
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC) 2 J+ R" J! `( e* n) {
  11. {+ S  }5 u; ^# Q
  12.         CDC dc;: l/ D, ~' _/ M* r+ Y( z; \
  13.         dc.CreateCompatibleDC(pDC);
    6 O) W* y$ @" l. W
  14.         dc.SelectObject(&m_Back);3 q5 \  e/ ?; {! p5 ?* _
  15.         //获取BITMAP对象
    $ c( P9 h' `' B9 F: n* D. t
  16.         BITMAP hb;
    1 ?4 w: U. j( h4 r  y& Y0 w
  17.         m_Back.GetBitmap(&hb);4 A- P4 C6 M% v8 t
  18.         //获取窗口大小
    : x( ]: t! m! R! g' `* P. o
  19.         CRect rt;
    6 c4 Q0 J# E7 |3 w
  20.         GetClientRect(&rt);; Q! e+ v/ s) m8 X% `9 j; Z
  21.         //显示位图2 G- g# h; h& o* ?% E! a
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),# H- j8 a+ H' ]" H( ~* W/ N
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);
    : w$ W; i+ v! e/ O- D
  24.         return TRUE;
    0 n! M, {* V0 b1 o# D- {
  25. }
    $ O1 m; c' ~) ^2 `( j
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 1 m+ G$ |0 b2 x9 V
  27. {
    - U3 Q7 X. T6 k8 u% q
  28.         //设置透明背景模式% V. h5 Y) y; ?9 e; [
  29.         pDC->SetBkMode(TRANSPARENT);
    % Z. g6 T0 O' ]. d3 Z/ W
  30.         //设置背景刷子为空( k5 F( c8 x0 V$ E! S# a) N8 |5 b
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);! V) S+ Y( ]- L4 Z' V5 q& Y# _
  32. }
复制代码
! C8 I8 g+ K3 |' {) v" p( O7 O. j
  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。
: u+ d. M) _+ y" k
' W8 l" ?; M! z/ ]  3.3.3 WM_CTLCOLOR . T6 T# B7 O1 }" Q4 Q
  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。
5 L: G$ n3 ^' {! V; ^  WM_CTLCOLOR的映射函数原型如下:
, \5 H( ?# v2 G4 C" a  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );# m5 U. V! i# c/ c
  返回值: 用以指定背景的刷子
$ A0 P6 a0 u" ~5 r1 a3 o( T  参数:
" a4 ?) L9 u0 n7 o/ ~+ X  pDC指定了绘制操作所使用的设备环境。 : G. h8 j/ l( Q" L! P
  pWnd 控件指针
& }0 w. _( _2 u  w# `  nCtlColor 指定控件类型,其取值如表2所示:
% j) m2 k, T$ @" \( ?" I  类型值 含义 # ^$ s( g8 H2 U
CTLCOLOR_BTN 按钮控件 ( m; T3 I3 V4 ^3 X! Q! j
CTLCOLOR_DLG 对话框
. u+ e) n! @- Z% c/ y* x" |CTLCOLOR_EDIT  编辑控件 5 `2 S% X) I! }& i- l$ k
CTLCOLOR_LISTBOX  列表框   L2 O1 O8 a/ Z9 k+ J
CTLCOLOR_MSGBOX  消息框 ! _$ E& L) n( q( T7 L5 K4 h( p1 I
CTLCOLOR_SCROLLBAR 滚动条
6 i! R6 Q: n( a5 S* s: s4 ~+ `CTLCOLOR_STATIC 静态控件 1 U2 i' a" O6 S! l+ k+ C8 p( r& B
表2 nCtlColor的类型值与含义% {5 w" l/ x! P$ s

, s. Q& C- X% l9 j* q  作为一个简单的例子,观察以下的代码: - l5 C" K& T5 I9 t) x6 Q% l( Z- w
  1. # k- `' w; O) r
  2. BOOL CUi5Dlg::OnInitDialog()
    ; p# w' o2 c' l! }
  3. {2 V7 H' E, ]9 C& R. V
  4.         //…
    % C5 v1 h0 V- X( E6 }
  5.         //创建字体
      |) x+ ]- P$ Y& y. m
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font2
    $ Q3 Z) T9 V( r+ x/ u  {/ P/ u
  7.         m_Font1.CreatePointFont(120, "Impact");" B: D& S; \* Q* K" A. A0 m6 T
  8.         m_Font3.CreatePointFont(120, "Arial");
    : B8 I. ?1 J4 ]' b  \# B' C
  9.                 return TRUE;$ v: O3 M4 J, x* h4 P! c! J
  10.   // return TRUE  unless you set the focus to a control
    - c) r3 r1 U6 s7 `
  11. }1 }; n& ]: b9 U6 g/ s1 F4 S. m# b
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    3 G8 P* p7 c3 v5 p- K! Z
  13. {) b/ T3 k1 X8 G, c& u
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
    " o+ d0 g1 g# [
  15.         if(nCtlColor == CTLCOLOR_STATIC)3 T- X, n0 m, ^9 s# I
  16.         {
    1 d& [1 v9 m' v$ \) e% P
  17.                 //区分静态控件6 O2 E7 i! K; G
  18.                 switch(pWnd->GetDlgCtrlID())
    : X- c1 A$ r% G: n9 ]
  19.                 {) W4 H- N9 P2 b4 I! r' C: S& A
  20.                         case IDC_STATIC1:  U% t! Q' u7 E3 q+ k) d
  21.                         {
    & H3 u. w+ |" F
  22.                                 pDC->SelectObject(&m_Font1);4 }: y' F; [+ I; s% E& q1 B8 t
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));
      {( r- F* K: n1 B
  24.                                 break;
    - o1 m# G5 o& B: S$ Z
  25.                         }
    ) Y' [; f  R$ E0 u) ~# o" N
  26.                         case IDC_STATIC2:
    8 }5 N( [+ s" R# c% v7 x) G( m. X
  27.                         {
    6 o/ [) L. ]2 n3 e6 f
  28.                                 pDC->SelectObject(&m_Font2);2 b8 G4 `0 c9 o/ H8 S
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));6 E- Y3 @3 o5 p9 U0 _* ~
  30.                                 break;$ B0 d3 H/ e% Y4 b- @
  31.                         }
    5 j6 W4 S2 Y; L! }8 P
  32.                 }
    / n; K/ K( o; _! B
  33.         }+ @6 O. w( S4 O; ]
  34.         return hbr;& t5 R8 S/ W0 x; @9 Y! b
  35. }
复制代码

( m2 D6 _, J0 x4 w3 W2 W# }   生成的界面如下:
+ \/ C+ M$ m( g5 h8 v5 n1 P5 V9 K
图7 利用WM_CTLCOLOR消息美化界面

6 }$ K9 E( g1 j: B+ b6 `3 i/ h  3.3.4 WM_DRAWITEM
3 {" k0 w* m. c5 ]0 w- A, v) `  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。3 G+ S) V( m$ T8 }
  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。
( a3 \5 {- m' L$ p) Q  WM_DRAWITEM的映射函数原型如下: / D! O6 I4 Y8 ], L( `' I  ~) S
  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );
# u8 s9 k$ C8 [. Z! g  参数:   [. Q/ E3 u/ D! e( W5 w
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 - N& _5 h& P1 [
  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下:
1 r, h; z0 H& V9 c8 w7 z4 m

  1. 6 l0 D2 G3 S2 D+ S$ _8 B
  2. typedef struct tagDRAWITEMSTRUCT7 C4 X! F! S: Y; ?* u
  3. {
    " S, Q" @7 g$ R* {2 W' h! L
  4.     UINT   CtlType;
    / q$ ]8 I6 D: X, J
  5.      UINT   CtlID;
    - |) s8 k  t& X
  6.      UINT   itemID;
    6 }; D. R& `3 q; f. U
  7.     UINT   itemAction;# ?/ {0 `8 u- Y0 G* v& T
  8.     UINT   itemState;
    % c: o9 J# N* o, h3 i3 J  p
  9.     HWND   hwndItem;
    6 ?' a/ w6 q! q) F" f6 Y
  10.     HDC    hDC;
    ; U* F' J: K3 Q6 x& L/ c0 B
  11.     RECT   rcItem;' H* p+ O, T# x, u0 G" o9 ~: L( Z  i
  12.     DWORD  itemData;
    : V/ x9 T, V8 P; j/ @* x8 p
  13. }DRAWITEMSTRUCT;
复制代码
3 a( e( u" J; G5 t9 v
CtlType指定了控件的类型,其取值如表3所示:
8 l& @5 h% M! r; P  U. V0 ~; F类型值 含义
: m) H4 T( U  K0 q7 K8 mODT_BUTTON 按钮控件   g) u* Z& I+ H* ^$ z2 ], I, r
ODT_COMBOBOX 组合框控件
0 x$ d; A! J: z$ }! r1 T4 X  GODT_LISTBOX 列表框控件 . j& ~$ S  X( H+ A
ODT_LISTVIEW 列表视图
7 G5 _4 p2 Q4 J, p* PODT_MENU 菜单项
8 v6 v; O8 `6 i9 ^ODT_STATIC 静态文本控件
1 K) B' m' ?' }4 A+ T: CODT_TAB Tab控件 . `# `1 b- g3 c  L
表3 CtlType的类型值与含义
/ @4 j3 P8 |- K- b! w7 u, ?( r
  U9 B' E+ K2 W2 o0 r4 ?; N, p  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
6 P& t! k- w# k! J! [  u4 @+ L, s  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。 - X( h) O- H3 |# H& D' `
  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:: S1 e9 S: w8 W5 N& w% S, Y2 q. t
类型值 含义
' e" d  u8 c$ r& m2 N! J: ~8 w9 SODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。
' H, w  j: D) F  G$ X% pODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。 / e' i5 x$ a& @3 f+ `
ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。
1 a, {7 \, |7 v: C" T1 X" B表4 itemAction的类型值与含义6 [4 n# S) `/ ^: ~
  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:" L! T$ W1 t) k
类型值 含义 9 s) a1 Y9 [! K/ W( t/ q
ODS_CHECKED 标记状态,仅适用于菜单项。 5 K6 Y! k3 w' {) j6 ?* O9 D
ODS_DEFAULT 默认状态。 / P  I( ?3 `. w. n" g9 J
ODS_DISABLED 禁止状态。
% X8 J3 Q0 v8 z8 s( z: F1 lODS_FOCUS 焦点状态。
; T& s& i* O0 ZODS_GRAYED 灰化状态,仅适用于菜单项。 9 E$ }  q" A9 f& [9 I
ODS_SELECTED 选中状态。 7 [) `. h; b1 q; g- @+ l
ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。
; Q/ ?6 T6 |4 V- E0 aODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 7 J& G' ~1 h3 y
ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。
  w+ y; M  Q( X, Y9 G: eODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。
9 d+ c) g7 S, `% u; rODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。 * \, z$ M# d$ i  d( N
表5 itemState的类型值与含义
! M9 [& F- d$ M" q7 y2 X! x  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。 . S. u3 u; Z3 u4 ~5 x3 Z
  hDC 指定了绘制操作所使用的设备环境。 4 }$ m0 y  p& k* S
  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 , ~' w) a8 {# {& p$ B. S1 d% U
  itemData 3 G. e; x4 T0 r
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 0 \9 @5 c. ]2 P" G: R9 ~4 G9 X: Z1 c
  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
0 s) I9 [; O% `% B  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。
( x% W8 E( x- P& @4 ]  图5是个相应的例子,它修改了按钮的界面: 8 F7 _0 N  A3 b1 K
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下: " b* J+ _5 u* F0 ~% b5 ^

  1. - {% L$ X) q/ I9 F
  2. BOOL CUi6Dlg::OnInitDialog()
    + f3 t0 u/ m: w* r9 L; o: C
  3. {
    ; Y) n% e' B9 x1 Y
  4.         //…
    2 k2 F$ R  A6 F, g
  5.         //创建字体: t# |2 D4 k, r5 L* ?. f
  6.         //CFont CUi1View::m_Font
    0 _: m/ C! o2 `
  7.         m_Font.CreatePointFont(120, "Impact");: h& `2 R: d5 T
  8.         //…
    0 `  i! H/ {5 n% h9 j( j
  9. }
    ' V2 r. l) j' d) O, X& i9 O! }
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
    & M$ g7 b) [5 s" x6 e% S
  11. {5 D' h7 I' c: R% x3 g' o
  12.         if(nIDCtl == IDC_HELLO_CFAN)
    " J3 }# B; N. u3 r% H
  13.         {# ^# X7 b/ m8 \1 Q* N$ m* u
  14.                 //绘制按钮框架
    7 M6 V/ G2 a% W& m& r) ^) v" m
  15.                 UINT uStyle = DFCS_BUTTONPUSH;; f' a7 H( B9 o6 B- C$ p
  16.                 //是否按下去了?1 q8 {, q# S' E; x" U0 p4 z- h; f, [9 y
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)
    1 x* K* J% e& q
  18.                         uStyle |= DFCS_PUSHED;
    7 N2 T* `& G) e% c) z( i
  19.                 CDC dc;
    9 |2 @. N) F' g) J
  20.                 dc.Attach(lpDrawItemStruct->hDC);
    ! h: {; c# z# r. R' z8 G2 N2 o1 J
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);3 Q' i) Q$ y* k7 d. v- ?1 F
  22.                 //输出文字/ u1 l9 }& S: F/ l" P2 l( {) F
  23.                 dc.SelectObject(&m_Font);; ?( r! B3 T4 _4 `' B
  24.                 dc.SetTextColor(RGB(0, 0, 255));
    % h4 [6 n$ e$ D+ ~& t& _
  25.                 dc.SetBkMode(TRANSPARENT);
    ! m6 _; o+ C- ?+ p, V/ x1 d! H- V
  26.                 CString sText;; _" ?' B2 k% Y. y+ W, \2 Y+ l
  27.                 m_HelloCFan.GetWindowText(sText);
    ; {- y2 r$ {2 q; V+ s/ S$ K& \
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);# O% H% P/ u7 x7 R& j( y
  29.                 //是否得到焦点3 c2 V5 L- |  F
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)
    3 V' Z6 h& G0 K0 j' M- [9 ~: @* |
  31.                 {8 |& `5 Q, ^- y6 L. ]
  32.                         //画虚框
    5 d& S& e3 t: N" a6 |) \
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem;
    & b7 b0 `* x+ s: B( n
  34.                        rtFocus.DeflateRect(3, 3);
    6 m, `; D; I5 w# z5 v
  35.                         dc.DrawFocusRect(&rtFocus);5 e' g' ^0 q4 ^
  36.                 }
    ) j9 p3 t3 y/ l' i9 I; j' Z( N( K
  37.                 return;
    ) s8 I' L; H/ J
  38.         }  @* o3 ?* \9 g+ C3 H
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
    / w* V$ [* J  U# H; Z+ q
  40. }
复制代码
( [7 l$ |( o( Z. Z3 Y
  别忘了标记Owner draw属性:
! K" m8 t2 t' w3 A+ r# Z
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。
/ G+ I3 r0 ~5 `% c: H/ D4 T4 F; p$ e. Y  H% [
  3.3.5 WM_MEASUREITEM % }# E1 S7 Z, q, @8 Q$ T* c, Y
  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
$ \* d, F8 ?, p3 z  WM_DRAWITEM的映射函数原型如下:
6 n4 Z7 [. S- J6 t" w- _. y  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
0 a! m. }+ I2 H& E0 v$ l4 k  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l- g( H7 j# ]7 V# ?
  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下:
) ~8 g0 {. t5 D: q, P, y% G$ S0 w

  1. ! f: u$ K4 F+ s" C5 P" G: \$ w
  2. typedef struct tagMEASUREITEMSTRUCT9 b9 t& a1 I8 ?4 d- C# I6 z
  3. {
    # f- V7 A7 H% k+ N  U$ m4 z
  4.     UINT   CtlType;: N+ X. w; H7 O* i& X
  5.     UINT   CtlID;
    & q1 D4 w0 x4 y, X. V# D# i- ~' g# J
  6.     UINT   itemID;) ]$ ^( s* }9 }0 k1 t3 i
  7.     UINT   itemWidth;& P! _8 w# m# U7 B
  8.     UINT   itemHeight;7 F  s2 T% A! X
  9.     DWORD  itemData;
    - G/ U- E! F, o$ a
  10. } MEASUREITEMSTRUCT;
复制代码
8 r7 |: M& N3 t! o# q$ Q
  CtlType指定了控件的类型,其取值如表6所示:, V7 w( P# A) x. ]0 a4 ^
类型值 含义9 k% ?/ T5 ?5 e2 ^8 y
ODT_COMBOBOX 组合框控件 8 c( T% x6 V* H8 T; }1 p
ODT_LISTBOX 列表框控件
3 Z% Q1 N" h/ H) }' S; e2 q) ^ODT_MENU 菜单项 ) b6 R* [0 N* H. c
表6 CtlType的类型值与含义
6 p/ j# P  Y! a. q0 s  U  CtlID 指定自绘控件的ID值,该成员不适用于菜单项 ! i- o9 M" h1 v( S0 O8 m* Q" C
  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。 2 Y+ j( n# e6 I: N; @: Y
  itemWidth 指定菜单项的宽度
& y5 y7 f& t; u  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255 0 R% ?  x1 {" t
  itemData
# P" z* J# ^! i: f) O- G  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
, L; F& b; f% l  ~' X( e* d  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 * P* x) @: P1 c- F. z2 m& p
  图示出了OnMeasureItem的效果: 5 b- G. J' ], w9 d; y: Q! r
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下:
. y8 e3 p. H) q( U$ J, C# r# V
  1. & \) _+ B8 w6 o! H' h2 U% B
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) , U- l6 |9 q8 B9 ]
  3. {  ]. ^. I: Q! a( K  d
  4.         if(nIDCtl == IDC_COLOR_PICKER)
    3 s2 A, u/ ~5 m8 E
  5.         {
    - q/ X1 h5 c& I* F: K8 q7 o
  6.                 //设定高度为308 ]) f7 T# F5 J. C  f) t% O) K! Z$ n
  7.                 lpMeasureItemStruct->itemHeight = 30;
    3 ]5 P, \4 E& E$ G' M5 _
  8.                 return;
    ) s+ l; q3 n2 D, Y) s; v5 w- O
  9.         }* O2 ^/ Z/ P8 }% Z: O$ M5 b
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
    . m% f" ~9 e$ t% Z8 S3 m6 ]: }
  11. }
复制代码
! W, S7 y' {; U, c
  同样别忘了指定列表框的Owner draw属性: # X3 @! M1 ?  o5 l
  图11 指定下拉框的Owner draw属性  
: ^9 `" a3 [$ g/ T* R
! _: s! v, _  r; v2 H  3.3.6 NM_CUSTOMDRAW 0 L  G7 X$ ]) B9 {6 P$ i  n- L
  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。 / p7 K' H8 z; X" _2 S
  可以反射NM_CUSTOMDRAW消息,如:
9 F. N& r2 o( @/ o" g5 ?$ u- T  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw) * h; H; Z# `7 N$ W' i4 b. I
  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); ( h5 N3 ]- q% a. P7 X
  参数: 1 i. O6 b& b7 Y" @  e
  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:
6 \/ Y: Z/ g! c- i; j7 [) x% n
  1. 7 U3 I) R7 d$ _* ~+ Q  [
  2. typedef struct tagNMHDR% i5 E: x* k% w/ @1 I. P4 K0 x
  3. {
    ' A1 A( F- u3 U: E1 [3 n8 m" s% ]
  4.      HWND hwndFrom;2 d3 x9 u! k' @) s
  5.      UINT idFrom;- G; p; U+ q$ Q) Y
  6.      UINT code; 6 N( O0 O* N0 C3 f( s$ {! v  Z
  7. } NMHDR;
复制代码

8 T' G0 _- ~$ I1 ~4 z% s. P5 C  其中:
5 h1 G  Z6 m' F5 |% Z  hwndFrom 发送方控件的窗口句柄
# X! e" s, x2 F; I  idFrom 发送方控件的ID code 通知代码 - N; A. N  r6 n+ b
  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下:
' w  ~+ V0 A: z  l4 w+ \. M. Y# [

  1. 0 w% V5 v9 a2 Y+ U" b. ^
  2. typedef struct tagNMCUSTOMDRAWINFO9 L3 s  V: x! v+ t% p# G! \
  3. {& y9 \, d/ T% X1 }9 ]- G1 _5 i
  4.     NMHDR  hdr;
    % D& W. _8 e9 w. X; o
  5.     DWORD  dwDrawStage;
    0 L. p1 k5 G7 {8 C
  6.     HDC    hdc;
    * ~3 W% V3 B, y8 d# v' T
  7.     RECT   rc;7 z9 A0 k3 d- c
  8.     DWORD  dwItemSpec;# r$ J$ l4 [/ h. [( l; I4 t! ?5 \
  9.     UINT   uItemState;
      a- g" R+ w" Q" P
  10.     LPARAM lItemlParam;
    ' b* H5 j1 d8 |. i2 c$ a+ I" V
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码

9 K4 S5 O$ X' g  H   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:! b4 [1 s5 f, A3 Q
类型值 含义   n# t2 h4 S4 u4 x/ f: I8 ?% J
CDDS_POSTERASE 擦除循环结束 7 Y9 i" W) |. H$ L) A( Q; e
CDDS_POSTPAINT 绘制循环结束 5 U8 m* n8 [7 r% z
CDDS_PREERASE 准备开始擦除循环 3 x- s4 E) M" N* t, T- o0 J
CDDS_PREPAINT 准备开始绘制循环
& P% Z8 E; n1 JCDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 + v9 @# H9 d7 |1 M! O
CDDS_ITEMPOSTERASE 列表项擦除结束
$ w: L2 Y) o- A4 Y% @( E+ O) i3 ~CDDS_ITEMPOSTPAINT 列表项绘制结束
2 v/ m- I5 D* K3 l' o3 s: F( v6 U  UCDDS_ITEMPREERASE 准备开始列表项擦除 , }, @' B. j& o2 s2 V2 ^
CDDS_ITEMPREPAINT 准备开始列表项绘制 9 m, ?" @9 k8 w, q) ?) b; X$ ~$ t
CDDS_SUBITEM 指定列表子项
' v# r/ T& l3 ?$ x" C' `1 b表7 dwDrawStage的类型值与含义
3 r6 Z* ]: j2 Q' `1 ^2 h7 k  hdc指定了绘制操作所使用的设备环境。
/ w+ C- c8 V) k) o  rc指定了将被绘制的矩形区域。 # R8 B5 _4 @& p$ M- Q% H9 W* x
  dwItemSpec 列表项的索引
. ?7 u" A1 S+ L  uItemState 当前列表项的状态,其取值如表8所示:7 [  Q- W) o* B/ m, O! f% ~
类型值 含义
5 E; u) ~0 o+ YCDIS_CHECKED 标记状态。 $ y$ h3 ~# x6 s- X
CDIS_DEFAULT 默认状态。
, A8 A  ^3 J3 E9 [% A, w- hCDIS_DISABLED 禁止状态。
9 J1 l/ O# u0 M, J% [: O# W: ZCDIS_FOCUS 焦点状态。
8 X/ C6 N4 E! ?CDIS_GRAYED 灰化状态。 ! {' ~0 e; j0 u$ N9 [
CDIS_SELECTED 选中状态。
* m  L" S1 I  w2 Y7 @CDIS_HOTLIGHT 热点状态。 5 n7 D7 q% [9 g
CDIS_INDETERMINATE 不定状态。
$ t, O% I/ l) n' f' S/ zCDIS_MARKED 标注状态。3 E7 O* [% d5 `' o/ S0 p! T
表8 uItemState的类型值与含义+ @$ a. y3 t2 {% x. X) s
  lItemlParam 当前列表项的绑定数据
2 `" K3 ?4 n/ g7 _, m3 G  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: 5 Z5 V" x+ q9 L" F- D' J
  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:
: |5 Y1 f9 m" |* r% z类型值 含义 , d4 _4 M# M4 _& U9 c/ D: a- z
CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。 ! y! |) f2 r! e% [
CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。
9 X- C' A2 G% w7 ~CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。 * H$ ]9 B& K1 m
CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。% @+ ~% }& x, G" L- o
表9 pResult的类型值与含义(一)
- ?: m# ]8 @/ h) I  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:; @5 M! k# j8 @/ _# ]0 r0 C* t  c
类型值 含义 1 A9 ^$ Z9 `1 J/ c  K" O& W7 ?( I
CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。 ) A# O4 ?  H, m, y
CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。 2 f. V3 i' {+ |# r( W( u
CDRF_SKIPDEFAULT 系统不必再绘制该子项。7 u; B" Z/ `8 i
表10 pResult的类型值与含义(二)
+ e- K, F5 ^5 d: K  _. {  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子:
3 j; \2 a3 y! W/ R' @2 A
  图12 利用NM_CUSTOMDRAW消息美化界面
8 R8 r: u( V* [  对应代码如下:   W/ l/ Z4 a' d# n" s

  1. 8 X* v/ o% c0 C. x1 P2 Z
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult). @2 e) i2 d5 N2 S7 U8 M
  3. {$ s$ {6 y" S$ q' h- P) j1 \
  4.         //类型安全转换) g& K% X8 e" e7 D
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);
    0 S2 Z! ^  I8 T- H
  6.         *pResult = 0;5 S; d, s8 K" F- V
  7.         
    , V3 C& }4 ^, r5 d
  8.         //指定列表项绘制前后发送消息
    2 t# y% D+ a6 @% T- Q+ S8 m: R
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)
    - q& ~, o" [3 A+ f
  10.         {. }2 h  t: ^/ m
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;) ?0 @2 ]) ]+ m+ @
  12.         }% M7 l4 T7 ~* d8 ], d, S4 c  w/ p
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)
    ( A. I- ?! z0 E
  14.         {
    0 A' X' @* s( }' d% R) m' r+ S9 s' M  L
  15.                 //奇数行
    - N, E9 G8 ]: x: c6 ~3 {
  16.                 if(pLVCD->nmcd.dwItemSpec % 2)
    2 ?& v1 M* w2 S4 O: \( b/ A# u
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);; Q, \* @& |+ S; Q2 z1 |8 K
  18.                 //偶数行) w7 H7 o+ y; K9 F; C% X9 l3 s+ G
  19.                 else
    3 z- h  _" k: V$ A- O+ a
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);
    ' q% I* z8 V8 h8 D' |$ m' ]
  21.                 //继续' `" @! ^: D' @
  22.                 *pResult = CDRF_DODEFAULT;
    8 P+ G7 B3 j, Z+ L4 f6 W
  23.         }9 `! U0 X6 U, l% r. t0 W9 @
  24. }
复制代码
4 E' A1 d2 Y" P8 ]& \3 G
  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。
; L6 {" m3 L+ L$ z% p' ]0 E& O
: T# B  e7 T# H  3.4 使用MFC类的虚函数机制
  f' D) I* i& Q2 O* t  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子: 9 U4 e+ f3 h/ _8 T

  1.   ?3 R+ ]: L  r1 u3 A
  2. void CView::OnPaint()) p/ x. L7 n' x; k# S1 N
  3. {
    7 w/ K; `1 z) p: L# x
  4.         // standard paint routine/ ]7 R3 ^8 E1 k: h
  5.         CPaintDC dc(this);- P$ H+ a! g* U# C2 e
  6.         OnPrepareDC(&dc);+ D/ ^) ]; L! F9 }% k3 V
  7.         OnDraw(&dc);) O. |1 z  s9 {
  8. }
复制代码

: E! {" Q& r# u6 X! L! p; F  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。 6 ]; a& k3 L9 b5 e, D- \
  以下列出了与界面美化相关的虚函数,参数说明略去: . b8 S: U6 `: U- S
CButton::DrawItem
. ~1 c) D0 {1 U% LCCheckListBox::DrawItem ; ?9 v! p4 \* w" C; m
CComboBox::DrawItem
6 Z. y) o# \% f- \: |5 _CHeaderCtrl::DrawItem   G5 G- O) d7 C( V. R8 D9 \
CListBox::DrawItem
( u2 A# \9 R5 ^4 NCMenu::DrawItem
7 X+ {# U9 {2 cCStatusBar::DrawItem
6 m3 E' k7 w6 O/ k9 J5 D* TCStatusBarCtrl::DrawItem
/ S6 d4 Q* `# o: y+ C. R/ hCTabCtrl::DrawItem
4 W. g0 t- E, Qvirtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct ); : \2 v" D* B0 K# R) {
Owner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。
+ @/ R5 Y6 E9 r/ e  限于篇幅,在此不再附以例程。
* Z+ G  C4 i4 d6 S
4 ^1 ~$ Q! `, r+ G0 a* g! r参考文献% Q& [- |" r* w& i1 e' i0 u
本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

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

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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