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

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇0 H& w& o4 F3 N% t1 U1 ^
  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。. {' z7 E& p, e
  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。: p8 w+ P6 b- x! q; k. _

1 V/ K/ l9 a- e+ [! R6 F9 V  2. 美化界面之基础篇
3 @' g  O4 _2 [7 I  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……% }/ Q) D7 s( Q, {6 a6 X# u
5 _, q8 ~" t4 I$ B6 o% m
  2.1 Windows下的绘图操作
, C- h/ r4 i$ I, ~1 y$ w0 F  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……
, _0 |; C0 k8 h9 K- p  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。: f- @" P- h  y2 [  s: i2 U2 i

& `0 p9 |3 |8 z* Y) |* v  2.1.1 设备环境类
& z% p8 `3 g+ G3 u( q2 u  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。
. |' e: }; i% Y$ }  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:2 z2 \9 p- j1 Y- \( r1 J
  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式
7 a* C( ~8 ~; d$ E) i. ?2 H  Mapping Functions:映射操作
1 o' H; l, v$ z, Z4 w  Coordinate Functions:坐标操作/ o; f& O! z: _8 m3 x" h
  Clipping Functions:剪切操作
( v: W4 {9 g$ w) Y' D  Line-Output Functions:画线操作
) F" U) U! P  Q9 ?  Simple Drawing Functions:简单绘图操作,如:绘制矩形框
3 [4 m4 o2 F* |% m* n  Ellipse and Polygon Functions:椭圆/多边形操作
, ?0 j3 l' I6 J! s* f: ^" Y+ l  Text Functions:文字输出操作
7 H0 X- O& B6 }6 S" i, x  Printer Escape Functions:打印操作
& U6 y6 h  T9 |1 C  Scrolling Functions:滚动操作
5 w( O4 v" n/ \7 a  P- `8 i  *Bitmap Functions:位图操作
0 `& T+ I" q% y6 U& D  *Region Functions:区域操作5 z& k# |% I7 `6 V# z% h
  *Font Functions:字体操作" J. T0 A  @4 p' \& R
  *Color and Color Palette Functions:颜色/调色板操作# U& O* e6 T2 Y
  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。* }0 L  x. T! O1 c
: ]1 ~. T6 \+ ]
  2.1.2 图形对象类
- c, J# |7 O4 r( ~8 \  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。
9 v9 g* u# }( B" _& z! m+ u; c0 Z  下面的表格列出了MFC的图形对象类:
8 _0 c# M6 e# z3 W4 H. m$ v5 e' h  MFC类 图形对象句柄 图形对象目的
  B7 J- s9 ~  @2 f  CBitmap HBITMAP 内存中的位图, o: n2 |7 Z: r$ Y. Y
  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式4 B6 h5 l9 r9 V" c* u
  CFont HFONT 字体特性—写文本时所使用的字体3 e7 z! T/ l+ E
  CPalette HPALETTE 调色板颜色. F: [% {* V9 N# g0 O' I6 X
  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细+ ]7 K/ N# a1 P% ^  Y
  CRgn HRGN 区域特性—包括定义它的点
8 a% s# l$ }) f5 y4 x- p  表1 图形对象类和它们封装的句柄
& o$ E+ ?1 K7 K4 t8 I
$ s% S7 ~# X* E4 A+ w+ t  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:
% T9 A& I; _! |0 x* |6 L
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:$ A! ?, o9 t1 X* l

  1. ; E: C( y' e. Q  Q4 t; V
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)
    ) X, w9 ?- h9 @! U
  3. {9 |8 {3 z3 o( L( z. o
  4.         //设置背景色
    ) y# ?$ Q4 h' c, ?2 j+ O
  5.         //CBrush CUi1View::m_Back. q" n% L' c8 p: }
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));
    / h- S! {8 o! u
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL);
    " @4 N5 s7 |' R! U. M+ e( j
  8.        return CView::PreCreateWindow(cs);
    ( y7 @- A- K3 T2 l3 E' H  w* u
  9. }5 k/ Z+ O  u1 E8 q! W# K  H$ W
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
    : a% ^9 j, V& ~+ G# [: h
  11. {
    * G0 `" L1 s! o" D
  12.         if (CView::OnCreate(lpCreateStruct) == -1)
    $ ]& T5 O* P# a( z
  13.                 return -1;
    5 o' m7 Z" r$ W) i
  14.         //创建字体
    # h! w# J4 p: }& z. B
  15.         //CFont CUi1View::m_Font
      Q9 U/ \' Q: ^" D3 B1 Z9 S8 p: D
  16.         m_Font.CreatePointFont(120, "Impact");
    3 [* k! `- F7 n- F8 l0 j+ a! W# Y
  17.                return 0;
    3 C: o: T  X' F7 F1 q' Y, K- {
  18. }# h, E8 b+ h; S# w$ w8 O3 B
  19. void CUi1View::OnDraw(CDC* pDC)
    # }7 f) v6 a, b6 o# K* s
  20. {  j5 H2 Z: G/ A# R+ Q& g4 j
  21.         //绘制按钮框架
    ; ^& F" J# ?# X1 m) }0 m3 Y& f
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);( s: e; y) x' T! c: N
  23.         //输出文字- k6 O" A: `$ ]1 a. s
  24.         pDC->SetBkMode(TRANSPARENT);
    - P# c& v5 c7 @4 |
  25.         pDC->TextOut(120, 120, "Hello, CFan!");" s5 ^8 a# }0 r: V. c2 v9 T3 T
  26. }
复制代码
) L6 O; U4 _- t- C# s
  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。 / w; r3 R; b6 o
. B; x% i" A0 r8 E1 Q6 n' U
  2.2 Windows的幕后绘图操作
. w4 }' G# u' T7 a- X! _5 W  在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。
! f+ N* z9 h- L# ]+ F# a& e  有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。
9 @1 c1 M9 @; X$ b) l  所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……/ A9 h  G. _2 a" N- X
  有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。! }# c9 G& T1 N4 q
% {/ V8 U* L+ m5 y
  3. 美化界面之实现篇) B/ E3 w; Z0 [3 d. D
  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。2 X1 t; {0 h( F6 l/ n

7 |" F9 F( E6 R; b6 }  3.1 美化界面的途径
$ j: y+ W6 ?/ F' _. |  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:& ^8 X9 C9 S& j* ]; `& A
  1. 使用MFC类的既有函数,设定界面属性;8 A' q8 ~& F8 R3 ]+ v; ]0 a
  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;
& f/ q, L0 y& Q- t* b2 l4 t  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;
% D+ ?3 _! j# j6 K  一般来说,应用程序可以通过以下两种途径来实现以上的方法:# g# k, \" `: X. k
  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;/ l+ P5 f. |$ t8 F# H# ~5 H
  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。
' ~# O# f- n9 Y  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:% j/ h* g# p$ q2 Y
  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示: 0 N" w) Y6 i5 d' ]8 O2 o4 Y' m
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;: ^# _; K% K: t) K7 r3 a" }, F
  以下的章节将综合地使用以上的方法,请读者朋友留心观察。% u& @* b4 }# M* V

3 @9 s4 Y8 |  ]9 ]/ Y1 ~  ]$ M  3.2 使用MFC类的既有函数
3 Z! B8 m; ?, b  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。$ b8 |: @3 w; s  D" d8 P

9 u% k4 q& s  \# w$ dCWinApp::SetDialogBkColor. f0 K3 ~- I$ a9 v! H
void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );
* h6 F% c: U5 o指定对话框的背景色和文本颜色。' B0 U: Q. p* F; u7 m9 `

3 a9 A3 g- p. r; Z. _+ KCListCtrl::SetBkColor
* x) d; o! r5 Y/ S9 V! z1 [. JCReBarCtrl::SetBkColor
: H  G' b$ r6 s, `: tCStatusBarCtrl::SetBkColor8 E. T9 G0 w' W( q
CTreeCtrl::SetBkColor" g7 x9 Z5 X7 R3 v* w$ F0 S% W
COLORREF SetBkColor( COLORREF clr );+ [# x0 f1 ~4 }0 {8 C6 Z2 N
设定背景色。/ u  f! l3 U& R) u3 B! }5 D& _7 L

% X1 p. W. ?# W& \: h) C2 y( w% oCListCtrl::SetTextColor
6 n. |( _7 g8 X! t$ oCReBarCtrl::SetTextColor
% M) d5 E  Q1 O0 S( R- qCTreeCtrl::SetTextColor
8 T2 J! h  A( e4 Q6 s$ _' A2 xCOLORREF SetTextColor( COLORREF clr );
- ?' M" ]5 C7 v' c设定文本颜色。
8 Z' {( e6 Z1 O" z# Z
9 r6 N2 B9 G1 C: y, J6 j: @, {CListCtrl::SetBkImage
# t6 v# Q3 F' F& ]BOOL SetBkImage( LVBKIMAGE* plvbkImage );
+ X! c0 q3 O7 N% m+ UBOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);
# C* u( S( o# v" O5 sBOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );1 o" B% W3 T. o' Z# ?
设定列表控件的背景图片。
& _9 _2 T0 v1 h/ R# x  _7 w
6 u0 i3 w4 d  o/ sCComboBoxEx::SetExtendedStyle
5 o( E+ U0 u& wCListCtrl::SetExtendedStyle2 E6 }+ t" d1 b% G9 ~3 X
CTabCtrl::SetExtendedStyle. Q$ _, {5 o. u! B. _: T
CToolBarCtrl::SetExtendedStyle   v+ f/ z! z$ j: R
DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles ); ( H/ ~$ C4 r8 ^& s. D% q
设置控件的扩展属性,例如:设置列表控件属性带有表格线。, [( w! d; Z. v# t) N. d& [
  图4是个简单应用MFC类的既有函数来改善Windows界面的例子: 0 @  N; X. d6 L3 h/ [% |
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:
, @; ^2 w+ d7 k. M" }0 a0 e

  1. # S4 h: T' p$ O, |5 q  u3 i$ F; D
  2. BOOL CUi2App::InitInstance()
    % T0 Z1 }1 o# m$ w+ Z$ f/ ~  u8 |
  3. {
    % _' c1 k. e. x* }, }) R
  4.         //…9 ?8 [6 {. z# R! D- v/ f
  5.         //设置对话框背景色和字体颜色! k: y4 b  \* i2 f* k6 N
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
    7 @$ ?* R! M2 v% M
  7.          //…  ?8 g1 P7 }. J5 p" l4 C" N5 o8 @
  8. }
    * _, Q- r* Z: c$ H0 _: z. C
  9. BOOL CUi2Dlg::OnInitDialog()
    7 Q2 L2 S3 i! @1 O5 H
  10. {
    & b0 ^7 K5 I* Q. {& b
  11.         //…
    - H4 ?0 \+ V% c' U. h
  12.         //设置列表控件属性带有表格线. s- q2 j6 x' K: f* }
  13.         DWORD NewStyle = m_List.GetExtendedStyle();
    7 o) K% l4 ]: l. j- Z
  14.     NewStyle |= LVS_EX_GRIDLINES;7 b3 G1 s; c2 T' ?
  15. m_List.SetExtendedStyle(NewStyle);
    " a6 X3 G2 H% `! B, a; ~  }
  16.         //设置列表控件字体颜色为红色7 y$ N9 O* t) V  H7 f% w( X$ {
  17.         m_List.SetTextColor(RGB(255, 0, 0));$ G( X' q, A/ n5 x, e) C* O7 V
  18.         //填充数据
    # [  h- ]) F) }# B
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);
    ; W( g" B6 m8 h! a& g! R. d' r, z6 H
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);5 I1 q1 F4 ^9 V  O2 i" x6 q
  21.         m_List.InsertItem(0, "5854165");
    , \, Z( C; m. j4 H  @- y
  22.         m_List.SetItemText(0, 1, "白乔");: b4 k1 `( r" ?) a
  23.         m_List.InsertItem(1, "6823864");0 y( I( i1 X6 ^+ L# O0 l. o! ]$ p7 d
  24.         m_List.SetItemText(1, 1, "Satan");* r- M  `& v( _2 ]% H4 r
  25.         //…5 L+ S1 ^6 ]  w" Y' P7 g/ L
  26. }
复制代码
0 e) u, v/ R$ s. `3 z  d/ q7 V$ P
  嗯,这样的界面还算不错吧?
( a4 z3 y" l) C" x, J: Y! ?
: i2 _7 _; G" ^  3.3 使用Windows的消息机制
& G' A1 w7 p2 ]( W2 S$ `  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息: ; O: X# I( Y6 M8 X# p1 N' E+ ]) l
WM_PAINT + {0 m- J& A/ C) X- s
WM_ERASEBKGND
3 q. u* e6 O) T- kWM_CTLCOLOR* + ~: k: a4 ]3 f, Q
WM_DRAWITEM* 5 W6 w, P+ @/ J& e' }- z6 S
WM_MEASUREITEM*
, M+ ^3 k: w/ p- I7 ~) r: \% aNM_CUSTOMDRAW* 3 ~' g* Q6 Z" p
  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。6 U2 G# M7 S; o+ r* y
; C4 H6 s. q8 ?5 }2 v
  3.3.1 WM_PAINT2 ~8 M$ H! b2 r. N
  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。# D" A: O: m* S# r' C) \1 @& @+ [
  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下: + ]* w9 Q& ?3 R; N0 h
  afx_msg void OnPaint();
7 t" Y/ G6 p/ B$ j2 I' A  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: 7 r, E- ]" f! ]0 H
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单: ( H2 t- K/ o1 I7 c; T% {: u2 k

  1. 5 @2 ~+ p! O( r1 v- O. f
  2. void CLazyStatic::OnPaint() ' E. f+ q$ o+ b
  3. {! g: O! T) h% [1 V6 w
  4.         CPaintDC dc(this); // device context for painting
    * S) r4 S! c0 A
  5.          
    2 `& h& x. w1 r( R
  6.        //什么都不输出,仅仅画一个矩形框* Z+ B6 U0 W0 ]0 J4 g
  7.         CRect rc;
    : e5 V6 @) \" M, k; [
  8.         GetClientRect(&rc);
    2 j/ F2 q: N! w3 n/ P. Q5 @
  9.         dc.Rectangle(rc); " n9 t& M" T2 l! H0 Y$ H3 j; z
  10. }
复制代码
  J5 p9 P3 o5 X, w8 Y/ R3 K) M
  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。! i4 z% \& ?; j( N8 D

! U2 C2 G7 R' e  h) w6 T  3.3.2 WM_ERASEBKGND
: y5 R1 A# i: N) r  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。
, D1 W: a0 E5 v0 ^7 A& K0 E4 H  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:
7 d9 E* N* }' _% F5 P# g3 S' v  afx_msg BOOL OnEraseBkgnd( CDC* pDC ); / n! ?$ J* o8 Q/ W
  返回值: # J* B) S, C6 h: b" k9 b
  指定背景是否已清除,如果为FALSE,系统将自动清除 . C$ i/ x# r8 F; f* w
  参数: pDC指定了绘制操作所使用的设备环境。 / Y- w& D* u6 Y; g
  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景: 2 y7 s- u+ s5 H/ k
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单: 7 u" W. Z2 x9 i( a% h' g7 {. h3 W- y

  1. , ]9 {' J; @  _: g( T' B/ p
  2. BOOL CUi4Dlg::OnInitDialog()
    ' j7 z: ?6 v9 G# Y' C* U  M8 a! }
  3. {
    ) {7 T& l( L  W! {
  4. //…& |4 e5 C/ C! @  d- e  U
  5.         //加载位图9 g& O: l' J+ {
  6.         //CBitmap m_Back;( ^% n7 T. P2 o2 L: @5 I! _/ m
  7.         m_Back.LoadBitmap(IDB_BACK);: v' |' t. B9 ~
  8.         //…' r, z0 l7 V% R" `1 q: t1 U
  9. }
    6 Z. J. z9 C% @; X+ \* j
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC) ; t8 Z6 z+ K9 D" f
  11. {' [/ B! ~0 n6 [0 [% I
  12.         CDC dc;
    6 H$ L0 O: w# h2 ~
  13.         dc.CreateCompatibleDC(pDC);. d  C. l* g: o+ @
  14.         dc.SelectObject(&m_Back);8 P2 F( ^, U+ u, t7 F5 G6 |
  15.         //获取BITMAP对象
    8 [* c; i% W! B" f# z0 `
  16.         BITMAP hb;) w. g/ x( ~* x/ ?" d. |$ H
  17.         m_Back.GetBitmap(&hb);5 I- @4 p  d! p2 S6 p
  18.         //获取窗口大小
    , ~8 ^0 S/ C* b" c8 `% y
  19.         CRect rt;: _7 n9 u. Y8 x# A2 t
  20.         GetClientRect(&rt);
    ) l" I; u1 z% x4 M0 u. c8 U" t, L! F
  21.         //显示位图1 M# g0 I2 @# m# f" O
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),
    " T; K$ [$ a, _1 g( g0 X& \8 I
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);& `, i! o+ I/ r1 s2 k5 v, F
  24.         return TRUE;* O- ^& \0 P- C6 P1 r
  25. }
    ; \6 M. U4 w# m" p" L0 E3 I0 ~
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) * K) B' i% X+ |' D4 o3 n
  27. {/ l6 V' ?" q+ `; R5 T
  28.         //设置透明背景模式
    " L/ Y0 A* E+ H2 g
  29.         pDC->SetBkMode(TRANSPARENT);
    , q1 D/ m! l6 g( G7 J1 U
  30.         //设置背景刷子为空2 E3 j+ {+ Z- {) ]7 N& e: g9 d
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);5 x4 p9 G! D; Y4 u: ]& o6 f
  32. }
复制代码
( N; `7 b' P- r1 {& H  h5 m! e
  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。
7 y+ {! s6 I0 q' A
1 E/ m- K1 L; z* x7 h! u: i  3.3.3 WM_CTLCOLOR " a6 W/ v- ?7 a; v+ C$ n/ \0 \3 i
  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。
' s5 w9 I% E% H( ^0 y+ Z7 d  WM_CTLCOLOR的映射函数原型如下:
; N4 D& C( H1 a2 H9 v  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );/ r' D; X- m4 g; W) M  U
  返回值: 用以指定背景的刷子
1 a5 [7 I& q7 r2 ?: k, Y+ U  参数: " M/ p, O- `% e
  pDC指定了绘制操作所使用的设备环境。 ) s$ r/ V3 C+ a1 P0 I. V
  pWnd 控件指针 3 h3 @. q# a/ r6 t/ r+ V# b, o# A
  nCtlColor 指定控件类型,其取值如表2所示:
( [2 B4 s2 [% c6 T# F' P  类型值 含义 $ t: \. l; `& L: U( u" C9 g
CTLCOLOR_BTN 按钮控件 ( B6 Z* q8 j5 l( N2 m7 x0 D$ c8 n
CTLCOLOR_DLG 对话框 / A2 n7 D" g6 j
CTLCOLOR_EDIT  编辑控件
: V2 g& N! N' OCTLCOLOR_LISTBOX  列表框
3 Y7 w0 x& z4 W0 B) }" j# `( ECTLCOLOR_MSGBOX  消息框
0 o( w0 H. W& i6 {CTLCOLOR_SCROLLBAR 滚动条
8 D; o6 S: M0 ]$ I' T* Q, ]* P# XCTLCOLOR_STATIC 静态控件
4 i: j7 E1 y; o1 M表2 nCtlColor的类型值与含义' z  }) D1 J2 @7 Z( T/ G
" }0 U7 V6 ]* G
  作为一个简单的例子,观察以下的代码:
4 e; a: z5 r3 D7 {; a

  1.   J& f3 E0 |& G% n' ?
  2. BOOL CUi5Dlg::OnInitDialog()3 b+ H! R: [$ k5 z( l: I$ K( m; ~" ]
  3. {
    8 Q4 }( s  e7 K+ M, N. k
  4.         //…: |/ D& A; x: q( q2 p
  5.         //创建字体+ M2 P8 \9 L) M4 a5 v. `
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font2. W, e7 F6 P9 E! s" j4 J, i# y
  7.         m_Font1.CreatePointFont(120, "Impact");
    & P5 F  D, _1 ]; @# @* D
  8.         m_Font3.CreatePointFont(120, "Arial");
    ' H1 F: z* [8 {) T
  9.                 return TRUE;
    , W9 k+ n8 P* U' l* x- G& ~1 e
  10.   // return TRUE  unless you set the focus to a control ( S. Z' a% j. b0 g$ Q' F
  11. }1 q; o. W( f& ^" Y1 x) d# d
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    $ }2 I# ~, c* G( C$ \3 }
  13. {7 \) g! z4 f, `# c
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
    % z6 K9 b6 n: R3 g: [
  15.         if(nCtlColor == CTLCOLOR_STATIC)
    . J& \$ @1 B! N" u1 m
  16.         {
    + R8 j3 }1 w# e* \8 i' _1 T' S1 _- @
  17.                 //区分静态控件
    " [" F  j, M& Q0 ?' V( N
  18.                 switch(pWnd->GetDlgCtrlID())1 L  K/ e* V# d( j
  19.                 {
    # y0 g5 R7 |# b. S; m
  20.                         case IDC_STATIC1:
    9 n  v$ @# M! Y  n
  21.                         {
    & z6 ]. Q' c! o) L
  22.                                 pDC->SelectObject(&m_Font1);! o9 }2 u) Q: Q$ L4 x! w2 k7 ?
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));2 o: I6 [* {! v$ Y+ l4 o
  24.                                 break;
    6 l, j! J+ f2 ~$ n. q3 N2 A
  25.                         }
    " i; b* h$ n  ^# N
  26.                         case IDC_STATIC2:
    4 T* [; W  a4 J- B- O; U# L3 ~
  27.                         {
    7 A2 n0 s! B- @; W
  28.                                 pDC->SelectObject(&m_Font2);
    ; y6 |7 P3 ^1 e8 o
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));
    $ z' t3 Z+ q7 ~* N3 R
  30.                                 break;
    ) z' q" v, @/ o! U  N
  31.                         }
    3 C1 R* Q9 A' M8 f+ n
  32.                 }
    $ O3 @# J( G+ S% W
  33.         }
    ; ]: U( A# s- L7 u. g; e
  34.         return hbr;* G) ]* R3 E1 B5 B' V
  35. }
复制代码

9 }2 T0 k7 ]5 p2 m1 r& m   生成的界面如下: & L, R7 B' n- {- x$ V
图7 利用WM_CTLCOLOR消息美化界面

: `- ?/ ^( W: k2 `1 b$ n  3.3.4 WM_DRAWITEM 0 K9 h7 W3 w# q$ |7 v& p
  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。
0 x; E7 A( J$ ?  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。 $ X8 A) B3 o, H; ^+ U* f. G' _: O
  WM_DRAWITEM的映射函数原型如下: , E7 |( t# s) g/ S
  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );
; W6 U5 P  O; R6 b6 X# h& D0 _  参数:
) Z5 S. O9 }. T( C  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0
& P' D/ c- ^7 h0 c% p  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下: 8 S) l- K& ~' `- T# w$ e% X5 E2 n
  1. + E/ T3 Z- G+ p5 u) d) z; R1 m8 ]" J
  2. typedef struct tagDRAWITEMSTRUCT/ ~. q0 x6 \+ i+ ~1 @) z0 \
  3. {
    % f" T+ R) \: d/ Q
  4.     UINT   CtlType;
    3 Q8 B# T% H! r1 ]7 V
  5.      UINT   CtlID;. u/ R. {5 Z* Y! o2 J: o7 q5 k
  6.      UINT   itemID;
    5 t% O6 X: m% E( W  |
  7.     UINT   itemAction;! ~& {/ G. @* _/ E1 p! u
  8.     UINT   itemState;
    3 M  W- c, c, m. b
  9.     HWND   hwndItem;
    * t+ g7 S( z5 |$ a
  10.     HDC    hDC;. f; Y8 V. {* A) F
  11.     RECT   rcItem;
    ' U7 p/ |1 W! x' I- P5 Y3 }* [2 q/ I
  12.     DWORD  itemData;+ `  y9 i  ~* D1 u/ w: _
  13. }DRAWITEMSTRUCT;
复制代码

0 }, v, ~2 M$ r/ ACtlType指定了控件的类型,其取值如表3所示:
# M1 ~" x! Q6 d! R  K类型值 含义
& z3 I& |( }' h; E3 a! }ODT_BUTTON 按钮控件
& x- M+ l. w2 Y  F. E9 a  ]ODT_COMBOBOX 组合框控件
7 O# d5 ?, h. X/ ^1 m, uODT_LISTBOX 列表框控件 ) t8 C) F! u3 w! M3 E7 g, U
ODT_LISTVIEW 列表视图 + o$ }: E0 @0 x! }# ]% V
ODT_MENU 菜单项 % O$ Q( F2 M  m1 r, k" v  u
ODT_STATIC 静态文本控件 . w9 f& ]3 {: L6 f  c* R0 o
ODT_TAB Tab控件
8 z+ U6 i% M% A8 I& `表3 CtlType的类型值与含义+ H& G9 I' T: `% C* M
2 J" f6 h  t# T! v( H' t
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
  T; Q( s6 R" Z/ g  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。
4 O8 e! V: S% ?$ K1 {4 k# k8 B  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:
  M) P+ O2 V* i  e- H# `0 H5 q( q1 s( ^类型值 含义
9 Z  J# ^0 |* f+ [# ?9 T' t' [! yODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。
7 m4 d5 u1 w2 v" T# `* O6 `% W( ~ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。
+ f, U) k/ A2 q; D4 q4 @ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 $ ?, s# B& _) ~- ~& P
表4 itemAction的类型值与含义
! _: k6 Z4 P! J5 D; k5 d, u' u  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:
' `7 Z* v  Z  h7 O' B5 ?; e. y类型值 含义 * g7 x+ \5 I' l' l  l) W9 {
ODS_CHECKED 标记状态,仅适用于菜单项。
! Z$ H$ `7 n7 S  g, W4 z+ v* f$ gODS_DEFAULT 默认状态。
5 o5 r3 u0 \5 g1 mODS_DISABLED 禁止状态。 : }" n; s, ~+ s+ o+ [: J- t9 @
ODS_FOCUS 焦点状态。 7 w" J5 ^' P4 s4 j) I+ u" u4 k( }( f
ODS_GRAYED 灰化状态,仅适用于菜单项。
) l& _6 B7 E% aODS_SELECTED 选中状态。 " v- I6 F3 ~4 R
ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。
; V( O  U+ @- L1 h5 @* I1 r- ~# TODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 " R" _1 T# A5 I2 t2 U( u2 x9 F& n* Z
ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。
! V. J7 `( s  O: [+ B) W, K7 D! n% mODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。
; N0 f; k* u7 p; E' m2 J2 u; dODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。 8 X' L! e$ s7 L# N; t$ D
表5 itemState的类型值与含义
0 D" S4 h  _1 b2 G  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。
' T' x2 e# n8 `# S/ r  hDC 指定了绘制操作所使用的设备环境。 : ?# A9 V; o$ t1 r: S
  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 ( s1 o5 G: b% ^" N! g" o3 r
  itemData
+ j2 t& Z' k% e& r/ _6 w# ^) w1 Z  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
, ]$ E* w) R2 ]8 }  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
6 c7 T7 T  }$ A5 T  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。
6 e& X/ k! X$ u  图5是个相应的例子,它修改了按钮的界面:
2 `7 [/ b# Z( {: H
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下:
2 u4 N& p7 C7 d3 M( J
  1. 7 N; e# W& d+ t6 t6 A7 z& e9 M
  2. BOOL CUi6Dlg::OnInitDialog()7 Y% G% B- p/ w  O! P+ g2 G6 Q1 n
  3. {
    ) x5 A5 z0 D2 z- Z, Q% H
  4.         //…. D/ z9 w1 M, S
  5.         //创建字体
    7 [; f0 _) d) Z* G6 r% ~. _
  6.         //CFont CUi1View::m_Font# p- y* {! q+ R- w7 W( H6 S% N- [
  7.         m_Font.CreatePointFont(120, "Impact");5 H6 B/ k5 U' _, \, V* Y
  8.         //…
    : p0 Z0 x8 {1 }1 ^  B
  9. }
    # A9 G, w! G6 d4 i7 e  C# c7 k
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
    ; k* t# H; Y$ k
  11. {( P$ l6 _9 y& H1 y- c
  12.         if(nIDCtl == IDC_HELLO_CFAN)# H( A, p5 F, B0 K+ \
  13.         {
    0 d, w$ ]$ U, S0 i7 e
  14.                 //绘制按钮框架
    # O( Y+ F" ^" P
  15.                 UINT uStyle = DFCS_BUTTONPUSH;
    / L% c' J$ b! }9 c; d: P3 K1 V) S2 l/ Q
  16.                 //是否按下去了?
    ! I. Z2 D; X2 L$ m' d0 e
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)
    8 I% o% ]. ]$ A* h+ o" x/ n
  18.                         uStyle |= DFCS_PUSHED;8 V1 \- }$ ^) }6 _7 o7 C; Y; X' O* Q
  19.                 CDC dc;
    3 T5 a+ s* ?. p9 b
  20.                 dc.Attach(lpDrawItemStruct->hDC);
    1 o5 `2 T3 y% y7 \( c; o5 t) \9 c
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);
      R8 y" h& W# f
  22.                 //输出文字
    2 a; ~9 L' ~$ \( T: x. c$ b
  23.                 dc.SelectObject(&m_Font);
    * }3 w6 k9 ~' o- a/ b
  24.                 dc.SetTextColor(RGB(0, 0, 255));
    8 U9 X6 Q! b* v5 A0 g! @/ u% p+ m
  25.                 dc.SetBkMode(TRANSPARENT);' _' s& B: E" U" V! \
  26.                 CString sText;6 D1 Q. p. x' s8 g
  27.                 m_HelloCFan.GetWindowText(sText);' C) x0 Y. F: D& F. A+ ~
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);% R% c8 q7 |- h* d. {5 W6 X# d2 ?
  29.                 //是否得到焦点
    , Y% v9 u0 I% o
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)# @% p! w: F( v- S, D5 s7 X) R
  31.                 {
    ' N' V. p' R) \. F- S6 g
  32.                         //画虚框
    - ~+ s' c, Z. d& u4 l* N
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem;
    7 e: }+ e: n% S0 O5 f% X6 ^0 U
  34.                        rtFocus.DeflateRect(3, 3);) w/ x/ r. Y1 j7 H& J
  35.                         dc.DrawFocusRect(&rtFocus);
    ' e. t: H" g9 g+ T2 T. D! C
  36.                 }
    & `7 q$ t' s4 e- U- {
  37.                 return;
    , \3 K- W. H! Q- c; k* A# V' q9 F
  38.         }/ F: T. I5 y: c2 P: _
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);/ W8 A3 m; p$ f# D7 H7 J0 c4 a, a
  40. }
复制代码
- X& m  O" U$ ?& m4 ?$ u) x' c/ A
  别忘了标记Owner draw属性: , P, c0 W# k5 ?. g3 Q
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。
* s6 |8 W$ o% y/ c! o
; L( k: W2 _# p' w& `+ }  3.3.5 WM_MEASUREITEM . d# d9 z' N% \1 }, t7 O
  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
6 V3 m; N7 D- }! V( I7 m  WM_DRAWITEM的映射函数原型如下:
( W& t# C$ I5 Q* ]0 J# L  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct ); ) v4 ^/ z: c! w! ?
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l& v, T- X2 K/ e3 a. _
  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下:
2 ]" E! X  P6 e' `; h

  1. / U. e, e# i0 }, M3 M3 ?
  2. typedef struct tagMEASUREITEMSTRUCT
    4 S: p* Z5 Q) `; X
  3. {; T6 M0 g$ m3 L9 M: e
  4.     UINT   CtlType;
    9 }4 ~! n0 e4 M' r# e2 I0 {
  5.     UINT   CtlID;
    & h$ L/ T) P- T+ v) z
  6.     UINT   itemID;% l/ |) ]. z! Q( n. z; C+ y
  7.     UINT   itemWidth;
    , f/ H# a+ N( U$ E) ~+ r
  8.     UINT   itemHeight;$ X7 V4 B) @& `) f, Y
  9.     DWORD  itemData;9 t2 @/ W- G" g* o- X
  10. } MEASUREITEMSTRUCT;
复制代码
2 v1 Q6 Y4 y$ k7 [3 Z! x
  CtlType指定了控件的类型,其取值如表6所示:
1 r; n5 a% F! O) L类型值 含义" [5 [/ P9 O' h) s
ODT_COMBOBOX 组合框控件 5 A( e9 {" {% j" k: i$ a
ODT_LISTBOX 列表框控件
1 L+ m: W% }% V& _3 ]ODT_MENU 菜单项
  g. p; ]. p! J1 \! @表6 CtlType的类型值与含义
2 c4 j/ [. J2 }) t+ _! B' o  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
$ o, y) ?$ a+ c  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。
8 {! V) P% F( r- h' t! h  itemWidth 指定菜单项的宽度
& ~/ T' T) K) c+ ~3 c. v. r' o  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255
; j2 {0 o; P( v; b3 c+ P2 w  itemData 5 I$ E* f( u& t$ k
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
" O; N) f# X3 J7 s0 y7 L  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
3 r; J6 O% n0 d2 D7 A- l$ i! t  图示出了OnMeasureItem的效果: % B( n9 ?8 }- p2 ]; M
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下:
5 K" t7 j$ j4 H6 p# W( X2 h
  1. " l6 h% T3 q. W. i6 ]
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) ' Q4 P( |0 V+ s3 h. q2 {
  3. {
    & L) s( s) e* Q/ C7 T) s
  4.         if(nIDCtl == IDC_COLOR_PICKER)
    ) e( `* @, B6 @# Q: _5 ~3 \7 q
  5.         {  ?1 j6 q5 F8 l7 o) r$ k
  6.                 //设定高度为30
    % ?3 u# @- o' m2 M7 o
  7.                 lpMeasureItemStruct->itemHeight = 30;# \8 q. Y* y1 Y% C4 c! a3 _
  8.                 return;
    4 q1 q+ U& N! c( n0 v9 F
  9.         }
    ' h& j4 g6 d! o
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);& m, k" U9 P2 e4 ^; d9 {/ y+ ]( s
  11. }
复制代码
) |9 Q( ^% j! H
  同样别忘了指定列表框的Owner draw属性:
' x# Z( T# H  c% `& Q8 z) y8 T" P$ H
  图11 指定下拉框的Owner draw属性  
4 a4 M6 [3 N4 D2 V- F$ K. H, @# A: e/ G: X4 c; n
  3.3.6 NM_CUSTOMDRAW
" S( }% L; u6 S( g  }0 l, c  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。 + ?0 e6 Z5 H4 b! f# S4 H
  可以反射NM_CUSTOMDRAW消息,如:
- A( D6 y( P: i% D  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
, P% s6 W: z; Q. R9 R6 G: \  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); ' d3 x. n3 J" H* B, r* ]6 B
  参数: ! j% j7 b/ z1 l
  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:
8 ^! N/ X2 N$ a8 r

  1. , }4 F# ?$ r. v! ?3 T0 S! L' [
  2. typedef struct tagNMHDR3 p" ?( o  a9 w: l3 Y& ?
  3. {" Z0 a: b4 Y& K+ N3 Q/ h: o
  4.      HWND hwndFrom;! l, a" t8 b! l$ c6 I
  5.      UINT idFrom;" ]+ f  e1 H5 Z7 [! Q& D, D6 U1 q7 M
  6.      UINT code; * n9 E% N& r" b7 p
  7. } NMHDR;
复制代码

1 [, v$ g& j0 n& h! k  其中: 7 u, Z3 H5 H- B) J0 @
  hwndFrom 发送方控件的窗口句柄 - f. a" Z- n1 T, s5 y- G
  idFrom 发送方控件的ID code 通知代码
1 h- W) h1 m3 g; H7 h6 C1 O  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下:
* p" v( Y' q& }2 _4 R$ B, w
  1. 0 e; m" Y3 W3 [- c+ h
  2. typedef struct tagNMCUSTOMDRAWINFO& Y: v+ y/ e  f' B
  3. {2 o- V$ i0 \! H/ Z+ y8 F' S+ [
  4.     NMHDR  hdr;" y6 j3 i5 |( \. y: V* |, Y
  5.     DWORD  dwDrawStage;& n8 T1 w; s& j1 V; f
  6.     HDC    hdc;  k$ B) [* W9 R6 ^/ K
  7.     RECT   rc;
    1 M- i) ~, `+ b1 o& I- g
  8.     DWORD  dwItemSpec;  g4 `  W* Z) @4 x0 m" y5 C! y
  9.     UINT   uItemState;
    # l! m+ U! j- ^; c& V
  10.     LPARAM lItemlParam;& H; g4 x) l. ?6 b
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码
  V& a( f0 ]( ]5 I$ r
   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:
3 l. H" ?/ S% g- Q9 B: X4 z类型值 含义
/ ?! F( I; F  l+ p6 t4 ^CDDS_POSTERASE 擦除循环结束
1 V" E$ D; d* B" bCDDS_POSTPAINT 绘制循环结束 ) }  B3 d  _* s% s$ C9 ~
CDDS_PREERASE 准备开始擦除循环
$ i) F( R6 w) g/ FCDDS_PREPAINT 准备开始绘制循环 ; A6 p# N! [1 r
CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 + G1 _+ E0 T7 c3 K
CDDS_ITEMPOSTERASE 列表项擦除结束 9 l! I- f3 N7 c( I! V
CDDS_ITEMPOSTPAINT 列表项绘制结束 & d- f6 a$ j2 B" [
CDDS_ITEMPREERASE 准备开始列表项擦除
8 S/ P) k$ q: Q* h3 d7 U, C! DCDDS_ITEMPREPAINT 准备开始列表项绘制   b( B0 y% z4 h* M
CDDS_SUBITEM 指定列表子项
0 H. c. T6 g" i% s( v6 i表7 dwDrawStage的类型值与含义& I" v" [4 M% {' L
  hdc指定了绘制操作所使用的设备环境。 ' Y7 b0 K& [. f9 |- z) ~
  rc指定了将被绘制的矩形区域。
) L( X1 T$ g* y+ V+ K/ Z, L2 i  dwItemSpec 列表项的索引
) i% \7 s( m4 n% G' ~  uItemState 当前列表项的状态,其取值如表8所示:
" v. l+ y2 n/ `6 `4 v, s2 i类型值 含义 ( W  M1 `3 z3 Y  Y: F6 ]
CDIS_CHECKED 标记状态。
( g+ H! h& l! ~5 L  b( }CDIS_DEFAULT 默认状态。
6 c+ Y) d+ l' Z; I  XCDIS_DISABLED 禁止状态。 $ X) {6 e$ M% v  s5 M
CDIS_FOCUS 焦点状态。
7 G/ b- U2 X, j9 v: P1 G+ m% M# CCDIS_GRAYED 灰化状态。
0 m4 S* {3 F9 V5 G  wCDIS_SELECTED 选中状态。
3 a; H+ |0 q! \+ ~, l% b$ KCDIS_HOTLIGHT 热点状态。
7 ?9 {! s; W/ d5 t- K7 V: b5 I! ?CDIS_INDETERMINATE 不定状态。 3 k+ I7 e/ L- Q7 k  t1 U1 s3 }
CDIS_MARKED 标注状态。
* m& G0 i& ^* H' Z7 d* e表8 uItemState的类型值与含义9 r& L0 B8 |* N; D8 D  B2 U( y
  lItemlParam 当前列表项的绑定数据
! [# q5 W+ V( y- ?2 f; h  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: + O9 P& x- r( \2 G, _# O. N
  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:" w5 K) j# R$ J4 K
类型值 含义 : v4 x& q% k* ^! A
CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。 - J$ Z6 A2 Z5 _( V
CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。 0 X/ r0 J) P! U- U3 Q& X' y  N
CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。
, k1 V* u- s% r' H; l% i8 L1 d5 g- XCDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。- }, k9 f; t" w
表9 pResult的类型值与含义(一) ( p+ |+ M! O- P' W3 }
  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:7 w( Z2 X$ O& u9 c0 ~* V
类型值 含义 : P2 b8 _) S2 d. W
CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。
/ P2 k8 l2 w7 E! |! y' w# m! sCDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
# ]7 O& s' g6 E" S. F! i3 ECDRF_SKIPDEFAULT 系统不必再绘制该子项。
5 l% }( S! G- u( i3 i/ K表10 pResult的类型值与含义(二)9 c" P* |8 u, I% e. V- F
  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子:
" \2 z6 ~! f4 s3 z
  图12 利用NM_CUSTOMDRAW消息美化界面 * r" J& l9 P* {+ @/ w; F
  对应代码如下: . l, I- K& K! x+ a

  1. * {. [1 p7 z& e# `0 C7 S4 J* `
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)) b0 N( o1 }) v7 e+ Y
  3. {
    1 z! {8 h" S) g7 {# H
  4.         //类型安全转换
    7 d& x+ g/ f( B! J2 E. O. l7 _- B3 D
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);2 @$ g+ a2 @+ Y% v
  6.         *pResult = 0;
    1 l- H, q/ R; h2 ^4 v3 w
  7.         / i% _* C) s+ g. J
  8.         //指定列表项绘制前后发送消息
    : x( G1 z8 O- @: o# v, K3 }: Z
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)$ a7 e$ z. _7 Z' C  u
  10.         {+ \8 c1 R! W9 w5 o' s
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;
    ) {+ e; l6 ?8 Y0 O9 E
  12.         }. E3 a* X+ J7 W6 V
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)
    " W) u( U2 Q1 j- t1 D$ S& y$ N
  14.         {
    5 D0 h: g( N2 G. ?9 O4 I4 Z
  15.                 //奇数行
    0 u9 v3 s! U' i  e; s
  16.                 if(pLVCD->nmcd.dwItemSpec % 2)$ Y9 s0 |7 y% \' s: R; c$ R  q  O
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);
    ' _2 I( v  w! ^6 @
  18.                 //偶数行
    * w9 `7 T! C5 S6 i! \
  19.                 else, y* [' l; r/ k# b
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);
    " S  L9 A4 }- _. X" I8 n
  21.                 //继续
    9 f" d9 ?$ G0 m
  22.                 *pResult = CDRF_DODEFAULT;
    6 c, p7 a. H. x% M, T9 {7 ~5 U
  23.         }
      r+ w* `/ Q9 F+ v+ X' T: ]
  24. }
复制代码

) y6 f2 |$ A5 i5 f  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。 2 m3 q/ ^& a8 d, C+ L) ]8 O

/ W9 N& T0 h! e  3.4 使用MFC类的虚函数机制
9 S) O6 W' r* g$ G& n  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子:
8 F1 b; t6 W" o+ r% I- K& g% C
  1. ' P+ D8 s  j* \( j( Y% q! a* K" {
  2. void CView::OnPaint()
    : f/ l$ w: l! d$ E# M+ [; I
  3. {
    0 y* H0 h7 G) L' u
  4.         // standard paint routine
    " L8 ]( u: O# ?/ Y6 g. B
  5.         CPaintDC dc(this);
    + n  U3 s8 {2 ?; ]" k3 O
  6.         OnPrepareDC(&dc);
    ) R- l  o: A9 S. u" @
  7.         OnDraw(&dc);
    4 ]( |! j" {$ C4 P8 a% i
  8. }
复制代码

5 Q3 S% _( K" W/ ?4 T" N* }  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。 5 a4 H7 [1 `( U) a& F8 N
  以下列出了与界面美化相关的虚函数,参数说明略去:
* P1 f% }2 G$ tCButton::DrawItem
2 h- W" F8 u/ m  v8 zCCheckListBox::DrawItem 9 x( o( {- E- m6 C
CComboBox::DrawItem
8 k$ v/ `9 y3 ~5 A" Q. o! v% qCHeaderCtrl::DrawItem 7 p0 W  M0 f% M  P  l
CListBox::DrawItem 6 A+ A$ V, A! ]9 u" e9 Z0 t$ `! r, l
CMenu::DrawItem
7 Z  l. z. Z/ V! iCStatusBar::DrawItem $ I3 i& B: N' ]/ E0 S. J) A$ ?
CStatusBarCtrl::DrawItem 3 o% L, m- \: ~/ [: E& J
CTabCtrl::DrawItem7 f6 W% f; f* O1 K
virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );
9 g2 k  s4 q9 L+ _3 gOwner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。
1 e; E; ~9 ~9 M2 C; l  限于篇幅,在此不再附以例程。 : V2 R* f% N$ p# A3 [

+ u4 L% O3 G4 e参考文献5 Y, t8 O$ j( {
本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-8-13 22:52 , Processed in 0.030660 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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