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

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇
, [  W8 O9 a( k& Z5 }  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。
. K. e6 G7 E0 E' ]" B  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。/ s8 k  ?5 d7 l

9 @! Y( A: T) D7 i7 b  2. 美化界面之基础篇- m: T3 e9 P" m' o$ c( x3 H. K
  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……
/ p5 V+ h, E; [8 Z9 m% d" _/ L
* w5 c$ W, h- r7 _9 H  2.1 Windows下的绘图操作
2 J5 K. m, }5 ]5 i5 _1 J2 F# h  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……
3 u- S) W/ C+ c/ b8 w  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。
0 @5 |5 D# I3 x% D' c; q+ x$ O4 }( [% v
  2.1.1 设备环境类
' u' J3 }! Y) @: \" M1 w( V  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。
# J8 @# T$ J+ m' `/ x1 h3 q& O6 k  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:! x# U3 W* h9 ^$ T# B
  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式
/ _  L, r0 n6 i% L4 J$ A  Mapping Functions:映射操作
! e1 t+ T6 K4 u) t- `  Coordinate Functions:坐标操作. f( e) Q- Z# `+ K6 t9 _. p  n1 |8 `
  Clipping Functions:剪切操作
4 R: O8 A. Q0 U. I$ s' R7 M  Line-Output Functions:画线操作7 k0 t" w7 Z, W4 s2 l2 a; Q
  Simple Drawing Functions:简单绘图操作,如:绘制矩形框9 z* z( P" e2 E
  Ellipse and Polygon Functions:椭圆/多边形操作
3 u% Y; \9 O" Q0 l: _  Text Functions:文字输出操作. s# o# a9 w/ x7 h1 J) b( S
  Printer Escape Functions:打印操作  x2 A0 @; c8 x" P3 \% [
  Scrolling Functions:滚动操作
! F1 i7 B. q/ s& x  *Bitmap Functions:位图操作
) L) M5 A4 f  o  t. i  *Region Functions:区域操作1 e" E$ n& j' B# y) s6 g
  *Font Functions:字体操作
- i! T2 I0 o" n; ~  *Color and Color Palette Functions:颜色/调色板操作' W8 z& i  ?+ F
  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。* J, m# g0 f8 V0 g& n( N, f* Q. c! F

% Y5 g9 x) o+ `( \" y) H; J2 R  B- S& u  2.1.2 图形对象类
3 w) ~2 Z4 Q- K2 L( Q  m9 j8 j" {  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。
: T$ p4 c, h. k% A' g  下面的表格列出了MFC的图形对象类:8 T& {0 M6 X$ Q5 d
  MFC类 图形对象句柄 图形对象目的
# U( C; S6 E5 \- U4 t% \! F! d' _) Q7 e  CBitmap HBITMAP 内存中的位图
! }5 C! d/ q: F7 i# p; h; f; E+ f  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式
6 b6 ^: L" ~5 y8 A8 E# H+ x  CFont HFONT 字体特性—写文本时所使用的字体
# ?9 ]- k" V) }8 g( X  CPalette HPALETTE 调色板颜色
9 i9 _6 P7 G) E- h# r  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细
" e7 Z  b2 l' q- L  CRgn HRGN 区域特性—包括定义它的点4 e9 h- v% `# e: l' h% V9 I
  表1 图形对象类和它们封装的句柄& V* i6 E1 W2 p0 g4 ?2 d+ I

. i6 |5 l% x; B2 Z0 u+ D  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面: / ?3 r$ N1 C/ N# p& x
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:
( z4 e3 Q7 G, c/ q& P( L
  1. 8 X, B5 I3 ~( ~  N, c9 E$ @
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)
    / `* h' U" M) j' p
  3. {
    % l6 h6 D4 h9 }, r  ~* [5 a: L- v
  4.         //设置背景色/ S) ?4 S: ^! l
  5.         //CBrush CUi1View::m_Back. w5 v  s; F, R& |
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));4 p+ V7 x8 d  @
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL); ( e/ O8 \! W% P. K3 [7 `/ B* x
  8.        return CView::PreCreateWindow(cs);
    , q1 x. y8 N+ j/ w* H* K- L  p
  9. }
    ' N8 k1 |# U$ V. v, d- h
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
    % H2 P; u$ D$ g( B* {
  11. {: t4 ~/ I! D4 H& X
  12.         if (CView::OnCreate(lpCreateStruct) == -1). }. w+ F. _# `6 I4 g( x9 T
  13.                 return -1;5 ?, ?3 t4 s. y0 v6 r7 K
  14.         //创建字体6 }& f! t& I& v/ m. D" M
  15.         //CFont CUi1View::m_Font
    , [+ H8 Q, o* ?8 O* {9 j( K
  16.         m_Font.CreatePointFont(120, "Impact");
    1 Y& t$ Q6 |4 |. {( i0 P- a
  17.                return 0;
    4 d, B5 Y' w% v8 h7 {0 S: I
  18. }
    9 R7 _, j# V; }# X# P' o
  19. void CUi1View::OnDraw(CDC* pDC)  n! w7 x+ z7 V0 a% M
  20. {, z1 h) @+ y( @* \
  21.         //绘制按钮框架
    4 K% m0 n+ o2 Q1 J& q
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);5 u9 }! y8 ?! R% w9 z
  23.         //输出文字
    9 ?5 z3 l/ H  X( p) d. c
  24.         pDC->SetBkMode(TRANSPARENT);" ^. `4 `  r1 e) U- O
  25.         pDC->TextOut(120, 120, "Hello, CFan!");" ], H0 l  E. Q7 i
  26. }
复制代码
1 q$ q$ c7 W. T* g! w4 X9 g( i
  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。 1 a$ _9 O1 C8 j/ H

5 [- F3 w6 c- p- c+ X1 E  2.2 Windows的幕后绘图操作
' u' B! f9 K3 g6 @0 l3 R2 V  在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。  E7 C$ F& w) g8 v, G
  有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。
) e; f4 U2 k: F1 e! B( n  所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……' Q+ C5 O/ t# V( I! [% {8 `
  有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。) i* b# f5 G1 G5 H2 t  j  x

" Z4 v* ~* a2 ^, O  3. 美化界面之实现篇
, [7 o; ^& u' H) q7 i1 t% v  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。
0 b! @4 K4 q9 p" V( @$ l- X
1 D( J" k8 E; ^3 C) Z3 L% H  3.1 美化界面的途径
+ h) ~1 G, S3 M5 j# n0 z. \  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:
( K& N$ U) ~1 @7 J3 D4 u; a: G  1. 使用MFC类的既有函数,设定界面属性;% r5 a8 y/ v. y. I5 [6 G( d/ o; N8 s' O
  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;
) m+ \, @0 J! c3 D, B' V% U! j  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;
$ d2 X+ F2 r' P$ ?7 S! d/ s: O9 |; z  一般来说,应用程序可以通过以下两种途径来实现以上的方法:: ?8 H5 q$ a7 I; U0 T, D
  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;
" u$ R, d' f, g( [7 k  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。
4 K* T$ G( o2 ^& m  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:$ ~" z) i! q5 w, C6 v# J0 G, s! l
  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示: 0 Q3 P! P2 h. i* `
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;
) a7 h! J; o0 M& F  以下的章节将综合地使用以上的方法,请读者朋友留心观察。% i, t( c$ K+ C# Q6 A8 l
; F6 a2 L) D' Q' ?+ [7 k
  3.2 使用MFC类的既有函数
* S$ G) Q, q$ C  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。/ s; K# R- }$ \4 @. f( b, {! [
0 N7 e7 P! X- X# O7 O4 k  B
CWinApp::SetDialogBkColor) {9 z3 s$ I1 n# i* X7 H) \
void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );- A- j: |9 r5 s$ [
指定对话框的背景色和文本颜色。" T; e2 A  Y& s! ], B

+ @4 t* c! u1 @- GCListCtrl::SetBkColor2 H1 Z' l  ^% |# f; k- p, L* i* F. ^
CReBarCtrl::SetBkColor
, U3 B4 k4 y& p/ C& k5 v2 rCStatusBarCtrl::SetBkColor( @* J% K- M. ^
CTreeCtrl::SetBkColor- a# K8 _. g# ?  A0 G
COLORREF SetBkColor( COLORREF clr );9 k" d+ u3 s/ b6 U" D6 a
设定背景色。; h7 u! A9 v+ |: I' A+ V' k& Y

- z/ V2 V) \- z" OCListCtrl::SetTextColor! M2 f2 |0 w/ f0 Y% S# U& ~
CReBarCtrl::SetTextColor
& d7 T/ n8 C" I& C2 uCTreeCtrl::SetTextColor
) P. `* j' s7 l: I; x6 t' m4 g# LCOLORREF SetTextColor( COLORREF clr );, ^+ l8 B. u' Y6 F
设定文本颜色。
. A7 j" B% L  l5 s, F% j$ u" t. y. a& U2 g8 L2 `4 M3 N
CListCtrl::SetBkImage
9 @- w/ Y# N7 \/ a4 z& V  Y' d4 \BOOL SetBkImage( LVBKIMAGE* plvbkImage );
0 \; P' M9 |7 A8 X" EBOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);/ h4 b7 S8 c( N, e4 u0 q
BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );# ]9 s) _6 F6 K1 |% ~
设定列表控件的背景图片。
$ h+ d9 X  T& t4 F2 X* i; P- }" i5 P
CComboBoxEx::SetExtendedStyle
$ \/ L& {1 l; H) ~CListCtrl::SetExtendedStyle2 H( e% l$ f( E8 Z' C! q. ]
CTabCtrl::SetExtendedStyle2 |! e# f/ x% Z
CToolBarCtrl::SetExtendedStyle
3 S% \5 z+ C2 A1 CDWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles ); 2 @4 p+ A1 C8 }6 }9 h
设置控件的扩展属性,例如:设置列表控件属性带有表格线。
- h& B/ A3 V8 @) @! @3 a( X  图4是个简单应用MFC类的既有函数来改善Windows界面的例子: ( {" `% i$ |" O: i7 R! h) R
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:
: x- R6 Z3 p! h4 x2 Z9 S
  1. 7 U) a  Q/ \: d' C  z
  2. BOOL CUi2App::InitInstance()
    ! U9 C) }& w6 E  q- B/ I7 m
  3. {% W" B" z! b3 L1 _) @5 ]# q
  4.         //…9 Q* Q8 W9 n3 p. q3 S
  5.         //设置对话框背景色和字体颜色
    ( ?* R- c+ O; Z- S4 D& q' F$ Z  }: G7 a
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
    / D6 s# C/ g& ?8 f. a3 J5 {3 K
  7.          //…
    + |0 M8 {0 ^+ U* W
  8. }
    ) x: I6 Y( G, B# a; _
  9. BOOL CUi2Dlg::OnInitDialog()
    7 b* C' X' F: \
  10. {" U. @0 L$ h0 W! h+ a4 k
  11.         //…
    5 |5 |! U7 ^7 f; X8 I# k
  12.         //设置列表控件属性带有表格线
    7 O/ |& H7 J8 i; e  A. \7 }
  13.         DWORD NewStyle = m_List.GetExtendedStyle();
    # |- S- F1 u" `1 X) }: \2 t
  14.     NewStyle |= LVS_EX_GRIDLINES;% i9 C' A3 Z$ G& |8 i' \
  15. m_List.SetExtendedStyle(NewStyle);% ~& K" g' G, X! \; O% W
  16.         //设置列表控件字体颜色为红色6 |; _6 z7 _2 @2 W1 @& p! d
  17.         m_List.SetTextColor(RGB(255, 0, 0));
    * v0 `. s1 Z# K+ U- S& |% g" T5 B
  18.         //填充数据' \+ d4 A" y9 l/ p6 w3 c4 m
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);2 I6 Q) @" q% o) ^+ C6 s% b& U6 J
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);
    $ i+ T3 T9 A# O; d% H* H
  21.         m_List.InsertItem(0, "5854165");- c$ |5 p, b; m. J7 k) j* ~
  22.         m_List.SetItemText(0, 1, "白乔");
    1 f# Z) V% w/ s7 M2 `5 G5 j7 h- G
  23.         m_List.InsertItem(1, "6823864");
    & q* U( W# e' a1 R
  24.         m_List.SetItemText(1, 1, "Satan");; `" [7 e2 c- s2 p
  25.         //…
    2 {" r# j; E1 |
  26. }
复制代码
+ K4 V; d* i) a# ]* H
  嗯,这样的界面还算不错吧?
8 G8 w2 E& f) ^. s4 h
7 ^( O) X" z+ ?# y# |8 n8 ^  3.3 使用Windows的消息机制
9 g- ~: J/ s  `; D) W  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息: ' q! t* l; N' M3 e9 V
WM_PAINT # V6 b* @1 _4 W3 V) c* y
WM_ERASEBKGND   `2 M* Z8 x* Y8 K, @4 S4 j
WM_CTLCOLOR*
4 w3 E! [6 I( h* ]WM_DRAWITEM* / l0 Q8 Y9 `. g" ?% r& z
WM_MEASUREITEM* 2 F9 j  z6 Z: b% w
NM_CUSTOMDRAW* 6 ]& `6 m- l2 e5 |8 I' n5 ~
  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。" t, i  [, u$ ~. k6 M/ r  _

% N) {% N$ M: i1 v8 J  3.3.1 WM_PAINT' d1 V% l8 I: M
  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。
( c9 A9 U! e& \) _8 m$ U  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下:
7 o8 Z5 \5 b* V; g- K  afx_msg void OnPaint(); : Y3 e! S4 l0 E7 }, [1 b' {% q
  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示:
/ A( W6 D7 Q% n1 N6 ^
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单:
( c; n* a+ P' j, `. L8 L$ ?# ^

  1. % V3 e9 V, X$ t: x$ N8 ^) Y
  2. void CLazyStatic::OnPaint()
    & z3 o6 X( e. Y  h, h
  3. {
    - X' u- K# J! g/ l4 D
  4.         CPaintDC dc(this); // device context for painting) N7 n0 v2 H$ l2 t  ]
  5.          , {3 t2 p! B8 L: ]; u/ R4 ~, `
  6.        //什么都不输出,仅仅画一个矩形框
    . F1 k% ^: o; E+ ~
  7.         CRect rc;/ |$ p% d$ Q( o4 {) ?+ ^- \
  8.         GetClientRect(&rc);4 R1 j& ^! v5 T1 n3 N
  9.         dc.Rectangle(rc);
    9 [. f( m3 g0 `4 E' m  R+ |4 D
  10. }
复制代码
. V$ \; ^1 y. m9 A  W
  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。- J8 r' E7 Y5 R; C

% s7 |$ e$ n, s7 a* p+ R  3.3.2 WM_ERASEBKGND
9 X+ y5 l0 s  q1 `/ L  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。
. g' Y5 v! L7 n; A. B  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:
; B- Q# [; o; ]/ c% [7 r6 c  afx_msg BOOL OnEraseBkgnd( CDC* pDC );
; V" U) r0 V' z6 d8 {  返回值: ; z. W- U5 X) N$ K
  指定背景是否已清除,如果为FALSE,系统将自动清除 2 X3 E- ?7 B5 ]6 X; G8 y3 L" N" Y
  参数: pDC指定了绘制操作所使用的设备环境。 1 N& ~& r- {5 \$ n" k: ]! h' a
  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景:
+ A! i, T0 _, d8 j9 _( c! M
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单:
+ n' u; b3 N  g# p9 v. p- }
  1. 6 Z' z( S; [& \4 c2 n1 b/ j0 F; _3 ]
  2. BOOL CUi4Dlg::OnInitDialog()
    & `6 x" n8 Q; O1 f/ ?) ~
  3. {+ P5 u0 p. w. E1 |' O* L: N2 J
  4. //…
    $ G* M0 K. W3 d9 D+ m: s  q
  5.         //加载位图
    7 c' p& h8 J& O# v1 x; }6 P4 t
  6.         //CBitmap m_Back;+ T4 i9 K8 m% x& z+ E8 }
  7.         m_Back.LoadBitmap(IDB_BACK);* g0 v; Y0 b9 Q- h$ q* d+ D
  8.         //…
    2 U8 |( o2 J  D
  9. }
    % z: b% Y0 z! M
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC)
    8 p: ]! u: T) t- }6 S- h
  11. {
    ! X8 [. J' c* e; M$ [
  12.         CDC dc;' C  P* O4 b1 M6 a' [  ]9 S
  13.         dc.CreateCompatibleDC(pDC);" |# U, U: |8 g  ~1 l
  14.         dc.SelectObject(&m_Back);
    ; {9 ~* U3 Y. l1 s  \; C2 O1 l
  15.         //获取BITMAP对象
    ) ^1 y7 R3 M  }: t6 {. R
  16.         BITMAP hb;
    " U9 s9 Y# G* Y/ A
  17.         m_Back.GetBitmap(&hb);
    ( T2 q: H1 b) C- j- U  ?7 J. X4 n
  18.         //获取窗口大小
    3 |$ |5 k# S2 V# V) T
  19.         CRect rt;" O3 z) A% o3 n- W7 ~- [2 G$ X
  20.         GetClientRect(&rt);/ ^, m2 |3 q& s% n- X$ R. K
  21.         //显示位图" L( X/ w% F+ O9 R2 Z- y
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),
    6 u/ [- M2 P* Q" R6 T& ?" i
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);, U. d# g! Y3 C: L9 t" A
  24.         return TRUE;7 E( }7 M2 b) ^& y, k, y5 }
  25. }
    1 w( P7 a  l8 l* ^
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    6 J) }; p' O1 _0 t$ ~$ o+ N7 b! c0 u
  27. {) \# u1 @. K& f* K5 }
  28.         //设置透明背景模式
    ( V3 v% h# S) G& ^% E0 u( D
  29.         pDC->SetBkMode(TRANSPARENT);6 w. E; w- }5 I3 x, `+ K1 X
  30.         //设置背景刷子为空2 _# ~: E( b$ r! d' [1 M
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);
    ) U0 l6 G7 V9 M6 p* y. s
  32. }
复制代码

9 W0 T, u, s! C5 }6 p9 R8 K3 V  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。 2 i+ {# u$ K4 y$ `7 g3 b! S2 {

. M! X5 f. ?, E0 o0 {! E5 P& |7 v  3.3.3 WM_CTLCOLOR
5 i1 }2 G1 L+ Y  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。
9 A8 I# W' C! [  WM_CTLCOLOR的映射函数原型如下: 9 q, P8 `( m: f7 B
  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );( O  z$ w- h5 ~' M9 |- F9 i. C; u
  返回值: 用以指定背景的刷子 " ~1 O# k9 }% h! }$ v
  参数: ; \0 x1 F' {) R
  pDC指定了绘制操作所使用的设备环境。 ) X- y- R: Q; s( [
  pWnd 控件指针
6 {" N/ Z# n$ C0 ^! k  n  nCtlColor 指定控件类型,其取值如表2所示:
( D# z; ]5 g2 A, S  类型值 含义
& c+ i& L" ?; j5 A& x2 iCTLCOLOR_BTN 按钮控件
+ ]6 ~; v) A/ S! ]3 _! M4 fCTLCOLOR_DLG 对话框 # U1 b. ]2 J9 y0 B6 N2 u
CTLCOLOR_EDIT  编辑控件 ; G1 I% ?7 G8 ], E$ M
CTLCOLOR_LISTBOX  列表框
" L# B/ f( D; K. d4 DCTLCOLOR_MSGBOX  消息框
9 D4 m+ Q: n6 ~% k3 [9 [CTLCOLOR_SCROLLBAR 滚动条 # z2 P0 S6 j5 d) s4 A6 p4 |
CTLCOLOR_STATIC 静态控件
" l* l; U$ b! `# o表2 nCtlColor的类型值与含义! q1 k; G9 M/ I) g) ~! P* |0 D
; P: r/ c; H1 S/ `/ H9 Q
  作为一个简单的例子,观察以下的代码:
/ d7 i: M  h! b7 a) g8 n2 D- V

  1. 0 Y9 S/ K" P! Y+ H
  2. BOOL CUi5Dlg::OnInitDialog()
    , h% B$ ^! z, w8 ^) _  c. ]
  3. {# S6 B0 i: N* G! D0 Z5 f$ y
  4.         //…
    * E3 ~1 Z2 M7 c% r$ k- A' Y
  5.         //创建字体; u: G3 \( w, f( g
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font2
    5 ?2 n2 }3 j9 D4 D- r# G, ~  N' h
  7.         m_Font1.CreatePointFont(120, "Impact");
      e& K" U" r7 Z$ M. }0 d
  8.         m_Font3.CreatePointFont(120, "Arial");
    " r% [, B/ b4 j  M* d+ X5 n7 |: u6 U
  9.                 return TRUE;
    $ N& H1 B0 ?/ B: d' \
  10.   // return TRUE  unless you set the focus to a control 4 r) s2 M& Y9 ^% ~7 k# x
  11. }- O7 {1 v; _& n7 n* @6 a
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 7 D- B5 y% |3 l
  13. {
    ! t. p( K, V0 Z) O% R7 I
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
    0 Q+ y' r* g" z; v9 P  u' b! I
  15.         if(nCtlColor == CTLCOLOR_STATIC)5 G( B1 P1 P- H, ~5 Y
  16.         {. f; @2 r) p, A4 z  h
  17.                 //区分静态控件& v. ^  o, b0 Y( L: H$ c5 v
  18.                 switch(pWnd->GetDlgCtrlID())
    * L  ~2 Y8 T" ~. Y# D, \
  19.                 {8 k8 p2 m( L6 k
  20.                         case IDC_STATIC1:, j" T+ f% f( i. ?, D$ x6 k. R9 I$ W
  21.                         {
    ! l7 _  n1 u- q; S
  22.                                 pDC->SelectObject(&m_Font1);4 V# X& o3 \& ^  z' l6 v
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));
    / K- G" P5 ~3 |, b
  24.                                 break;
    4 v4 ~- e0 C6 E1 J
  25.                         }# l0 P  }( C5 U& I
  26.                         case IDC_STATIC2:7 F4 |* k! n' }6 V9 h+ Z' K* z
  27.                         {7 t' Y8 R) k5 d2 E, S, k
  28.                                 pDC->SelectObject(&m_Font2);
    1 b0 h2 E9 \% Z$ V
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));7 c9 G7 [# k4 v: p2 p3 w: i
  30.                                 break;+ W- s: y8 U3 g. [* r6 @4 }' [
  31.                         }
    , R3 b) `8 i: Z' ^' x2 X4 L
  32.                 }9 J7 F* b$ `5 A  Z+ H
  33.         }
    6 Q/ z: h6 [/ r
  34.         return hbr;
    7 X1 l9 @" k! \, l( `
  35. }
复制代码
5 o1 y6 k" V$ q; ~  E! |( Q' A1 W
   生成的界面如下: 3 m7 \3 s; E( e: c6 J# p! D  D3 i
图7 利用WM_CTLCOLOR消息美化界面
6 d' o3 \4 O9 d3 `: g  ?
  3.3.4 WM_DRAWITEM
' c* K( Y" w) I6 n/ }+ V/ Y$ Y  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。0 r$ R! S2 O+ c( q- F: o' }3 F
  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。 9 a7 a% O3 F% @! }. V
  WM_DRAWITEM的映射函数原型如下: 6 ^3 A' w" n# e3 R2 l
  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );) V% J8 T/ o7 C3 K8 ~
  参数: 7 |/ N6 f  X* g- p' h
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 ) S# Z9 u5 X9 x. ]) q1 B% _
  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下:
; b/ A" Q* u1 d% M0 a3 A( m

  1. ( K0 `- ]. a) a# a+ g! z
  2. typedef struct tagDRAWITEMSTRUCT7 X1 l8 Z9 d1 O
  3. {/ P- d+ |+ R9 L# S
  4.     UINT   CtlType;3 `! I/ m# F9 C* [+ {$ K
  5.      UINT   CtlID;; e7 I) T8 R! H5 _' [% R. ]  f( K
  6.      UINT   itemID;% ^* L9 m! w- @0 ?
  7.     UINT   itemAction;" m7 t. W" d, P- H. r
  8.     UINT   itemState;
    , W9 p0 H6 A% s, ~0 d- }4 Z* f
  9.     HWND   hwndItem;, \* q" L& u" q( v$ r. o) w3 }
  10.     HDC    hDC;
    - d! Z7 S1 K8 U. ^0 u. d
  11.     RECT   rcItem;1 G6 K( t4 D0 D8 y& }& ~" p
  12.     DWORD  itemData;( z* n: p5 O! F( E3 F' H; [
  13. }DRAWITEMSTRUCT;
复制代码
3 F/ K$ f! ~. N' h+ `: k/ }
CtlType指定了控件的类型,其取值如表3所示:
6 h% Z$ H; c; D: d- I" M+ Q类型值 含义
& k8 a' p) G8 KODT_BUTTON 按钮控件 / X- s+ l( |$ F- w! N; p0 b7 U$ Y( N- _
ODT_COMBOBOX 组合框控件
( z. x: v, c" t1 W! J( [( ~ODT_LISTBOX 列表框控件 # f0 D! y" ^& O) g  Z! I
ODT_LISTVIEW 列表视图 : v5 S" [- O, `: t  b0 ]3 M
ODT_MENU 菜单项
0 G, H% ]; {1 j& v2 Y& Y% y; Q+ f0 nODT_STATIC 静态文本控件
- R4 j& r0 m* f5 G- I2 C0 z" dODT_TAB Tab控件
+ q3 q/ I5 e" S表3 CtlType的类型值与含义+ [) D! A. V. E" E) B+ M
/ W1 U2 d8 F% f  |; z7 ]; u* H
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项 , |- _$ x9 [4 T9 S3 v
  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。 , D, d9 V2 T0 `4 |7 m; i% g
  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:4 N8 u6 M4 m) l9 @4 ^  n. N! }
类型值 含义 ; C# C  o3 `& ~+ j+ P* q
ODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。
# J2 b% D" ~1 u3 K. c  _ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。
8 e3 X- G3 s; N! t6 g0 a1 T* T3 H9 g' yODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 1 V. e; a$ P' b) t; q( C7 N
表4 itemAction的类型值与含义5 \0 ^3 Y# i. R
  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:
# P, ^" g7 E* c2 X/ \类型值 含义 ( m0 y1 n6 L1 a: K
ODS_CHECKED 标记状态,仅适用于菜单项。 4 x( E  o  I$ u
ODS_DEFAULT 默认状态。
. P; ?  |* E/ C& }* YODS_DISABLED 禁止状态。
/ u" v" z+ L# x' i* x, ^3 VODS_FOCUS 焦点状态。 $ I. ?6 L0 |. [7 J+ g4 n- B
ODS_GRAYED 灰化状态,仅适用于菜单项。 # }0 m+ D" h: ]
ODS_SELECTED 选中状态。 ' o3 O1 d9 w, a  T" b/ F
ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。
0 @: u6 T7 w% p# H, P$ T9 GODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 . }" ?( D2 q( v0 G& o
ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。
) b  a! X& r9 y% r% ^! O- ?% lODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。 2 S7 C1 I' w/ i6 ^
ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。
; @- O3 L" G6 E' j/ B表5 itemState的类型值与含义' ?5 B% \6 ?8 B  @5 `
  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。 9 _! G& J( S: m! w
  hDC 指定了绘制操作所使用的设备环境。
- U9 a1 E9 \- `: }6 v+ X2 c9 z  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。
5 I4 ?# H4 G8 n, f  itemData
$ Q9 Y  D3 u! c$ k: F1 F  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 1 v$ p) m- a/ N0 ~7 d
  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
4 \' H( x! c. E  r" a( x" G  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。
/ U+ e! @* ~* K5 V6 r! E  图5是个相应的例子,它修改了按钮的界面:
1 C9 i0 c  _) K% [
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下: : R: p& }' \7 x5 a+ C0 \3 R
  1. ( g, d& b/ l; h& f) K9 {
  2. BOOL CUi6Dlg::OnInitDialog()+ q! y* V, J! L; f
  3. {
    , h9 T; E6 i. J% s
  4.         //…; d+ f! Q0 h! _; k# ^% t
  5.         //创建字体
    0 N& A5 S4 U6 P0 J5 T, ^
  6.         //CFont CUi1View::m_Font
    ' ]4 @8 @4 R7 _
  7.         m_Font.CreatePointFont(120, "Impact");7 u' ]7 B0 k( o* d- i
  8.         //…
    6 R; P6 i2 V7 ^3 ^. |; ~6 ^+ [
  9. }4 C6 [% U9 {. ^# |/ |4 Z# Q7 E0 O
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
    : `. a, F% `, n& N* o1 k. s/ ?6 @; r
  11. {
    # {+ ~7 W/ W7 {
  12.         if(nIDCtl == IDC_HELLO_CFAN)  y: ^2 x, G' o: `! t! E
  13.         {! h) R9 f" D; s' H
  14.                 //绘制按钮框架
    : k' F! Z0 v) z; a6 i/ [! ?. @; G
  15.                 UINT uStyle = DFCS_BUTTONPUSH;* M$ C) ^, Y6 j) @$ M
  16.                 //是否按下去了?* Y6 d: L  g8 w- }
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)
    ; d# m+ w4 L4 @1 M- H8 X
  18.                         uStyle |= DFCS_PUSHED;
    $ v6 k9 d8 t6 s4 s2 c, h! w. ~
  19.                 CDC dc;
    " q& C+ [  g; X1 U- i
  20.                 dc.Attach(lpDrawItemStruct->hDC);6 Y" |. g+ D, V$ H* K
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);6 j/ A  v. n' g4 k! p
  22.                 //输出文字8 O/ y4 t0 G4 g* x
  23.                 dc.SelectObject(&m_Font);/ D2 V; ?! p) m$ q/ r
  24.                 dc.SetTextColor(RGB(0, 0, 255));5 w" F; m# V* j2 V
  25.                 dc.SetBkMode(TRANSPARENT);
    * p# K) H- R* D; y5 ]" Z
  26.                 CString sText;' A* N! L9 {7 B- x
  27.                 m_HelloCFan.GetWindowText(sText);! w  M2 i3 N% C5 d" J9 e, }, G( j
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);  i1 H+ }" [- H' N
  29.                 //是否得到焦点: s  J! ?& h" U4 g5 E/ _5 S; `+ ?
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)3 ^3 Y. W, x1 b; r" H" f3 Q
  31.                 {" W5 c6 ~4 U/ I! j) E
  32.                         //画虚框7 z9 L: F3 p2 [' [- @& A
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem; - g# p7 l+ c8 _0 a4 ?; s( ~0 [
  34.                        rtFocus.DeflateRect(3, 3);" t+ x7 ]6 l# F5 g- T& S  g$ n+ }
  35.                         dc.DrawFocusRect(&rtFocus);2 B5 h2 H5 p( \6 ]2 O/ R) X" z5 F! _
  36.                 }
    0 s& m2 o1 R5 H
  37.                 return;4 U4 d7 |3 E/ v7 E6 @7 N
  38.         }
    , C5 y" l" {8 _' Q" s
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);: Q* b. s8 _6 j% q0 |( O
  40. }
复制代码

5 {( ]5 g$ v, H+ D  别忘了标记Owner draw属性: - B0 x/ J* j& m. h( X
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。
- w* X( n; w5 ?. d2 Y* L$ _/ L, u* r1 F  y
  3.3.5 WM_MEASUREITEM ! t$ O% J0 q* f* [1 j& e, A% [0 k$ l
  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
( M* e2 q* D7 v2 i  A$ Z' t7 N' I  WM_DRAWITEM的映射函数原型如下:
$ I% c$ R5 o2 B5 ^& D5 J  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
, v! d$ \% C; k" N2 ~  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l
5 B4 o" W5 F8 e7 |! l* N1 Y4 e5 n8 _  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下: ! B0 G1 p" X" `3 [" v6 P  W

  1. 9 m& p% T. Y. ~/ C, u* r2 H1 g; q
  2. typedef struct tagMEASUREITEMSTRUCT6 n0 n: b6 L9 t/ ^  L, s
  3. {
    9 t4 _# n. j: _
  4.     UINT   CtlType;: W% E; L/ z. x. U9 }, C+ v5 E- j
  5.     UINT   CtlID;
    + C7 C' r6 z* o& w
  6.     UINT   itemID;
    % S& d( [2 B& u, K/ a
  7.     UINT   itemWidth;5 t" ?4 _1 b1 z8 ~, r
  8.     UINT   itemHeight;
    ' D0 Q3 |- p4 v% r# R3 t+ H; s+ l2 }
  9.     DWORD  itemData;
    + i9 z; q" }$ J: Y6 K
  10. } MEASUREITEMSTRUCT;
复制代码

$ k! Q6 ]# {, Y, X1 V- T, t* P/ b' T5 y# z+ O  CtlType指定了控件的类型,其取值如表6所示:
% t4 O2 T$ c' \( P类型值 含义
- w3 C: L+ y1 C" w! uODT_COMBOBOX 组合框控件
. N. k+ {  r& e2 J" V9 eODT_LISTBOX 列表框控件 ) d+ S$ i7 Z) S/ V4 u& q0 U: O
ODT_MENU 菜单项 : x0 S  @4 u% N7 ~
表6 CtlType的类型值与含义
1 `6 _% E) I# j/ n2 B  CtlID 指定自绘控件的ID值,该成员不适用于菜单项 ' O0 }% T; r, N, ^. G
  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。
; t9 v) c% O9 k5 h  itemWidth 指定菜单项的宽度
1 d# C$ N; p3 `  }/ t# E0 L5 }$ K1 ]  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255 / a0 w6 _. {2 m
  itemData : |; J8 M" B* g# ~0 f/ z5 c
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
" S: j! [& h; I: z  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
8 C/ v  n) v+ G  a' e  图示出了OnMeasureItem的效果: 5 t2 k7 x: V, Q4 H% F$ J* f6 q
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下: ( X) C3 c! t% ^7 D

  1. 6 e: C: P3 ]" h0 @3 u
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
    ' h' }/ B4 T/ Q# ~2 L0 l4 d& m
  3. {( p: z& O% J7 R0 \* E1 d; |! J
  4.         if(nIDCtl == IDC_COLOR_PICKER)
    9 Y! b- _) l1 J1 s9 l% \, E
  5.         {
    * l' L6 G; {7 m/ p5 g% `% L
  6.                 //设定高度为30
      ]( V/ X5 n  s' |. v4 c
  7.                 lpMeasureItemStruct->itemHeight = 30;
    7 ?! H2 v' A/ i& j7 d3 j' m
  8.                 return;
    3 D' |) E# A" Y8 W. ~
  9.         }
    7 j) z4 l/ e, X
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
    $ t7 z: r9 X$ n8 c
  11. }
复制代码

  X2 b2 H% L  n! y+ F! t" B  同样别忘了指定列表框的Owner draw属性:
! P) z& |' Z8 G! K8 m& Q5 a) E
  图11 指定下拉框的Owner draw属性  2 d3 G. d! M% G, D1 p/ a% y; f# d2 U

. G& t, ~: n* J+ e" m- d! q- r# c  3.3.6 NM_CUSTOMDRAW
3 s; s. A/ f' F) e  \  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。
/ C1 V4 q4 }* i9 W  可以反射NM_CUSTOMDRAW消息,如:   ?; A+ h( Q; m8 H9 I
  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
+ ?! I/ K& m4 f. u7 c; i  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); ) l2 d/ E, e3 Z  B9 [1 ^
  参数:
. m4 i; \  `& `  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:
( e. [; {/ m3 e& ~0 w7 C4 a
  1. 4 I$ P8 b1 x# D* w6 r# o4 B
  2. typedef struct tagNMHDR; O) K! F, h3 c- k
  3. {. G. e+ B: T8 K1 _
  4.      HWND hwndFrom;3 y. V5 `/ z9 S  a$ f6 y
  5.      UINT idFrom;
    % m1 ]$ D( L& |8 ?& G6 n; U
  6.      UINT code;
    # }0 v0 V3 Q3 B  u& j7 @8 V  M
  7. } NMHDR;
复制代码
7 p5 @/ k( z; Z9 P5 |7 [) [5 a) b
  其中: : ?4 t3 T) M. N
  hwndFrom 发送方控件的窗口句柄 8 N( k) m; ~2 J" @
  idFrom 发送方控件的ID code 通知代码
3 w. b4 W$ u4 U- Q: X+ U  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下:
4 O) p, D" Z: k+ \

  1. # i8 B4 _0 x" X8 }$ B
  2. typedef struct tagNMCUSTOMDRAWINFO
    5 Q. {6 u5 D5 \& u
  3. {
    2 w) j1 R0 E5 {+ L5 j
  4.     NMHDR  hdr;
    ) `* ?% |5 l& O2 S9 I, O# C3 h( Q( a
  5.     DWORD  dwDrawStage;
    / k, Q( Z. @0 N( C- D
  6.     HDC    hdc;
    * ~( x+ q& g  F1 U; I$ r5 ]# q, V7 j
  7.     RECT   rc;5 f# r+ _; Q7 _0 b8 _! ~! ]; g
  8.     DWORD  dwItemSpec;) Z8 U' Y; Y& D6 }+ A: z" S
  9.     UINT   uItemState;- X9 q  W" H1 ?2 u3 s  A
  10.     LPARAM lItemlParam;
    ) S; D" S; [& g. |- ~
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码

  [! R3 h' ^7 n   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:
0 N" A3 O7 U5 `4 z6 E  \7 b类型值 含义
4 V. V% u1 l/ XCDDS_POSTERASE 擦除循环结束
4 q: ~5 I" g) x7 x$ a. B: P- m0 oCDDS_POSTPAINT 绘制循环结束 7 U& ]/ ~2 S) x% t% ^4 r) M
CDDS_PREERASE 准备开始擦除循环
' }( i4 k' N+ b% @& P9 VCDDS_PREPAINT 准备开始绘制循环
+ z' o9 Q0 N' H5 t8 eCDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效
, Q" i/ B  Q$ E, O9 f  O2 |CDDS_ITEMPOSTERASE 列表项擦除结束 ! p* N" F/ h- r2 a7 ^# g4 A8 g3 s
CDDS_ITEMPOSTPAINT 列表项绘制结束 . q6 s! B; h7 n+ ?8 }& {
CDDS_ITEMPREERASE 准备开始列表项擦除 ( B9 }4 U6 |# \0 l6 U
CDDS_ITEMPREPAINT 准备开始列表项绘制 " B0 b" s3 L: N9 q  }% w6 A9 g! n/ [2 K4 \, `
CDDS_SUBITEM 指定列表子项8 b/ Z0 ], _! t1 B# L
表7 dwDrawStage的类型值与含义
2 Z5 \4 E; j! m) y" e4 h  hdc指定了绘制操作所使用的设备环境。 $ r0 [4 ?% k! }* h- i' o  d# }# t4 l) i
  rc指定了将被绘制的矩形区域。
# z6 l" a& X. N: ?1 [  dwItemSpec 列表项的索引 * h9 _. n3 g0 j# m0 O5 ^; Y
  uItemState 当前列表项的状态,其取值如表8所示:2 V- N/ I  b  v( ?& _
类型值 含义   k; B% V0 w6 {; j" _% W9 C2 Y
CDIS_CHECKED 标记状态。
6 y0 y' E1 o  H3 y3 sCDIS_DEFAULT 默认状态。
5 Q" c6 e* {9 U9 @CDIS_DISABLED 禁止状态。 3 p1 r7 a$ Y) Y4 D: p' v: [0 y
CDIS_FOCUS 焦点状态。 3 c1 H# B0 N& {8 [1 s
CDIS_GRAYED 灰化状态。 , Z$ @6 l2 }7 s1 M+ {' J8 m
CDIS_SELECTED 选中状态。 / Y1 x8 P) M* Y7 {* A5 Q" o
CDIS_HOTLIGHT 热点状态。
- D; c  C) u/ F; o6 ZCDIS_INDETERMINATE 不定状态。   O7 y& v- N6 D: V
CDIS_MARKED 标注状态。3 `# P$ L4 \0 |1 I! L5 w4 U
表8 uItemState的类型值与含义6 M( F0 D0 O9 P- i4 g* [- _# `
  lItemlParam 当前列表项的绑定数据
4 [! N1 w7 g0 y1 N, N- `2 m. X! Z  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: 0 b  U& N, E5 h! U7 F: M
  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:
# K8 v7 O' Q4 ~8 Y( l类型值 含义 & c; N/ B& |* J2 `$ O5 \
CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。 ! L8 |; G1 l# l+ T  J6 c2 k: t
CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。 1 @+ A( X/ H8 d
CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。 . c4 K4 T* u; K2 ?( L  g0 [
CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。% w. O# \, A) e  o
表9 pResult的类型值与含义(一)
  p  k! h" Q  U, C' J$ Q  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:
3 P& E8 j9 E2 d, M: {类型值 含义 0 N+ H1 \$ S! |. ~& [
CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。 , f- c  Y2 ]& [. t( W
CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
; R% E; L$ L$ n$ b8 y2 ?CDRF_SKIPDEFAULT 系统不必再绘制该子项。
+ ^- o+ l3 y3 Y& m: A表10 pResult的类型值与含义(二)
- H2 M% N' k' P8 V! z8 ~  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子: 9 y5 u8 r0 x' u3 Q. a% w( ^9 B
  图12 利用NM_CUSTOMDRAW消息美化界面 0 e; c0 C% |9 o  C4 K
  对应代码如下: 9 j1 z/ [/ M6 Z5 l8 X4 o: q, T
  1. , f0 P: L; J- f4 V) E
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
    $ q! t' y3 _, ~5 A$ D4 |+ X
  3. {
    % O2 ?; J1 T$ l% _" E
  4.         //类型安全转换
    / o" e/ c. Y8 S5 `3 Y; p
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);  }6 g7 R$ i" ?3 R
  6.         *pResult = 0;$ y" ]9 D' J+ l! ~! u/ m
  7.         
    7 y1 d( F4 B& [" |' v. ^9 o0 C
  8.         //指定列表项绘制前后发送消息4 ~6 Z) W. i4 m4 m: d1 [( D- O
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)
    # k9 ~  ~+ [3 ]1 F
  10.         {4 a! |/ W% T. s% W/ P
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;
    - C8 b- ?! T2 E( B% H8 C
  12.         }6 J# B% |. d4 R6 r. a
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)
    4 R4 s3 s* o+ ~4 p
  14.         {: |5 K0 p5 |3 R9 W0 [
  15.                 //奇数行
    ; a' r& F% k* P/ G' y- K) ~
  16.                 if(pLVCD->nmcd.dwItemSpec % 2): y. o- Z0 S! W2 x
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);
    9 W; j& O/ A' I; s
  18.                 //偶数行
    8 u: W4 h. S9 d/ @6 X- I5 x
  19.                 else
    # Q; q! W- u/ s2 @2 w" c5 S
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);+ a1 V. n* O+ h/ v  @
  21.                 //继续
    3 }2 c5 J2 [3 t9 b4 A8 S
  22.                 *pResult = CDRF_DODEFAULT;. ~  k6 }' `7 j; U
  23.         }& T% J% Z, s$ D6 I
  24. }
复制代码

! o; a8 L) ^1 ]& y) w' B+ E  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。 # @( I! e5 E& {
1 ]& U" R  X- f& V
  3.4 使用MFC类的虚函数机制
! b( V! {! e: P; w# F9 Q$ h. K  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子:
! p6 X) o# g! I

  1. ; A- Z4 P" q, b, Z9 R$ ]
  2. void CView::OnPaint()
    6 J4 ~, N6 P5 N
  3. {' k' f8 D7 C) L. C$ i4 y. i# k6 y
  4.         // standard paint routine
    9 g7 M. x  Q1 O' `' u: ^/ u# j  ?" a
  5.         CPaintDC dc(this);
    / Y2 U2 U4 t7 m& z3 F7 i1 s- t
  6.         OnPrepareDC(&dc);! h9 ^! F; ~9 P# L1 }6 d: ]
  7.         OnDraw(&dc);" t+ V1 x% M# t2 K2 K
  8. }
复制代码
/ l2 \$ Q8 U6 l
  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。
" m( t6 a  C6 s8 ~+ m  以下列出了与界面美化相关的虚函数,参数说明略去: 3 ]6 `6 T) |- O; T
CButton::DrawItem
5 O' j  `3 T/ m5 mCCheckListBox::DrawItem
& Q, Q8 \; @; o! b5 s: MCComboBox::DrawItem
' ?% T& K! t/ ]  z! s# FCHeaderCtrl::DrawItem 6 J/ H4 @; _+ Z7 l# Y" V
CListBox::DrawItem
7 C1 ]! g& ^' i3 v- ~CMenu::DrawItem , z- W; `/ m1 h1 {. d
CStatusBar::DrawItem $ @6 z* l/ l2 R: P$ ^
CStatusBarCtrl::DrawItem 0 Y1 Q1 H- R0 `$ K9 d" m
CTabCtrl::DrawItem! ]7 P% C3 [0 |& o5 Y
virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );
5 Y2 I) O' G3 O. lOwner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。- B+ N4 p7 ~6 t/ e/ a# P% E) y, t8 o
  限于篇幅,在此不再附以例程。
2 u$ \. e! l- F5 u. O  [- u( |% X7 G" ?7 u4 S! d- t+ k
参考文献8 J0 N1 u( |( Q  X+ ]* i
本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-5-2 10:17 , Processed in 0.021696 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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