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

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇
$ M4 `0 `, m8 g" {  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。
. p3 Q9 A# U( G" E) t5 c  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。
5 `" V7 Q8 d& z7 B
3 e6 w/ P; L* n) X6 O& d4 A  2. 美化界面之基础篇
4 ]0 E+ B$ a! T  {' B, |  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……
; P% B  U$ q& E) F" X: A7 |$ h: b- M* W- {) u, b, m6 @
  2.1 Windows下的绘图操作' z; K' @4 i3 _: l9 M
  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……) A5 P$ i. I5 L6 x; ?$ |
  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。3 d2 n7 O4 ]1 m" _' B  _) y
& V( c4 V$ s2 o
  2.1.1 设备环境类, d, w2 J/ E. i4 ?! H. S
  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。1 O7 Q2 A7 w$ }+ _7 ]3 w& t
  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:
+ P3 F( b5 @8 l, o, K4 W& z0 s  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式! `( ^8 _" h  B$ ]% c
  Mapping Functions:映射操作: M+ b4 |5 L5 X
  Coordinate Functions:坐标操作2 t. v4 g. X# E  @: @
  Clipping Functions:剪切操作8 W! ^, j6 a- M1 V0 {
  Line-Output Functions:画线操作
8 P  ?, @6 r# V0 L  Z! f  Simple Drawing Functions:简单绘图操作,如:绘制矩形框1 \! [" ^& I  ]& Q( K6 C* s- j
  Ellipse and Polygon Functions:椭圆/多边形操作* x0 r% i: x# L1 B- s
  Text Functions:文字输出操作' ]! g9 y) ^" M2 Q; G7 o$ T, X
  Printer Escape Functions:打印操作
. S2 D9 V) L2 c1 k  _# R8 X  Scrolling Functions:滚动操作0 L0 u' N& [, h& q$ R2 V
  *Bitmap Functions:位图操作
% D. O' r7 u! x+ D' |* F  *Region Functions:区域操作
; J% u+ Q5 K. [. \( Z- f  *Font Functions:字体操作
4 E) x% j: ~" _  *Color and Color Palette Functions:颜色/调色板操作8 d! c) R- r2 [0 E6 {! g
  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。
' J+ @3 [) F8 z9 x( ~: T
. M2 Z1 A  y! g7 G, M& l& S  2.1.2 图形对象类3 p% g/ F; s3 z$ E& h5 ~, b
  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。0 u+ g2 D# j$ ?2 W% h1 z8 `4 j
  下面的表格列出了MFC的图形对象类:
8 o8 d7 b, M/ K' Q! {  MFC类 图形对象句柄 图形对象目的
0 h- |# `5 I) N* i& ]5 Q6 _  CBitmap HBITMAP 内存中的位图
4 @8 N/ _* ^  d# ^  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式
* W, y% \2 ^: M3 R  CFont HFONT 字体特性—写文本时所使用的字体7 J' F- u& b$ |' u: m( \
  CPalette HPALETTE 调色板颜色3 j8 V4 q. {0 C0 Y/ K
  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细/ o3 j2 ]9 q7 |% X5 Y3 t2 W
  CRgn HRGN 区域特性—包括定义它的点
0 a3 P) f: A6 D- r: E+ d  表1 图形对象类和它们封装的句柄( Q. \6 A; N* g) ^( F6 l2 x# h/ i
( S: b6 Z2 I6 d9 u3 B0 D
  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:
1 }% p7 }! i, G. i7 X1 v- o7 H
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:( Q& A  |* d9 b

  1. 5 b; S% A' ~& _
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)
    0 H: p# r% Z' H
  3. {# y7 O4 m* ^6 |% F- t. A4 O' r0 N  E
  4.         //设置背景色( ^' }' Y  g. q1 ~3 t8 W9 \
  5.         //CBrush CUi1View::m_Back2 z1 \3 i% A+ R: }3 p! c
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));1 j* ^; @; _% P+ y& b
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL); # h! C% u4 m" S! {$ u
  8.        return CView::PreCreateWindow(cs);
    0 |! U; k, E' z+ W# O& f
  9. }( p  Y# o+ `. `, U+ ~1 q* w$ M
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
    / n! s$ V+ f2 Q# k* N
  11. {
    , V; C; t8 F: c' ]* ~  G
  12.         if (CView::OnCreate(lpCreateStruct) == -1)
    6 w) t7 _% n  ], ^! H
  13.                 return -1;+ a; K3 e2 b5 F' T- z3 f
  14.         //创建字体
    9 i( t8 e. P0 D4 @& k' F6 z
  15.         //CFont CUi1View::m_Font& g9 M  A) P3 Q% `$ y  Q0 c! l4 r; P
  16.         m_Font.CreatePointFont(120, "Impact");
    3 o( C4 h4 T0 H- F# q% e% b) l: u/ f
  17.                return 0;: R# p/ O0 l+ u% x
  18. }
    , L" ~+ @) E3 G+ r0 G1 N
  19. void CUi1View::OnDraw(CDC* pDC)( c, w+ n3 \, f8 u
  20. {
    & n% i- Q6 Q8 q# P; G. R# @
  21.         //绘制按钮框架: R* |3 u3 j8 w' e+ @: \9 |
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);
    5 M) t3 ^& x" Z+ X
  23.         //输出文字
    ' [( c0 h7 _& M; h9 J
  24.         pDC->SetBkMode(TRANSPARENT);
    0 K8 g! K# O& O& n5 v/ w
  25.         pDC->TextOut(120, 120, "Hello, CFan!");. J( h- m5 z0 s; w- A' T' E
  26. }
复制代码
6 B, `1 N' S; }. @
  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。
  \* ?+ B" ]/ n' e/ x/ c( z% M* v( f7 S" S# V
  2.2 Windows的幕后绘图操作
0 p+ {- c6 ~' W9 \6 z5 W4 c% f  在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。
+ W6 H* r, r: a0 V  有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。
/ f6 {' D& `- A# B  所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……" {- F' a( v7 t0 F0 j. B
  有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。8 ]. k  L6 `3 z7 R% b" D9 X

6 }( X. r4 K2 G. D. g  3. 美化界面之实现篇' Q- F' H! w) j0 F
  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。( }8 v4 O, v0 ?( O( |! C

4 |; E2 E% B# C" v8 j# R  3.1 美化界面的途径
) `' a( ~+ V, n) N  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:+ s& {5 ~6 O; c. _( h
  1. 使用MFC类的既有函数,设定界面属性;  J8 N: o: n2 ]* h2 M
  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;! s. Y* d* B- t' `$ \; ^# }$ h
  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;
: u0 E, u9 \- J# ^4 a! v  一般来说,应用程序可以通过以下两种途径来实现以上的方法:# _' C( L6 b# ]2 m: Z: \
  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;$ F0 h% e1 [6 L" u5 N
  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。* A6 d% @; c5 v8 t7 _2 b! \' t
  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:
+ w  W% K- a2 M& W3 V2 C9 z  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示: 4 Y3 e6 m0 V( m% ?6 ^
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;0 S& X( W. {  f3 Z! `! X% \8 {0 ?
  以下的章节将综合地使用以上的方法,请读者朋友留心观察。
  S' w& J) k$ ~2 @9 B4 \5 P- h1 F" p! I" D' @" F8 f
  3.2 使用MFC类的既有函数. A3 f% Y: G1 f. {8 L7 ~, [7 ^* }* G
  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。6 j$ F) }( N( x" r( I

/ B& @0 U1 o" R+ `4 I6 eCWinApp::SetDialogBkColor- e' x' Z  n2 Q, k& `
void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );7 L( G8 [5 o4 e1 z  W) i+ Q
指定对话框的背景色和文本颜色。
- p1 S  e9 c" W. F  c
' K  r4 {- p$ \2 |; m' e- WCListCtrl::SetBkColor0 H- v6 _+ t- K( }
CReBarCtrl::SetBkColor
5 }8 [; i- _* j2 m% t% p! @6 gCStatusBarCtrl::SetBkColor! X  O1 [+ `5 p- |  S1 X
CTreeCtrl::SetBkColor) y" ]0 r- x& r0 m9 ?/ t
COLORREF SetBkColor( COLORREF clr );
% @# M* ~4 r2 l* ?7 A4 M设定背景色。! c# n$ U2 e5 k$ [5 C1 Q1 I9 g

6 h; Y" C9 k0 Y: L7 H$ FCListCtrl::SetTextColor& W% r- {& f& m- ?: x- ^
CReBarCtrl::SetTextColor/ X. ]8 b& t8 m# D
CTreeCtrl::SetTextColor3 ?0 t) W: R( u* v* R
COLORREF SetTextColor( COLORREF clr );
0 ^) b; c- `$ x! h6 t+ a设定文本颜色。
. |! @( w, N, e! V5 P7 W# ]; \5 G: k
CListCtrl::SetBkImage, h% [5 t' M, N7 m7 v9 j5 Q2 S
BOOL SetBkImage( LVBKIMAGE* plvbkImage );
) R8 ~! A0 n6 J. j6 }BOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);% c; e% H: N% a9 d5 ~8 q: _
BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );' E) m$ U  X& @
设定列表控件的背景图片。
7 D7 f! d) }8 U: g% f. {: h
/ V; L$ e8 e2 X5 m) ACComboBoxEx::SetExtendedStyle# C$ G! V1 Y/ W  e) ^  Z
CListCtrl::SetExtendedStyle
. G7 d0 ^! r' {# |  @; R! nCTabCtrl::SetExtendedStyle- \( _/ ^5 Y! ~) c
CToolBarCtrl::SetExtendedStyle
9 @1 V' m+ d% a4 W$ G4 }DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles );
2 a* G* i  t. `* j/ N设置控件的扩展属性,例如:设置列表控件属性带有表格线。7 ?! V" ]; O3 a& N0 y5 r5 ~0 _& J- M2 P
  图4是个简单应用MFC类的既有函数来改善Windows界面的例子: " ?* h- S/ b4 {$ }; S; z! }
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:4 d) g8 N- Z' _, V: l2 U
  1. ) K! Y4 ?4 z+ h3 R3 a& L' t) u
  2. BOOL CUi2App::InitInstance()
    " K/ F( e% p- R9 A9 p9 k8 l
  3. {/ T' O, [! V* {4 u; r9 O
  4.         //…1 P! X! T7 ?; \- c
  5.         //设置对话框背景色和字体颜色
    2 D" z! F/ I$ H3 K1 s
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
    6 d% P6 r" Y/ [$ Y( G
  7.          //…; Y$ J, E( Z" w6 P; W" d/ |, f5 C
  8. }
    " O6 C# `: A8 @0 a' m& `
  9. BOOL CUi2Dlg::OnInitDialog()
    + B/ {' i! C6 p$ h! W
  10. {- u. I7 w) J/ w8 @" V- E
  11.         //…
    7 \% J& P: K  J8 U
  12.         //设置列表控件属性带有表格线
    , g- T! L3 [# y
  13.         DWORD NewStyle = m_List.GetExtendedStyle();" m- Z# m4 t6 n2 \6 m9 Y9 `3 [
  14.     NewStyle |= LVS_EX_GRIDLINES;$ A: d5 w; x: I  S, q
  15. m_List.SetExtendedStyle(NewStyle);4 j9 c+ a9 |5 C
  16.         //设置列表控件字体颜色为红色
    ' ?2 c4 i! L  `  r" \
  17.         m_List.SetTextColor(RGB(255, 0, 0));: C7 d7 J% Y* Y! k! X* R+ N9 z
  18.         //填充数据' o! \1 R4 ]% m. @8 ~4 ?7 I$ d
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);5 w6 _/ o$ w+ p* J2 K6 f5 f: N3 Z5 |
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);
    0 F! s/ z. M" T5 F% Y
  21.         m_List.InsertItem(0, "5854165");
    8 C" n- t% U8 o6 L: L
  22.         m_List.SetItemText(0, 1, "白乔");7 g. r/ @% ]" L! g1 H' r
  23.         m_List.InsertItem(1, "6823864");7 [! L3 P* Y% Y
  24.         m_List.SetItemText(1, 1, "Satan");/ x' Y2 K2 i; Y; |% i% b
  25.         //…3 {& ^  v; [" `) d
  26. }
复制代码
" V& _$ I! {( A. U+ o8 a2 g
  嗯,这样的界面还算不错吧? 9 ]* H" K: S) ~
; r6 k% o0 X$ Q8 K& C) y/ o) M
  3.3 使用Windows的消息机制
% }  g/ y# x0 a- y- |/ j  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息: $ G& j/ _+ a& c- ~7 ~
WM_PAINT
$ ?1 U" f9 I; }6 W5 R" O8 {WM_ERASEBKGND ; `* O1 O- s# m+ H! V! E8 i* L
WM_CTLCOLOR* 8 [' V8 G  \+ ^) A0 C
WM_DRAWITEM*
% C" P( R, U4 C: R( T# GWM_MEASUREITEM* * l- o  ~! ?0 S' s) A3 o7 T  }
NM_CUSTOMDRAW*
1 `) Y3 t4 W" y  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。7 |: h4 r4 B, d# P; S7 F

1 \5 e; X/ z# @; d  3.3.1 WM_PAINT( V  u$ F& E# _
  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。5 `6 y4 W+ H/ R$ S$ O: T& s
  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下: 1 F" S/ G8 F2 U
  afx_msg void OnPaint(); ! l7 C0 ^: F' f1 L# B- \: K
  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: 9 v9 C- E+ W6 f
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单: " v; y5 D- u2 i% V5 l
  1. " p" e# S" O1 U4 o
  2. void CLazyStatic::OnPaint() ' A( G3 X' [# n2 a( M2 A8 U" ^9 I6 ^$ q  m) a
  3. {
    8 h7 K0 U6 Y* u5 e2 ^4 r
  4.         CPaintDC dc(this); // device context for painting) E( I# @+ I. Q7 ?; N1 L5 G
  5.          
    0 s8 B. B9 e  u* n, s5 M, t
  6.        //什么都不输出,仅仅画一个矩形框
    . e( c0 v' d$ U. }  \
  7.         CRect rc;. r$ }4 |" p* y
  8.         GetClientRect(&rc);8 j; ]; j6 Q+ t  e
  9.         dc.Rectangle(rc); * Y+ d5 F1 A2 |: J% e# a0 ^
  10. }
复制代码

, |* \1 m# H' e; S9 b( d) c  r. O  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。
; w2 K# ~6 \; n; A5 Q" U( a9 S. [* K
2 f$ C1 X) P/ M: `( t% Q+ N1 d  3.3.2 WM_ERASEBKGND ) i, [1 Y6 w0 F  _* `1 V" k0 R( X. I
  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。 / Y5 V0 L2 C$ A
  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:( n- R7 r3 j1 `& J4 F$ Q7 a# \; d) [
  afx_msg BOOL OnEraseBkgnd( CDC* pDC );
3 S9 u9 @( h' Y0 x8 y* {  Q- n  返回值:
/ Y8 f; ~5 J8 ]* `0 l% e* y  指定背景是否已清除,如果为FALSE,系统将自动清除
8 ^0 {7 g" F4 G3 C. U& \' A  参数: pDC指定了绘制操作所使用的设备环境。
/ ^1 B5 h) x1 m- d! F  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景:
+ e% e6 k1 U1 w' M
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单: ( T' B* [$ |+ [2 F& s
  1. ! b0 @* s* K2 P; U& r6 n& m2 C
  2. BOOL CUi4Dlg::OnInitDialog()/ _& {; M% |$ O6 {. N
  3. {4 V. e7 B% a4 B- ]) r
  4. //…
    ) R7 D( P) C: O2 `" ]# t7 Z5 C* [; |
  5.         //加载位图( }- w1 O! [: F7 E( W) o5 c
  6.         //CBitmap m_Back;  U) v2 ?  d! ?' O5 j7 z
  7.         m_Back.LoadBitmap(IDB_BACK);
    0 b5 M* x: w+ F$ w" o
  8.         //…5 n7 o6 _$ \9 C' N# u; n+ l
  9. }. _# @7 ~* A: D* L. C0 D' Z
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC) 6 e6 P6 ^& Z/ g* ^! `& L
  11. {
    : A3 o% ^3 _( L+ H% U; k/ c4 ?& u
  12.         CDC dc;, G, N+ s6 [: e4 y
  13.         dc.CreateCompatibleDC(pDC);
    1 O* d! z) B. i$ K4 L" `$ {
  14.         dc.SelectObject(&m_Back);% L* H" k0 Y  U6 v% x
  15.         //获取BITMAP对象  X# S5 T- s& @6 L7 N4 m
  16.         BITMAP hb;) ]; u5 d: h7 {% j$ ^6 \) k  \
  17.         m_Back.GetBitmap(&hb);
    8 O; ]& p( P. Q  J3 Q* z
  18.         //获取窗口大小
    + x& D5 N4 O9 z
  19.         CRect rt;
    ! H" q+ s! e" K( r8 y0 E, M' O/ ?
  20.         GetClientRect(&rt);, j# `5 |4 d5 \1 \4 u
  21.         //显示位图
    $ k7 d4 u( c( Z6 O' R1 X
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),
    ; T# D3 r/ W( Z( b
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);
    5 q/ ^! r( {( d) L: ~! E
  24.         return TRUE;9 M" u2 @3 O% J7 i+ N% q
  25. }
    - M( i" Q' X3 S  v0 N* I1 w% D
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    . }$ B- P/ G4 ^/ y
  27. {  h. ]) N* }$ c& `/ u. V1 c
  28.         //设置透明背景模式6 A2 R! F$ Z4 F
  29.         pDC->SetBkMode(TRANSPARENT);- L5 `+ T- C( x
  30.         //设置背景刷子为空
    6 w* F3 t- u4 N, {8 B
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);/ j; d8 n( `5 H) E
  32. }
复制代码
% e3 e) E& z; k9 c3 b! l# r
  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。
& W7 s$ ?5 T: n. i8 _6 c2 j& s
9 \8 o: Y0 @3 y& `; u) T  3.3.3 WM_CTLCOLOR
# }; Y9 \; G" M( B  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。 - E1 ]" O1 ?& [2 a$ a. n& n9 \/ m  k4 v
  WM_CTLCOLOR的映射函数原型如下:
  t7 l7 d7 S" G4 W- A  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );8 ^: z0 S" T( O5 n/ i/ h& a- J
  返回值: 用以指定背景的刷子   Y+ W2 n" R1 R% ?. d
  参数: 1 T! Y9 P, ?, E4 P
  pDC指定了绘制操作所使用的设备环境。 0 W% P5 G7 p0 H: K. E
  pWnd 控件指针 5 W- z* D# g& l' D
  nCtlColor 指定控件类型,其取值如表2所示:
3 Z( d* q0 f* ]- N  类型值 含义
, G, J  O/ u: O% x' PCTLCOLOR_BTN 按钮控件 % E" p% f2 P/ r" j. q
CTLCOLOR_DLG 对话框 7 `! d  u5 }# Q/ m" c+ D0 `
CTLCOLOR_EDIT  编辑控件
2 }/ P8 ^* f! g" t' K& J4 aCTLCOLOR_LISTBOX  列表框
9 {, g5 p8 e8 GCTLCOLOR_MSGBOX  消息框
/ f' c4 L4 \) _% H3 ]# F. g$ E; ~CTLCOLOR_SCROLLBAR 滚动条 ( J% R" q3 a: U! h
CTLCOLOR_STATIC 静态控件
6 |6 P% s) S7 K" Q) b/ {表2 nCtlColor的类型值与含义
, k+ h3 n4 I- C9 c3 @) `: V. `: v7 A0 @8 C. Q, h5 J
  作为一个简单的例子,观察以下的代码: % p6 @( _# f: W  H, I6 r4 I( y

  1. 8 Z$ }1 u6 [9 Q& ]) c6 }# e
  2. BOOL CUi5Dlg::OnInitDialog()
    & p9 P% N5 |' e( Y+ e1 y) d
  3. {
    & K3 }* P! V  F; Z, b- d- ?
  4.         //…! \  o/ b% o- ~+ N0 o! A) w" A
  5.         //创建字体3 S4 O1 B& n& L9 }2 `. X
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font2
    5 D# a0 N" ?6 U3 S8 @4 e3 \
  7.         m_Font1.CreatePointFont(120, "Impact");
    & ?" i: U4 f+ ~0 {. Z, l. i) z
  8.         m_Font3.CreatePointFont(120, "Arial");+ o5 t* t; T, G; P
  9.                 return TRUE;
    / l6 M* J# ]) o
  10.   // return TRUE  unless you set the focus to a control
    1 P* N( S$ J4 F3 S/ l, a$ o
  11. }
    . U+ H: U1 C3 S1 w6 o1 ~" ]
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) % e6 ]' q0 u" N! r
  13. {
    2 q; k6 h( o4 [" I: S' V+ ~
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);. ]: X, r( j9 b/ G  H3 e6 e% \
  15.         if(nCtlColor == CTLCOLOR_STATIC)
    " S' L, P" _; `
  16.         {
    3 l  ?: b- a/ Z( s- t
  17.                 //区分静态控件
    ) Z" L$ e/ |; g& G& g
  18.                 switch(pWnd->GetDlgCtrlID())8 S6 X/ _4 X, ~, B# O  G7 ?
  19.                 {5 e$ N* R/ L  w* O# F8 @6 f, b4 C
  20.                         case IDC_STATIC1:
    . I6 E( ]- e7 P. t2 v
  21.                         {9 K4 _( l8 J3 H1 Y( q- `8 V- q3 ^
  22.                                 pDC->SelectObject(&m_Font1);
    4 o" l8 r+ m3 W
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));+ @" }5 n) \1 k  N: m$ k, E
  24.                                 break;# b* e8 r) E: C! z" C; n* Z
  25.                         }
    4 c0 O7 I- E$ ?' i: B
  26.                         case IDC_STATIC2:3 Y# L) g4 K' f' }+ ~) M5 b
  27.                         {
    9 j: b3 i  X! E2 A
  28.                                 pDC->SelectObject(&m_Font2);
    3 ]* d& c. D0 A5 U0 l3 V: y
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));( B2 U! g/ E5 _5 r/ w1 N
  30.                                 break;
    . q0 G; D; h# P* D, K7 P
  31.                         }  n, p9 s& m# k3 M6 o, w7 R
  32.                 }
    2 x) I2 j. P) l6 F
  33.         }
    " }5 T9 b$ p& ]
  34.         return hbr;3 d' M! Y* `3 _+ x/ V
  35. }
复制代码
+ `6 o) K9 K' i# A$ @6 j5 w
   生成的界面如下:
5 `, f. f1 Q! x
图7 利用WM_CTLCOLOR消息美化界面

. O. l& h) y9 M. E9 m- ^9 N  3.3.4 WM_DRAWITEM
5 g. R( |) j5 V  I) @7 O6 F  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。% I9 J" k0 \& }: V3 T: S& r
  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。 . N) Z: w' z7 y6 l6 p
  WM_DRAWITEM的映射函数原型如下:
$ n* L, S+ S- I3 q4 z7 s  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );1 C" Z$ b. T6 {1 m
  参数: - z/ k4 n& I6 F* K$ _  B5 O
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 & e0 A, i, `/ t0 Y7 P- O
  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下:
0 `) N. B+ `- e( I8 I
  1. * _, v: X0 G2 s
  2. typedef struct tagDRAWITEMSTRUCT
    : o$ \, j1 a/ R) ]+ a( d9 m
  3. {: Q$ ^: i8 \+ }: ]# C& T
  4.     UINT   CtlType;  `2 C6 b; N+ K9 ?
  5.      UINT   CtlID;* ~* |8 f5 O+ y
  6.      UINT   itemID;
    1 e% k% x0 \4 x- D+ N9 G. d! p
  7.     UINT   itemAction;% w) C# m4 v! W: g- [
  8.     UINT   itemState;
    ! D& f. }) a4 X
  9.     HWND   hwndItem;* i; S0 ~6 S8 x1 o
  10.     HDC    hDC;
    8 ]+ x! y; y( U8 b
  11.     RECT   rcItem;/ c2 a) K, V# w/ }  `) c( m" \5 s
  12.     DWORD  itemData;5 V8 \% N' n8 E: A5 y& k* l
  13. }DRAWITEMSTRUCT;
复制代码
& e7 s- H  d5 ~0 _8 e7 W8 T$ }+ |# I
CtlType指定了控件的类型,其取值如表3所示: / g1 ^$ o( k3 H
类型值 含义
4 v# R  h' B/ r" x; \. T! b% I- uODT_BUTTON 按钮控件 2 D1 O$ K( f  ^' S
ODT_COMBOBOX 组合框控件   o# C3 ]- }9 N& _
ODT_LISTBOX 列表框控件 2 j  y# F% Q! m5 D
ODT_LISTVIEW 列表视图 2 [  z1 `' R3 n2 Y. A- r/ Q1 e$ Z" S
ODT_MENU 菜单项
* w; F) F7 x* x5 F- v' NODT_STATIC 静态文本控件 ; j6 D1 m  X) g2 v/ Y
ODT_TAB Tab控件 : ^" r: g  j. o" i+ C  y, y6 S
表3 CtlType的类型值与含义. s4 K3 N  H  ?5 H
1 g: T; v: o( u
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
; i8 w, }1 n2 i/ o# G3 ~  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。
) L3 g( e- x0 a2 A  L$ {/ F  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:  d$ T0 U' P. j% z
类型值 含义
9 J) k% |# G  w: a( e1 U& J: XODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。
$ C+ F; ?' n7 h, AODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。
0 f0 b& I, A- o7 }' [8 e! r/ x) {ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 7 i8 I7 m0 N4 X) v
表4 itemAction的类型值与含义
: c& ?, A) [1 T  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:
6 G: ]$ t1 w9 w类型值 含义 9 u+ l, o+ n2 F0 G
ODS_CHECKED 标记状态,仅适用于菜单项。 2 _( D6 S2 v; @9 A) v6 x/ ?- i4 Q
ODS_DEFAULT 默认状态。 7 o* p3 b( |" M9 ^) [
ODS_DISABLED 禁止状态。 ( i: j) W. n3 z! q9 Y: b+ O6 M
ODS_FOCUS 焦点状态。
: p  f0 e# v# a5 RODS_GRAYED 灰化状态,仅适用于菜单项。 0 K- B6 g$ ^: ^9 M" b3 m
ODS_SELECTED 选中状态。
7 ]! w; O) B3 |) ^$ a$ e+ xODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。
, E% U( t7 B/ YODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。
6 k' C" `, H1 c- P1 ]! oODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。 , M: l( X# ^2 z2 W/ r
ODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。
. l+ Q1 ~/ V+ `) h8 Y* E# lODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。
8 B( P* @; b& A* M8 i/ b5 _表5 itemState的类型值与含义
1 f; u3 A9 K: z1 T- W, k& L  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。 : m% [8 w0 g% L/ z0 a, m
  hDC 指定了绘制操作所使用的设备环境。
7 k" C& T/ t: o5 d) s  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 : X0 Z6 L2 F0 T  h# U4 ^
  itemData
& ~3 [+ M5 J3 ]  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
- q# I( }. n- _5 r8 P& L) @1 G( W  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 . [) W+ E6 Z! \% ~9 H# h. x0 E3 G
  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。 ) h: X- M. Q4 [
  图5是个相应的例子,它修改了按钮的界面: 6 q$ |' I( J- Z: z% `. B
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下: 2 r! _$ u: S$ y

  1. / H: D- C9 c. k2 C7 E9 d
  2. BOOL CUi6Dlg::OnInitDialog()' a: N( i9 U# {2 w4 [% C
  3. {5 W+ i/ M/ i$ y5 S
  4.         //…; K0 e& A4 R+ O) a" C( S
  5.         //创建字体: F) Z! k: ~/ x* }' N
  6.         //CFont CUi1View::m_Font/ t3 Y+ Q) x7 A; E8 ]  l  s" l9 P
  7.         m_Font.CreatePointFont(120, "Impact");- h0 ?( y$ N7 K* Y4 N& M' _9 J
  8.         //…
    $ y% Y4 y% p# |
  9. }
    & _! [6 J6 l& D9 x
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
    2 L. q3 c% o" c1 Y4 P
  11. {
    / j, y* ^; n( s2 d
  12.         if(nIDCtl == IDC_HELLO_CFAN)( u$ u9 y' v/ {
  13.         {! L- y# G8 X' W
  14.                 //绘制按钮框架
    " ]+ |. @- H" t$ q5 C, W' x
  15.                 UINT uStyle = DFCS_BUTTONPUSH;
    : o' @: m7 M) k) n, ^" ]
  16.                 //是否按下去了?
    9 v" D8 j; P1 H
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)
    , _/ }; B& t* ]% N0 f
  18.                         uStyle |= DFCS_PUSHED;9 o7 b9 g* l7 _/ o+ B
  19.                 CDC dc;
    , U5 k- @+ a/ w
  20.                 dc.Attach(lpDrawItemStruct->hDC);2 V/ Q" d2 j) j4 g5 N
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);) c1 y; [" C+ {- P3 z
  22.                 //输出文字% _; H" ~1 O$ \. Q: H1 g
  23.                 dc.SelectObject(&m_Font);
    ! N. V, H% |8 Q! W3 j& H& n8 s
  24.                 dc.SetTextColor(RGB(0, 0, 255));
    - q  q" V$ t" Z
  25.                 dc.SetBkMode(TRANSPARENT);
    4 D2 G3 r2 I& b. f( u5 e" m
  26.                 CString sText;
    ( ^+ U0 @: S( N! V" b% I0 \
  27.                 m_HelloCFan.GetWindowText(sText);7 j  c3 d, C7 L3 c
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);
    5 u# [- W0 ^6 K2 d" v, F9 O+ s
  29.                 //是否得到焦点/ B/ M% F& \8 r7 }" Y1 ~
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)
    . ?: X0 S( v  a0 u* D
  31.                 {: U" [5 t- x% w3 q0 J
  32.                         //画虚框' j6 Z2 N) U5 t% B) V' v/ c. i/ I4 N. a
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem;
    - p2 h6 N/ T( ~- p* @
  34.                        rtFocus.DeflateRect(3, 3);( t1 B6 F: v; S' U
  35.                         dc.DrawFocusRect(&rtFocus);% C0 ]! Q7 T- I) @4 ^1 R
  36.                 }
    0 a* e" j/ X8 r- x5 s3 o
  37.                 return;
    6 H8 \- v% y/ F0 u1 k5 X: Q8 a
  38.         }
    6 f& S1 Y7 U/ B% \1 K
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
    ; b: c6 e% k5 ^& c
  40. }
复制代码

; P) P# g$ H* T1 M  别忘了标记Owner draw属性:
- C$ U1 p8 X6 v0 Q. A( Q
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。 : y- H- O& C% L
4 U; R& N- V+ e  k  C4 S, `: b* Y
  3.3.5 WM_MEASUREITEM 1 |0 }, }2 r9 K  c
  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。 ; `  j6 ?! q" _  g
  WM_DRAWITEM的映射函数原型如下:
) z- p7 q4 n" [3 e3 z) F/ m3 r- h  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
: x  X$ ]6 Z6 N% o- C  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l- Y  l4 a6 h3 O) T( u) _
  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下:
4 q8 I7 n* d6 E  R" B

  1. 2 O* Q, R$ \: Q
  2. typedef struct tagMEASUREITEMSTRUCT1 `1 g6 |' u, |  t  R/ d5 J  r
  3. {3 h% r/ c- d7 Y5 `' Y. f
  4.     UINT   CtlType;- b" _( R( T& j! D( q  ^+ J
  5.     UINT   CtlID;6 M& R& V8 Z4 N& w* x0 d2 q
  6.     UINT   itemID;
    0 X+ z6 W: W" i/ e8 [/ D9 M
  7.     UINT   itemWidth;2 b' u: I0 z9 N4 u' i
  8.     UINT   itemHeight;& N" X! Q( l  h( u4 L9 W
  9.     DWORD  itemData;- v" q& ^1 u- n& s0 Z0 U
  10. } MEASUREITEMSTRUCT;
复制代码
6 g2 q1 Q. h: }$ [
  CtlType指定了控件的类型,其取值如表6所示:; `3 d4 I5 Y, w; q$ O
类型值 含义  g  d) G$ V& B$ {5 V( r( w# D
ODT_COMBOBOX 组合框控件 ( G, {  P6 u5 n( f3 u) ?) B' S
ODT_LISTBOX 列表框控件
; U7 R3 G+ p, P: k' A7 O# DODT_MENU 菜单项
: Q* L# T) N& C" Q. s+ T- G0 w5 P1 U表6 CtlType的类型值与含义
; d* X2 h6 O: A$ {+ O  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
! d' }: C+ B9 f9 x  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。
2 e8 L4 o' J, O+ f. Y  itemWidth 指定菜单项的宽度 5 N) Q: n- n% E* z# h8 M. x8 ]; y
  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255 / h2 a) U# L6 D- ?  I
  itemData
' _; B+ j* s1 B4 C  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
$ g% D5 x% `$ w& ~: C* e  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
1 O- x6 Z$ R/ b' x( \' e" @0 \  图示出了OnMeasureItem的效果:
3 r: z1 h# @& n$ `( y5 R. Q/ ?
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下:
7 t, ^! Q+ p/ _7 i0 T
  1. ! Y  n# j! T9 h; n$ Q% Y! i
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
    4 a0 P; a$ q$ v% T# j5 k" m
  3. {
    : p8 X: M" U. C  \& H
  4.         if(nIDCtl == IDC_COLOR_PICKER)
    8 I2 j. u/ R5 Y# ?
  5.         {
    % [: o( H% g3 c. s
  6.                 //设定高度为30
    ' y$ }9 V# u# {, U! @; e% e3 f. v
  7.                 lpMeasureItemStruct->itemHeight = 30;
    6 l2 Y+ t; w' K  n( g+ v
  8.                 return;( V  l4 b0 D- w9 s1 [6 Z( _: b
  9.         }2 w' R5 a1 s& e# J% Z* ]: x, `
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);& O5 K( `5 k3 F2 K
  11. }
复制代码
# [7 _# I3 J7 P  l9 ?# q
  同样别忘了指定列表框的Owner draw属性: 7 P$ |. O) P6 E. w) k5 e8 g
  图11 指定下拉框的Owner draw属性  
# m4 u, F* ?9 E6 M3 N4 M- x5 C+ |. D+ d
  3.3.6 NM_CUSTOMDRAW
. C* ~8 D4 i" q; Z/ h" _7 x  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。 . C& c7 S4 n" q
  可以反射NM_CUSTOMDRAW消息,如: ! Z) J3 R8 z  r) M6 Y4 p/ ?9 n
  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
# B5 c5 d. o- s  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult);
; D% ?  V, _0 B9 t9 I  参数:
% g0 U1 Y9 U% O! [  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:
4 {5 j$ g: Q+ ~) h: n# |* a
  1. " p7 s" I  P' b# Y  C
  2. typedef struct tagNMHDR, Q( {# F! I. g- V" i! Z
  3. {  W& N& T+ B8 ]2 Q
  4.      HWND hwndFrom;
    3 a4 _9 Q) A% l* x
  5.      UINT idFrom;
    ( c4 w" @. ?2 Z5 a! \  T1 D# s5 e/ y7 ]
  6.      UINT code;
    5 h$ P* n9 g) J# f+ T( y
  7. } NMHDR;
复制代码
/ E+ t' a6 _' Q; X6 u
  其中: 1 p" j* @9 N9 ~. V3 }) R" m8 t
  hwndFrom 发送方控件的窗口句柄
  d: M/ A! A, `- w0 Y3 k/ \  idFrom 发送方控件的ID code 通知代码
6 @% V$ a! [8 g& ^1 P" \1 h+ P  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下: 4 a! t4 f# Y% j' T# `) M, Y
  1.   K+ D: b3 E9 }( F$ R0 d0 `: m
  2. typedef struct tagNMCUSTOMDRAWINFO$ ^$ _9 l! T* O5 e  Y
  3. {2 }4 z! t; v" n) C
  4.     NMHDR  hdr;
    2 D9 A7 i6 Q/ U) A/ _  x- y
  5.     DWORD  dwDrawStage;& e, i: n7 a1 R# l5 R; |
  6.     HDC    hdc;; G8 ?/ @, ~: g) F: x
  7.     RECT   rc;3 L% M4 \" H) `7 T: C  T. P8 R
  8.     DWORD  dwItemSpec;
    , P7 T- d) a# N! z
  9.     UINT   uItemState;
    5 d  ?2 i; @& G1 s& o
  10.     LPARAM lItemlParam;
    + t- \  x9 F+ J/ @
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码

6 K. C' l9 _5 c6 S2 m2 t   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:
/ q2 U! u9 Y" O3 k4 w& }4 B类型值 含义
5 U! Q; L" q6 T6 v8 k0 }: i! f9 e# UCDDS_POSTERASE 擦除循环结束
5 X. D9 U* e* @6 uCDDS_POSTPAINT 绘制循环结束
5 J  v3 O# o4 D8 Z- Y: iCDDS_PREERASE 准备开始擦除循环
  }+ s5 Y; i! X/ [% M8 tCDDS_PREPAINT 准备开始绘制循环
+ b' M/ P! ~, f" vCDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 4 G1 f( P2 I% o5 Z
CDDS_ITEMPOSTERASE 列表项擦除结束 # Q, @" f! [+ i$ V- X2 L
CDDS_ITEMPOSTPAINT 列表项绘制结束
2 T% v# m, d0 F* F8 `* I7 m8 ~CDDS_ITEMPREERASE 准备开始列表项擦除
! c. N% [5 ^4 t3 D' n7 F4 WCDDS_ITEMPREPAINT 准备开始列表项绘制
0 i8 c9 X, ?$ M- aCDDS_SUBITEM 指定列表子项
4 i4 `! o$ a2 @) u7 G8 G表7 dwDrawStage的类型值与含义/ B% q9 }, H+ o. w3 L' N# g
  hdc指定了绘制操作所使用的设备环境。 4 Q5 L3 u1 B; t/ t% _$ B
  rc指定了将被绘制的矩形区域。 8 E: _" n& s* Z% c9 k
  dwItemSpec 列表项的索引
# ]/ Z6 C; o8 p' _; c' o  uItemState 当前列表项的状态,其取值如表8所示:# ~1 h: D7 U# L8 x; ~7 B% j
类型值 含义 # g( Q( v. M9 i- c9 D0 V# ^
CDIS_CHECKED 标记状态。 1 G7 R9 S0 ~; g/ Y! v9 K
CDIS_DEFAULT 默认状态。
5 o3 i  s- H$ d1 I0 t* eCDIS_DISABLED 禁止状态。 6 K. [9 {1 e# v0 @& J
CDIS_FOCUS 焦点状态。 , U, k4 Y, R9 Q$ ^
CDIS_GRAYED 灰化状态。 : b+ q* j1 I1 z' z
CDIS_SELECTED 选中状态。
, ]8 g4 ~( I/ [CDIS_HOTLIGHT 热点状态。 + X1 Q" E9 Q9 L* }6 ^4 D' R, Q
CDIS_INDETERMINATE 不定状态。
/ T8 g1 S2 C) ~  d0 ^6 KCDIS_MARKED 标注状态。
! _  W* j+ g) E- R, W表8 uItemState的类型值与含义2 R0 m0 y2 \/ c+ {: |! G
  lItemlParam 当前列表项的绑定数据 / T) W; m; K) J6 L; q
  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage:
4 N  n6 y2 m5 n: V  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:$ \9 \* c  k+ v* }
类型值 含义
- b3 O# `! j5 V& t# \4 V, Y0 B/ c- uCDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。 9 d5 {; F' W) Z7 W
CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。   I5 |/ r3 Q9 Y8 R
CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。 ( {# d: F) ?0 w4 `
CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。" ?/ X# X9 M6 H
表9 pResult的类型值与含义(一) ! E' V) v0 c: C. [6 F
  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:
' B( c; v3 G$ t- a类型值 含义
0 }- n: K' c+ ]8 FCDRF_NEWFONT 指定后续操作采用应用中指定的新字体。 " j# s. u9 Y' W; D7 d+ Z3 y
CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。 ) n( Y( b/ J  x
CDRF_SKIPDEFAULT 系统不必再绘制该子项。  q9 a' Z1 T& i& c, V5 I8 q
表10 pResult的类型值与含义(二). G: x0 @) @6 G7 G) ?  Q  t
  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子:
& q% P+ J9 A# [. b- V
  图12 利用NM_CUSTOMDRAW消息美化界面
7 ^# m: E+ Z8 b1 I  对应代码如下:
) H6 Z3 Z- x2 L

  1. 2 e$ K, t0 G4 Z
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
    1 L3 e$ R/ D! W9 W" |
  3. {
    % @& O+ r9 n6 f2 x" ?2 f% M/ ^
  4.         //类型安全转换* Q) Y' E4 U" p5 f/ e
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);) p4 t/ R& ^% v9 Y1 ]+ B
  6.         *pResult = 0;
    ; c5 M3 j5 d' F6 [6 J& x: \9 _* x! u
  7.         
    & j0 k1 D- U; J# F
  8.         //指定列表项绘制前后发送消息' E! `! X: l. R# r2 q
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)* J* |8 K9 n% Y  E, w, y
  10.         {
    4 r( U& R- B$ B& n$ w/ {
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;  Y- i# Y( a) t# F
  12.         }! q* ~( ]( |" E, y/ t# E
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)
    / R7 o% {, P  A8 m2 B' ^
  14.         {
    & i7 b( Z. i2 h2 t1 C- g1 Q
  15.                 //奇数行
    ) O& e* O/ ]0 S! v6 @
  16.                 if(pLVCD->nmcd.dwItemSpec % 2)( o* A# E3 X3 g% E: _1 A0 l
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);, I' j; U3 s6 s
  18.                 //偶数行
    ( Y+ u' ~, |' g  S4 [$ k
  19.                 else
    8 p9 G1 t7 s! X* q2 c
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);8 t2 j4 x! e: v6 W4 s9 A
  21.                 //继续0 N( j. w" m2 N/ |- f
  22.                 *pResult = CDRF_DODEFAULT;) D. y! G$ ]2 Q9 U
  23.         }
    " S/ F+ ]1 R: k) j5 I% @
  24. }
复制代码

8 [+ C, m+ ^$ i9 ?4 i$ o  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。
: P* B% @3 Q1 s4 N- x8 C( s) v5 F5 w' j' ~9 M( ~% d# s& K
  3.4 使用MFC类的虚函数机制
2 J3 y6 L. n  q  ?* G6 C  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子:
- \) w( w" a4 ^- J* Q1 Z/ e2 x& h; D
  1. ) K9 ?1 F% M: Z) a9 }# q
  2. void CView::OnPaint()0 v% h+ |8 e; y7 v4 Q8 O; }
  3. {
    : ?9 }+ g& O7 v# D
  4.         // standard paint routine9 |; e& }7 s8 x
  5.         CPaintDC dc(this);- Z- P! e9 K, X! o+ F' s, O1 ^
  6.         OnPrepareDC(&dc);
    ; ]; C! F+ A: R& G5 `% X8 n2 F5 l
  7.         OnDraw(&dc);
    6 C! v* w3 s) Y+ {/ F  i
  8. }
复制代码
6 \  {4 r4 B; _) J" k  k
  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。
) m1 z8 p& B9 H6 a% L; I' D  以下列出了与界面美化相关的虚函数,参数说明略去:
7 r4 S; W7 \+ T; N( H* }7 \CButton::DrawItem
9 Z; J0 g. _( H1 y! XCCheckListBox::DrawItem
/ @' q* ~' l' v8 I+ W5 Q8 YCComboBox::DrawItem - a. Q7 d. U3 D5 b7 J' R
CHeaderCtrl::DrawItem
7 C8 Y+ l& P, c/ f2 i( V4 z5 h% H. JCListBox::DrawItem 4 G; N* t! P. J0 U) J
CMenu::DrawItem
2 a0 d4 I/ a* H( UCStatusBar::DrawItem 2 _6 \; s5 B( R! `1 t6 B
CStatusBarCtrl::DrawItem 1 v/ Z/ P5 g9 c& n! l: j
CTabCtrl::DrawItem% V8 p; ^( P  I2 Y9 F
virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct ); ! e' _7 A& H4 f" D) B7 `* z
Owner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。, {! h0 E7 y( a: C$ j; J1 j
  限于篇幅,在此不再附以例程。 / @% b* F6 j1 Y& c: M

/ Z- j/ i+ d( e$ n* |6 x5 r参考文献3 |4 V9 E! p* s9 D8 D$ U* p
本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2025-8-9 03:07 , Processed in 0.037481 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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