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

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇, w& s4 r/ `0 v: N5 N& J
  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。+ ^7 B( Y% }/ I0 Q2 `7 k
  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。
9 ^! ^5 @* m* \1 C" ^+ F5 W1 L
4 B! u: _% J; a1 R' X/ E7 s( [/ G! b  2. 美化界面之基础篇, x3 P( K. F5 g# f) S: k
  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……
' \3 [% ~, z7 H9 m, O
4 y3 @8 d7 \4 g9 |+ s  2.1 Windows下的绘图操作
3 P- }' A6 Y- }  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……
2 m: ]# c4 _9 e, p# {8 a  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。1 O, k( G! J, i0 ]" f. i0 L
  ^7 X# @- @' S' ^! E9 T
  2.1.1 设备环境类
) u# ]: I1 d4 Q  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。
7 K4 j4 S$ ]& F0 g% K  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:6 q9 R+ c4 X  U& }6 N; F9 |
  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式
$ j1 P) X/ u+ V! ~' Z6 F  Mapping Functions:映射操作
, Y& p" }5 b0 A+ Q( T  |  Coordinate Functions:坐标操作) I" w7 c. l9 n' K
  Clipping Functions:剪切操作8 F! G/ p7 H1 B% O" B4 J8 a
  Line-Output Functions:画线操作8 w: w- q# p) `9 a7 |  P' F" L3 t4 v
  Simple Drawing Functions:简单绘图操作,如:绘制矩形框
, B! v  Y! g0 j& }2 }# I  Ellipse and Polygon Functions:椭圆/多边形操作* h5 W7 G" Q7 W6 J5 H+ e5 V
  Text Functions:文字输出操作: T" _5 z5 z  L1 z# H8 {& z  Z
  Printer Escape Functions:打印操作& n0 Z0 J5 O) J) ~8 i6 r
  Scrolling Functions:滚动操作
8 Q$ t+ |' e2 A- a( m1 T  *Bitmap Functions:位图操作
3 M6 A- J( d( B, Z. a  *Region Functions:区域操作8 a$ m* \# v( o' s! m" J5 t1 V
  *Font Functions:字体操作' K* }: S* `# @. a
  *Color and Color Palette Functions:颜色/调色板操作
" n8 O. Z4 B- _' Z6 \( B  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。
/ {, g; E0 Z( V% l7 R7 P: X% o4 H
  2.1.2 图形对象类2 h1 o$ _: E2 H, ^- l: S4 {# e1 v
  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。
! n$ }* _( H2 s$ a- d- }2 |' B9 G  下面的表格列出了MFC的图形对象类:* r- b+ o( y4 {5 e/ H
  MFC类 图形对象句柄 图形对象目的; A  _% Q* w$ n: m) i
  CBitmap HBITMAP 内存中的位图
  f$ W4 P4 N) A" q: a# ^- W  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式
0 D, z0 Q, @( r+ `8 f  CFont HFONT 字体特性—写文本时所使用的字体* Y- Q, O7 G, M, f( b
  CPalette HPALETTE 调色板颜色
7 i& Y- ~' C% D3 L4 ?& Y  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细, d9 S" o0 l; ~/ [/ d1 a" I
  CRgn HRGN 区域特性—包括定义它的点3 q8 d* _0 S  j# y/ j
  表1 图形对象类和它们封装的句柄
  h- t% ]5 j' Y3 G' S3 i6 h
' r/ P0 q: h! T  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:
* }( a* z+ u3 h
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:
, [2 z: J" l0 ^/ Y" W: @
  1. 0 _4 Y5 A9 f; O/ Y$ A% i
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)$ N2 E! u$ i9 c' X7 g* E! e$ F
  3. {2 U% D4 r- n9 x; F3 ?
  4.         //设置背景色+ i& {; C* p/ J
  5.         //CBrush CUi1View::m_Back+ d9 c8 L. I( K7 [& ~0 ?2 F
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));" |: `) x  {; s1 g
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL);
    ! D! d" M, g# C( l+ Q: i
  8.        return CView::PreCreateWindow(cs);
    : H6 ~% i- l1 }2 l
  9. }
    : Q: T  k/ _2 G- |
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)1 v+ s3 o# O$ m0 V, _$ t9 ^
  11. {4 O* N* r1 T# D$ F5 W8 S
  12.         if (CView::OnCreate(lpCreateStruct) == -1)
    0 n6 x+ y* \! w& Y" m
  13.                 return -1;( v$ n" G" q- r# G" y  V  f
  14.         //创建字体, K8 S& G- }- X6 w$ _$ p( O
  15.         //CFont CUi1View::m_Font: i) C. ]' `1 Q+ _9 o
  16.         m_Font.CreatePointFont(120, "Impact"); ( S7 r+ ^( z* e2 h
  17.                return 0;5 Y0 G- R/ ~+ t; K5 _3 I
  18. }/ ~; p/ }2 O+ w3 s( T6 ?" Z
  19. void CUi1View::OnDraw(CDC* pDC)2 w7 A$ r# G' ~8 ~2 X7 k
  20. {" d3 q$ h6 V8 K9 c# F
  21.         //绘制按钮框架& [/ H4 ^' [+ x
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);) e+ H$ \8 g2 x% l+ U" g! P1 {
  23.         //输出文字$ ~6 @8 h  a/ [2 \' ?, g0 C0 V
  24.         pDC->SetBkMode(TRANSPARENT);: i' q! G. N: w. T
  25.         pDC->TextOut(120, 120, "Hello, CFan!");
    5 \* f% `$ [! p4 k3 M/ e
  26. }
复制代码
  d! ]. t% A- G/ L1 ?' M0 z/ [* A1 s
  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。 6 b9 p5 Z. m6 W* }
2 _- G$ G9 J$ t
  2.2 Windows的幕后绘图操作
$ Q: G# ^( `) M+ k( Z  在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。0 S& V- B+ b( D* o/ a: v/ k( A
  有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。7 @! R) S5 u9 N8 ]; v! Q
  所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……
: s3 o: A% q2 s7 i7 z- D  有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。/ L+ E2 j8 A# Y5 D

  _; t9 ?) Y% q4 W( z8 Z) U  3. 美化界面之实现篇# Z# L$ Q: N5 k1 W; ^
  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。- F( A- a2 @. W: e+ E& H( Y( O
) e# ~' X. d8 m2 }$ @7 [& _; a8 E/ l
  3.1 美化界面的途径6 |9 U. c- i3 g5 F4 B5 U* V1 B; j
  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:
; U/ p8 Y" K, }: H  1. 使用MFC类的既有函数,设定界面属性;( p) L- v/ f* V* S' {
  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;
# S: C0 T4 Q3 e3 j  z7 \( M  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;
9 b7 E& C8 m4 ?4 r0 J' g  一般来说,应用程序可以通过以下两种途径来实现以上的方法:& p$ N9 ?  n4 s4 V1 c- `1 I
  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;
6 {: n! Y! N; F9 @8 F$ w  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。
, a; R9 M0 u; S; F5 B  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:; y2 d- S: k& g( v6 f3 U3 S
  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示: 1 f! o( C& `, V) p
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;
' s1 ~6 _: Z# F( B. T0 g  {/ K  以下的章节将综合地使用以上的方法,请读者朋友留心观察。
7 h# }- q- u! ?1 u" ?: ?, ^  _( B4 [
  3.2 使用MFC类的既有函数7 _2 t0 v* K& a" [* |
  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。; O6 M) u) K* T5 G# X8 m

1 o. u& V8 B3 u3 iCWinApp::SetDialogBkColor
  H) T* a7 I6 t; v! E; \void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );3 g* N( l. O/ {* Z
指定对话框的背景色和文本颜色。3 k  j4 }' C6 G/ y, `% e; B

) K2 C; b# m- c5 c+ X# o* |CListCtrl::SetBkColor0 Y( t- m3 H  O" @7 R
CReBarCtrl::SetBkColor/ h; m* `1 E% t* [* X/ A7 K$ e
CStatusBarCtrl::SetBkColor
, j" S, E/ y( |9 SCTreeCtrl::SetBkColor
( j2 ~) J0 s" l" m! i$ OCOLORREF SetBkColor( COLORREF clr );( Z9 S6 l1 \0 ^
设定背景色。. e; \: w0 L* [1 v- B: ^, g
: l8 M3 D0 \8 ~% o3 Q" `7 Z! J
CListCtrl::SetTextColor
' {' U3 H* a# b. [( V& H8 mCReBarCtrl::SetTextColor
/ p1 u0 E, v3 \/ ]4 }$ `$ }. f1 @# t+ eCTreeCtrl::SetTextColor# `/ S8 c4 i4 L8 `1 _4 R  U& z9 j
COLORREF SetTextColor( COLORREF clr );
. a6 M0 M3 x& o' y9 `7 s( S  N设定文本颜色。
& N% e- X7 R# m( M( b1 b2 l/ a9 N0 z0 T$ P9 ~$ A6 N! d, m9 [
CListCtrl::SetBkImage" k* ]# a, \0 R' h" G9 I
BOOL SetBkImage( LVBKIMAGE* plvbkImage );
: E8 [2 T  k, h* u$ n+ t( {7 _  pBOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);
/ a. @3 w6 T% O  r# E: Q( h  JBOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );3 d1 H* M+ b5 K. c  P
设定列表控件的背景图片。
% R- V/ h7 d$ `. X1 |, K- l' U* P& e8 C
CComboBoxEx::SetExtendedStyle1 i+ C# X" L7 K! p8 @, `; g8 O
CListCtrl::SetExtendedStyle3 e/ H# d# z8 ~: }: G
CTabCtrl::SetExtendedStyle# H6 K0 C2 f) x4 t4 @% N9 Y  `
CToolBarCtrl::SetExtendedStyle 3 k) ^& b# s0 S: a& N
DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles );
% z* U" L! H. V" m( j4 U设置控件的扩展属性,例如:设置列表控件属性带有表格线。! t* X3 ?# [8 K
  图4是个简单应用MFC类的既有函数来改善Windows界面的例子: / k2 g5 B7 M* z0 t, n
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:& F3 S; J8 ^" R* Z2 d" K  g3 u: U

  1. & G, C7 e- K) v% X) J
  2. BOOL CUi2App::InitInstance()0 W& ~5 u$ T' c4 o
  3. {
    : l. x6 Q0 W5 D5 z1 Z) r' u8 Z$ Z
  4.         //…
    * r/ H  Z0 Q7 @3 w6 j
  5.         //设置对话框背景色和字体颜色
    ; {+ J, V" i. H3 b
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
    - P4 J( u' x& _0 a
  7.          //…3 {& x! b% t5 D! k" e1 L
  8. }
      O$ m" R! y* a' T7 T
  9. BOOL CUi2Dlg::OnInitDialog()0 b8 ~. f6 G. c- V
  10. {' f9 q6 i4 r9 ^) s4 h
  11.         //…1 u* p) G' u$ @' ~$ C; Y
  12.         //设置列表控件属性带有表格线
    , v" c: j6 @% M6 r1 u
  13.         DWORD NewStyle = m_List.GetExtendedStyle();4 q4 D3 P# a# l' |2 t
  14.     NewStyle |= LVS_EX_GRIDLINES;& ^7 G7 m( m1 D' O5 H2 F( G
  15. m_List.SetExtendedStyle(NewStyle);
    : P, i: S  q" P) d1 i2 `; H- P
  16.         //设置列表控件字体颜色为红色. D# i. D1 }6 {9 a0 J
  17.         m_List.SetTextColor(RGB(255, 0, 0));
    % `" ~+ }8 N2 n6 W0 \3 u& J( ^
  18.         //填充数据
    5 l# U. z5 @4 A; u, I1 Y& Q
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);& H; u0 [4 T0 u$ X& B
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);* C( R' l, e7 U+ p" }4 U
  21.         m_List.InsertItem(0, "5854165");
    % B8 |  i' S- b3 q! g5 M
  22.         m_List.SetItemText(0, 1, "白乔");9 q7 ^- W# O& V7 S" w( e
  23.         m_List.InsertItem(1, "6823864");. H& |- Y4 Z; O" w2 Q- }, t
  24.         m_List.SetItemText(1, 1, "Satan");
    ( E8 d$ [! y( v  e
  25.         //…* M& `" V) k& @
  26. }
复制代码

' t8 N5 B- P# l0 |8 ^4 D  嗯,这样的界面还算不错吧? , s- J' d3 B% a, y
9 x/ t3 G/ R# A4 e
  3.3 使用Windows的消息机制
" d/ {" D) ^+ O5 \7 Q- i% I) h9 Y  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息: 9 x0 q+ A1 R3 ?: P" }+ Y" b: _$ D/ i
WM_PAINT ( m/ v- _* ?! U- v
WM_ERASEBKGND 9 s3 k: [: v# ]7 n3 q; ^  G8 S0 I( Y- y
WM_CTLCOLOR*
1 O- h/ |, S. JWM_DRAWITEM* 4 O) \6 X1 b$ A
WM_MEASUREITEM* 0 P* u: ~- e1 j
NM_CUSTOMDRAW* & H% k* f0 w1 n# C, L$ I, D$ }$ C
  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。
# D. K3 B2 {8 L+ O) s8 x$ ^# {. w6 D& t8 O1 ^
  3.3.1 WM_PAINT0 |; a0 m+ `- [
  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。; g/ s; K7 k6 M% G
  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下:
% |& M. a' L1 j$ y. ?/ G5 K  afx_msg void OnPaint();
- e/ z: g) J( {" _$ l9 X  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: % O$ K* V. W9 b
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单:
. v$ j2 m) k  Y4 Y2 o

  1. : V( f. o$ l+ n0 Z# A+ h
  2. void CLazyStatic::OnPaint() 7 u) F) ^0 G: V: W/ K# T
  3. {
      ^: c( m8 {$ N& |/ n+ l
  4.         CPaintDC dc(this); // device context for painting
    3 R; R  y: O1 m, ~0 T, O' r
  5.          # U+ \0 W3 s  j; a4 d
  6.        //什么都不输出,仅仅画一个矩形框+ c7 q. e6 Y/ {1 [- o
  7.         CRect rc;
    # `( M7 ^- {+ q, n# N; i
  8.         GetClientRect(&rc);
    4 V  F- w# `* e) ]+ J4 C
  9.         dc.Rectangle(rc); ) w8 q9 \5 s. `) q
  10. }
复制代码

) e4 J7 i9 y* ]( P* B' ~- Q  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。
9 ?2 ~6 @9 _& L# S% V* J0 w) q8 q  @* t; H% x2 x8 A- l
  3.3.2 WM_ERASEBKGND
  T0 M! i3 O+ d) @; f  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。 2 @( }6 v: B9 y1 f% G. x
  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:
- H0 U, i3 I" l: C  afx_msg BOOL OnEraseBkgnd( CDC* pDC );
4 r# k+ E; f8 V( K" B, U- O% H  返回值: & u5 a6 A' H  p  w7 s% \4 m( Y
  指定背景是否已清除,如果为FALSE,系统将自动清除 ' R7 B3 p. u6 u$ u* x
  参数: pDC指定了绘制操作所使用的设备环境。 8 @5 l6 e5 W9 E5 ?4 T' a4 Q
  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景:
. R, P7 m8 t4 c$ v
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单: ! X$ k5 N. l' D6 G# v5 ~

  1. # P( ]% `* t: M3 L" K
  2. BOOL CUi4Dlg::OnInitDialog()+ i& ~& b- P- k6 M' D
  3. {3 R8 ~4 {- ?  n1 S/ L
  4. //…' U& A( K. Z0 G: X' c# ^
  5.         //加载位图
    $ h4 ?; m+ T9 \( o
  6.         //CBitmap m_Back;
    ' J+ ~  f( G5 e! |# v
  7.         m_Back.LoadBitmap(IDB_BACK);
    " f4 }# V. n0 K" e3 U2 z
  8.         //…( T" P0 S  ]& i7 W9 M
  9. }
    # p) \& V: |# f' a7 @
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC)
    9 U- l2 E. E4 A5 l6 n' Z
  11. {  [$ ~; P& J; ]0 m
  12.         CDC dc;
    - |; N4 x. n- u) Q
  13.         dc.CreateCompatibleDC(pDC);
    ! ]8 y3 T; D* c; \9 I/ a: _
  14.         dc.SelectObject(&m_Back);
    $ i4 {0 O* L7 [5 }7 _* j
  15.         //获取BITMAP对象
    * x9 u6 L; p* K/ J) H! v: u; z
  16.         BITMAP hb;
    $ Y$ x$ m, u& u: }  c5 G1 j  t
  17.         m_Back.GetBitmap(&hb);7 F& X9 n# T  [8 R* E+ Z/ U
  18.         //获取窗口大小
    ! X- r8 S1 B; U; [- [2 g  A
  19.         CRect rt;  V3 t% m" t) T9 c9 i+ I
  20.         GetClientRect(&rt);
    4 @1 [! Q5 h' K
  21.         //显示位图; `. P- O5 U4 H* C! E
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),- S. Y$ R+ ]% Q7 Q
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);# L9 N9 t9 l' |. ?) M( i0 P
  24.         return TRUE;* x/ U+ [* T: W* O/ d) q% q
  25. }! W/ @5 \5 ~. E' d6 \3 H4 ]: A
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 2 p5 W  a6 a2 f7 e. D6 `/ ?
  27. {
    / ]5 G" v# o, c, I& f- g; L+ D' v& g
  28.         //设置透明背景模式
    & N4 x. N3 _) b9 x5 n) V$ N; ^
  29.         pDC->SetBkMode(TRANSPARENT);! b# V# H6 u2 m0 ^8 G
  30.         //设置背景刷子为空5 L6 A) e! Z2 V0 k9 m* M$ J% Z7 P% p
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);
    - ~/ i2 Y& x8 m5 k8 z
  32. }
复制代码

$ a$ ?) X! w' L6 `& s  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。
$ @1 n( `# R0 p: h/ Z: p* }. l( r& V7 g- w/ A$ \
  3.3.3 WM_CTLCOLOR
# ]. Z: q9 l! y5 _& |  D  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。   L3 g( F! A  x+ h% U# d: w8 `: _8 ?
  WM_CTLCOLOR的映射函数原型如下:
; \& D: C3 j: l( E7 X  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );  t) C5 o4 C( r3 m- W
  返回值: 用以指定背景的刷子 % b# u" [; E" C8 W+ r, i5 I9 _" w
  参数:
. K; V) y2 S7 h  pDC指定了绘制操作所使用的设备环境。 4 o$ F) w& X& y# a6 s
  pWnd 控件指针
7 _" X( j3 O, c& s" T  nCtlColor 指定控件类型,其取值如表2所示:
* W1 B$ L0 B! ^* K+ k. X  类型值 含义
/ N8 s( b8 ^8 P: t4 ?CTLCOLOR_BTN 按钮控件 ; k! C, i7 {! d% n. {/ b
CTLCOLOR_DLG 对话框
: h0 ?0 ]$ G6 }% S5 \& ICTLCOLOR_EDIT  编辑控件 ' D2 A/ m0 L( S. ^5 |
CTLCOLOR_LISTBOX  列表框
' b7 @) L% W8 v! V) pCTLCOLOR_MSGBOX  消息框 - D" ~) i3 |$ x, k8 t% p7 S
CTLCOLOR_SCROLLBAR 滚动条 7 U1 u- e  U' p) z7 K
CTLCOLOR_STATIC 静态控件
2 U4 v5 u* y8 Y1 E9 e0 l6 q8 N( ]表2 nCtlColor的类型值与含义( R. y6 g% r& q+ K5 O+ ^

' f- ?  X! D1 p' D# E  作为一个简单的例子,观察以下的代码:
" q5 K" w( y5 z

  1. # I/ g. o8 Q3 T: N8 x% a
  2. BOOL CUi5Dlg::OnInitDialog()
    % W1 T4 {2 K" l4 i7 a' W9 G% E+ C
  3. {- J4 @! m7 C. K' Q; w2 M7 k
  4.         //…1 d7 u! X, X3 u- W9 H2 Y
  5.         //创建字体
    0 R( R, h) h9 a+ u( S7 y" f
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font2
    8 ~9 i/ Y" B: G' _7 w
  7.         m_Font1.CreatePointFont(120, "Impact");% L/ ~% u5 ~0 g$ ~* _& p
  8.         m_Font3.CreatePointFont(120, "Arial");
    / ?; b+ I/ ?( n1 J9 N- _( B3 F
  9.                 return TRUE;1 ?' z+ I; O  V+ h$ p
  10.   // return TRUE  unless you set the focus to a control * F( G) c2 C; f  t4 X
  11. }
    ) k* o! T. E* b( [' P
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) , F0 @3 b/ [/ u6 z
  13. {
    6 R( w' u/ _$ v
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);3 ~* ~! U1 L* G+ F0 t( n( @1 l2 J0 p
  15.         if(nCtlColor == CTLCOLOR_STATIC)
    5 o% a1 W# N4 B/ f" h
  16.         {
    / i7 a( w/ N7 R/ h3 A
  17.                 //区分静态控件
    $ |" E1 q$ {/ J8 M5 \
  18.                 switch(pWnd->GetDlgCtrlID())! |; ?  U: E/ j( y
  19.                 {! C4 V: C6 D5 y; j: V& O
  20.                         case IDC_STATIC1:: p0 X, ^2 n" s7 c5 U' j, L& t; B+ }
  21.                         {7 e' m1 ^, w' C3 M
  22.                                 pDC->SelectObject(&m_Font1);
    / W1 h8 r8 S5 [3 {/ j* \
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));
    0 H0 S9 ]7 _! S4 o5 t, e0 s; k7 ?
  24.                                 break;) K( O% O  i5 t9 L4 W
  25.                         }
    * }- @# j* A! L+ W
  26.                         case IDC_STATIC2:
    , H- s6 _9 u% ~; v- W
  27.                         {! F5 x" E1 k4 Z$ q5 X7 b! Q+ p
  28.                                 pDC->SelectObject(&m_Font2);% W. C2 o; }2 @$ r: v, K; G
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));% D# S* z) n. [0 t+ d; j3 h
  30.                                 break;; ?$ C! n% v, h+ d
  31.                         }
    * E% P) L' `; L" P9 G6 ]. b
  32.                 }4 F: s( K: K1 Q% M0 D
  33.         }* y7 n/ @, C# r- Z
  34.         return hbr;$ ~/ Z: ^6 ^2 A3 Q7 W3 |' h
  35. }
复制代码
: T( A1 E6 \( l3 g& T
   生成的界面如下: / S3 D# w  y3 _  T: T& z1 W+ a, }
图7 利用WM_CTLCOLOR消息美化界面
, ?: x0 r0 [1 y, u% V4 a% z
  3.3.4 WM_DRAWITEM 7 i/ B; H; v& u0 \# @
  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。) R/ W( q6 e; `# ^( @& _
  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。 * D8 H" e7 Z* X" s- Y4 q6 J+ x
  WM_DRAWITEM的映射函数原型如下:
) r6 q0 ^" Z: m# G8 T( Z8 z  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );) d; Y/ V: I0 f, V$ m3 q
  参数: 3 }$ q, v7 l4 ?2 m! `2 h
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 ) y8 T/ X" {( \
  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下:
0 z5 H! y, w) |. W7 f6 O

  1. $ L- N' P5 K4 e/ m4 g- I( E3 v. w) _* H
  2. typedef struct tagDRAWITEMSTRUCT
    : z- @8 Q+ k5 T- @2 r
  3. {
    0 ^* g! S# u6 f' V
  4.     UINT   CtlType;
    0 a; Q) F4 I' e( \
  5.      UINT   CtlID;" i6 |, Y/ l) Q1 R. x4 i* M: K* K
  6.      UINT   itemID;, [* H, P0 u$ _* o$ g
  7.     UINT   itemAction;# l3 D' `6 o! W$ u
  8.     UINT   itemState;
    * r4 J2 L3 ^' A  g- `
  9.     HWND   hwndItem;& ~. v) ^5 s1 S1 l" \& m( B3 T2 J4 s
  10.     HDC    hDC;" a/ e2 @  Y) B1 D
  11.     RECT   rcItem;
    5 Q4 M% ?( h! D" K& F
  12.     DWORD  itemData;
    0 S& Q2 y# g# Q
  13. }DRAWITEMSTRUCT;
复制代码

$ ]* R7 X) x' K# G. _0 d  D$ ~CtlType指定了控件的类型,其取值如表3所示: 3 h7 O7 v# o# R3 E1 Q& I# J+ {) F
类型值 含义 ! W: n; E9 Q3 v4 Q! r
ODT_BUTTON 按钮控件 ) h2 k* X: v6 d5 u3 F' z) l( n) U
ODT_COMBOBOX 组合框控件 + i. C  x4 i: P, q* a: C. p
ODT_LISTBOX 列表框控件 5 F& F" }5 |# r. F
ODT_LISTVIEW 列表视图
- n+ ~+ t/ E$ G  nODT_MENU 菜单项 # y* ]& S! N6 y& `3 l' u
ODT_STATIC 静态文本控件
" G/ Y' j- ~' {8 y, a3 U6 b' ?3 Q7 GODT_TAB Tab控件 " D1 Z+ P! d! r  H* A
表3 CtlType的类型值与含义
( H/ r! e7 I: l, i
- w3 V. f- b* M" J2 a' a7 }  CtlID 指定自绘控件的ID值,该成员不适用于菜单项 & J& b+ t7 _# k) I8 ^
  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。 $ M; g: A4 q2 R- g6 U) U
  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:8 X' ^/ h( f3 I5 b4 `; w9 n
类型值 含义
7 W% r! ~" \# c; J- X6 E% f% cODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。
4 k5 b6 q- I) }5 d  q5 L& `ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。   W) G# r, A8 T. @
ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 $ r3 n% b- C4 @4 w
表4 itemAction的类型值与含义
! D, U' p. _. k1 O) E* u' L" F" ]  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:
6 Y. ]* X% p1 _9 E2 n类型值 含义
. h- S$ n# m$ x5 gODS_CHECKED 标记状态,仅适用于菜单项。
) c. J0 C1 q( {! g# ^ODS_DEFAULT 默认状态。 6 N0 G0 t  ^; E- A, u
ODS_DISABLED 禁止状态。 * O, ?& O- H5 b, `2 j: u* S
ODS_FOCUS 焦点状态。
4 Q9 f- J7 t1 |: a1 @# S! YODS_GRAYED 灰化状态,仅适用于菜单项。
6 C# g! i7 s$ R8 n- V. k/ `3 }1 n, t& uODS_SELECTED 选中状态。 / `$ y- g4 z& w( Z
ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。 $ p! B* d. w' X8 _0 m' T" P; a
ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。
  g7 v& u1 R, BODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。
0 K+ B; e1 x) `) {ODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。 % A! ?) W! }( e4 D5 |
ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。 ( T) S, p6 F( G
表5 itemState的类型值与含义
" y0 a1 v  l: [  i9 ]$ O  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。
1 R* P, A- o3 p( \  m9 G  hDC 指定了绘制操作所使用的设备环境。
( D. O: Q; ?7 K  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 * i7 k5 B4 r. J* i4 @
  itemData
  [9 C& N* s- B6 }' Y  {8 t% v  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
) K: x, F6 e  _" j9 `  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 ; Y' e1 \& P8 K
  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。 # ]2 o  f8 p3 y* x. y
  图5是个相应的例子,它修改了按钮的界面:
' E( [1 Z+ o! ~4 o. Q/ h1 F  b- I. t
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下:
" s/ K3 h- s: _) q# B
  1. ' G7 U& H1 f/ U6 @9 [+ o- Z5 f
  2. BOOL CUi6Dlg::OnInitDialog()) r" e4 k6 Q4 ^" [
  3. {" e8 F4 u% V7 Z' v, ]
  4.         //…: ]  V. Z* e: s$ q$ h
  5.         //创建字体7 X7 \( |* a% m3 n
  6.         //CFont CUi1View::m_Font
    7 B: x  H# s! n
  7.         m_Font.CreatePointFont(120, "Impact");7 @- n4 j0 |' @. v
  8.         //…- t0 z/ _2 V& N2 J. a1 f% j5 L
  9. }7 F. z- [+ \& s# K) G7 r
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
    , `# }; e" r# i; C; {" _. N- U. v
  11. {+ Q2 P3 s( w0 M5 h/ y9 `" Y
  12.         if(nIDCtl == IDC_HELLO_CFAN)' O. Z2 {( w4 n
  13.         {
    3 n: q3 \; E4 d6 D8 `, I$ Y
  14.                 //绘制按钮框架& v2 D; Z2 R5 g
  15.                 UINT uStyle = DFCS_BUTTONPUSH;
    6 s! u' Z/ E* ]) R1 U/ N
  16.                 //是否按下去了?+ @! w' f5 \$ ?+ s
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)
    $ n+ u  {/ a' ]4 `4 V
  18.                         uStyle |= DFCS_PUSHED;& I5 @# }1 U3 x1 i3 W
  19.                 CDC dc;9 U- l0 i8 k4 e* q/ s
  20.                 dc.Attach(lpDrawItemStruct->hDC);1 ?9 G& I, F$ G# O4 v
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);
    / }) e9 A6 S/ H/ s! L
  22.                 //输出文字! L& Y+ q' U4 u$ k2 g+ c7 T
  23.                 dc.SelectObject(&m_Font);
    ' a9 F6 A! J' N1 K6 H8 K1 W2 i" P
  24.                 dc.SetTextColor(RGB(0, 0, 255));# S/ g6 \% }' v% [! J
  25.                 dc.SetBkMode(TRANSPARENT);
    # H6 A# _  `+ e" Y  H
  26.                 CString sText;! z: I* R6 ]% d
  27.                 m_HelloCFan.GetWindowText(sText);5 J+ U  [4 ]1 O. o+ ]( l
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);+ T  E2 y+ |9 g6 G
  29.                 //是否得到焦点
    " V$ ?9 d6 ~6 f  v1 Q
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)
    2 o% o- g6 E5 O* a0 M
  31.                 {) O' a# R1 C* U2 L: T
  32.                         //画虚框
    8 g, l/ A- `* m) V) G
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem; 6 c) x% w3 V) W( `# B' c
  34.                        rtFocus.DeflateRect(3, 3);
    4 w1 E$ x5 j$ t+ _( p
  35.                         dc.DrawFocusRect(&rtFocus);
    . v) _0 h3 V" ~/ B+ o. s3 I
  36.                 }5 S) s6 L% T: h% k( _0 [  [" W
  37.                 return;
      i6 g$ K3 g: a& S' r
  38.         }
    " o! ?9 F2 P8 G" y8 r6 s
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);5 v1 [+ S7 K/ B, {/ S2 ]8 F
  40. }
复制代码

! O/ ]  ^' u! F/ R5 B$ i4 q) B& R  别忘了标记Owner draw属性:
: |5 G" ?4 P! u6 h+ [3 _
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。
( T6 ?/ C8 M  u4 @' q3 b
3 v; s) K6 c3 }# I" b. [  3.3.5 WM_MEASUREITEM # x* ^6 w. |9 ]3 N  T0 t! P! N# R
  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。 ! P( v' [% n* ^9 {  v
  WM_DRAWITEM的映射函数原型如下:
! Q8 [: G* }2 e$ r3 P  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct ); / z. r- M$ X% i* W! P& ?! O2 T
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l1 Y- D1 ]+ L# a" v- O3 e
  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下: # e& T, t* W& w; R) U
  1. $ l" Y: t; |' c$ X9 C' R, }. l
  2. typedef struct tagMEASUREITEMSTRUCT( A& N1 h: e! N7 [9 O, F
  3. {& `: ~3 i  X' V4 d" r+ B8 b
  4.     UINT   CtlType;& u& X3 c3 I1 s8 i
  5.     UINT   CtlID;
    8 e- s, y* p, }( H) `& q
  6.     UINT   itemID;% A7 h( e1 u1 N8 C6 O
  7.     UINT   itemWidth;/ F$ f' L# W  u; g8 c" l
  8.     UINT   itemHeight;
    5 L$ n3 R7 E4 e: x5 b
  9.     DWORD  itemData;' |8 X! `- C( G! J* {$ t+ {+ H1 `
  10. } MEASUREITEMSTRUCT;
复制代码
; E; V5 ~1 R% S4 J# A6 P! G- x+ y7 h
  CtlType指定了控件的类型,其取值如表6所示:
& g; ]9 h! Y& ]类型值 含义
6 W, N% Z' p5 M  P6 v4 yODT_COMBOBOX 组合框控件 : w4 C5 J8 d+ r  z" b0 t: i! g. [6 K
ODT_LISTBOX 列表框控件
& }/ }8 P% w. E" fODT_MENU 菜单项
/ [2 P7 p, W* X$ q* W, y( @表6 CtlType的类型值与含义
" z- A' c* }, v; ~+ V- A  CtlID 指定自绘控件的ID值,该成员不适用于菜单项 4 u) Y( D, {: h6 j# n$ |
  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。
2 z/ t+ \9 Q7 f  itemWidth 指定菜单项的宽度
4 k. ~. E! A& J3 U, g/ H  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255 : E5 c' Y3 ]$ |! p) j; z
  itemData % A* r. r2 G0 k1 p4 v' \
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 7 s: d$ o4 N' a( K6 o4 i. C( N# s+ Y
  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
: e/ ?8 u+ P4 V! T! O$ M1 V  图示出了OnMeasureItem的效果: 5 X" @, q8 P4 j/ y
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下:
# B0 `9 _' J7 p

  1. ( }3 x( R) D$ G7 ]
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
    0 k4 ?6 T  r' c( t
  3. {2 R( W! x* \$ V' {
  4.         if(nIDCtl == IDC_COLOR_PICKER)
    5 z0 z; N  |4 O3 W/ H* a/ W
  5.         {
    ( ?0 p, ]4 P, m% Z# i2 n9 h; ?/ n+ K
  6.                 //设定高度为30/ [. o8 ?/ ?! m+ @- @# o4 c
  7.                 lpMeasureItemStruct->itemHeight = 30;% _3 W# n6 `. G3 P3 E! k( M
  8.                 return;6 D) ]+ w$ l( T% t) i! J( b
  9.         }( M8 o& L4 a5 _& \6 n. \
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
    6 L1 i0 D  ~' [
  11. }
复制代码

& G5 M) x; T0 X  同样别忘了指定列表框的Owner draw属性:
% x- n4 R. P- z' L8 ?" {0 N
  图11 指定下拉框的Owner draw属性  
( x$ x& n4 O( }! O" L3 @7 A! y+ k4 y7 K  K  E
  3.3.6 NM_CUSTOMDRAW
& w0 N7 Y% F3 t- z, e  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。 6 ?: ~2 s* c. b/ a
  可以反射NM_CUSTOMDRAW消息,如: + k+ B/ V9 c' J/ {6 O5 v: Y
  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
! Q' F) Y, ^, d/ m6 \; g  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); 4 u+ ?2 f1 C8 J4 ^' C/ y; _: I
  参数:
1 b( Z3 s9 L2 D" A( k' u7 J7 A' y% S  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下: " A' n: A% M- I% |" W0 C/ ?/ D0 j
  1. 0 k1 [9 ~2 y7 c
  2. typedef struct tagNMHDR* z. S) m& v" @+ w0 E* Y6 x9 k( S
  3. {
    * c* M# Y3 m* Z: k
  4.      HWND hwndFrom;" v( |- Y4 ~6 s4 X; Y
  5.      UINT idFrom;
    - r0 Q1 |" o8 i0 ]
  6.      UINT code;
    ! w2 Z, |- F1 A. B
  7. } NMHDR;
复制代码

3 i: L0 a$ @8 H1 y; c. n: u  其中:
. j$ `6 S/ K" Z0 w( f0 h& Q8 b  hwndFrom 发送方控件的窗口句柄
, A  s& B% F1 N. V" g  idFrom 发送方控件的ID code 通知代码
4 |6 M' e9 R4 H; |8 Q5 g  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下: " o: R8 `3 [/ i# p4 h6 j
  1. ' l) B2 @# y% }
  2. typedef struct tagNMCUSTOMDRAWINFO
    + d6 [% b" |# k
  3. {" b0 T( B9 O$ l) W; T$ ^5 e; `
  4.     NMHDR  hdr;
    " m% q* @: Z$ Y1 X# Q. {5 u) m
  5.     DWORD  dwDrawStage;$ N. c) S# A7 n0 X, `# N6 R
  6.     HDC    hdc;& c) v8 k9 p  n& o1 ^+ h0 [
  7.     RECT   rc;
    + ]$ O5 X, N% [7 Y! X- ?$ v- D" z7 n
  8.     DWORD  dwItemSpec;- l: u" N# z, n3 M
  9.     UINT   uItemState;8 h3 E& g2 c/ t$ |  u( L" a" e. M7 c
  10.     LPARAM lItemlParam;% @. R! h! Y8 A- K6 j/ y$ ~
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码
- v# v( q% O" {0 ]
   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:
  x$ ]/ X6 t6 l类型值 含义 ' N3 |# K3 `6 O, r" K
CDDS_POSTERASE 擦除循环结束
% Y' M9 U3 R2 z4 T$ ~* W# XCDDS_POSTPAINT 绘制循环结束 , X* l5 k, u% \. R0 R
CDDS_PREERASE 准备开始擦除循环
$ t+ K, ]: X0 W8 a' [/ XCDDS_PREPAINT 准备开始绘制循环
' z% P$ Y' `. `& s& NCDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 6 j3 P+ p0 x5 q
CDDS_ITEMPOSTERASE 列表项擦除结束 ) e2 k) m& k9 w+ E
CDDS_ITEMPOSTPAINT 列表项绘制结束 5 Q8 b' ]% _) [. ]( p
CDDS_ITEMPREERASE 准备开始列表项擦除 ! [# y/ i( m/ T: c1 n; |8 ^
CDDS_ITEMPREPAINT 准备开始列表项绘制 , z/ _: c) n9 J$ `2 U$ v
CDDS_SUBITEM 指定列表子项
: K$ B4 `4 I0 `3 R表7 dwDrawStage的类型值与含义  o0 n; s( a" [9 l
  hdc指定了绘制操作所使用的设备环境。 2 S2 |/ \  u9 B( r$ H5 D
  rc指定了将被绘制的矩形区域。 ) Z! z+ x: m$ X" `0 j, [
  dwItemSpec 列表项的索引 1 ?! L: Q& a8 |  K/ _$ z3 ^9 y7 d
  uItemState 当前列表项的状态,其取值如表8所示:2 s5 C3 j1 \1 @( ]% b
类型值 含义 $ H8 \1 K1 y. \3 h
CDIS_CHECKED 标记状态。 - d. g: |1 O; K, [( i
CDIS_DEFAULT 默认状态。
% N. `) j$ ]8 C; G; x0 t4 u; JCDIS_DISABLED 禁止状态。 7 o7 H' S: r# [: z7 {) v
CDIS_FOCUS 焦点状态。   o6 H2 c. l7 [4 ?5 j
CDIS_GRAYED 灰化状态。 ( W4 L( \0 z. u3 F
CDIS_SELECTED 选中状态。 ; m7 y* K/ y& [1 q8 U5 M9 h
CDIS_HOTLIGHT 热点状态。
  i; E* F; c% O5 L4 KCDIS_INDETERMINATE 不定状态。 - M# v6 N6 k6 b, ?' B( A
CDIS_MARKED 标注状态。
9 g0 O1 U: S+ _5 i: F" j# }0 i表8 uItemState的类型值与含义! ~0 U5 v$ e' u) l2 I
  lItemlParam 当前列表项的绑定数据 5 b" o- o% D) F; e. I
  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: & N/ K* P, ?# w6 ?3 c1 r! P
  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:" G) T! q% E' E( G! G
类型值 含义 6 n+ ^9 k# y2 K1 x: T7 e
CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。
, J. p! A+ \; ?" n: H5 nCDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。 7 _  X" @6 h7 F9 Y
CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。
+ k) S( u/ g! F$ u2 i! rCDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。
3 X' G7 d2 H( x  c; u表9 pResult的类型值与含义(一)
7 Q# G2 E: S" r5 t( v6 }2 x% \9 R- V0 v  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:3 Y! K- t7 A9 b" C0 b  J
类型值 含义 4 ~4 _2 B% ?# n# q: u4 _8 v9 J
CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。 0 m  d. k1 p0 h, B
CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。 , a: C9 L" ^! C& ~$ w
CDRF_SKIPDEFAULT 系统不必再绘制该子项。: V/ z9 ]+ C8 R
表10 pResult的类型值与含义(二)- k5 C  e3 Y0 g9 p
  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子: 8 g( P4 d% K4 D' b/ a9 t& m$ U( }+ R( C
  图12 利用NM_CUSTOMDRAW消息美化界面
/ P- q, k5 G( v, b  对应代码如下: ' K7 d; j4 U& y2 }

  1. ; H( c. D( q" S+ h
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)6 v* j' f0 a0 X. T( v
  3. {$ P; S; f. {* C3 e9 b( O
  4.         //类型安全转换+ O3 F/ g7 t! Y% \0 K
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);7 r5 F: \* \( y7 @
  6.         *pResult = 0;
    ! t- r5 Y1 }& m2 g8 h
  7.         
    7 n4 h9 {( ^; ]' z
  8.         //指定列表项绘制前后发送消息  ^9 L4 R2 d6 d% T' f# ^2 G
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)& ?& W6 ]' z9 @  A/ D  s/ B
  10.         {& A; \0 n1 i  P1 c
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;
    : v" X1 |9 W( ]7 g& ~) o
  12.         }
    0 p4 R+ x& F. `: n4 R6 z
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)
    * ~/ n3 p% s( R2 Q3 \6 n* u
  14.         {7 u! K- z6 |' S* c  {. ~0 {
  15.                 //奇数行
    5 T, c2 N# k: q2 z* x8 h- `/ W
  16.                 if(pLVCD->nmcd.dwItemSpec % 2)
    9 ~4 d+ y% g/ [0 G  q
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);
    0 G/ m; k) D! ~) t# j  \
  18.                 //偶数行7 B% n. x" y/ C5 `
  19.                 else: ]4 R5 R3 {# [, S- i0 q% [2 r- ]
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);% ^: t; y7 R9 I4 R  L- j' F
  21.                 //继续! ~7 j1 H8 s0 T+ M' A# Y
  22.                 *pResult = CDRF_DODEFAULT;; p  S2 i9 t. x$ a8 Y
  23.         }
      |9 p& e4 O' \
  24. }
复制代码

: s5 {2 }6 ~2 q% }) x" t  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。
8 Y5 K8 ]  @: L& ]2 L3 D* @7 z! P% d' M, h/ a+ v$ L7 L" V
  3.4 使用MFC类的虚函数机制
7 w) o% |& b( m5 Q# Z! D8 K3 Y  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子: . C  q" Y# a2 e" C
  1. & |  s! |6 M/ _9 G
  2. void CView::OnPaint()% M' ]! P3 p9 i0 V
  3. {$ h$ m# x9 V: r, v7 f' M
  4.         // standard paint routine+ k; w1 v2 n& S+ o( \
  5.         CPaintDC dc(this);  ]/ R) `* A  d1 f& c/ J; E( m
  6.         OnPrepareDC(&dc);9 S4 v/ i4 q* U! H+ w/ z7 c
  7.         OnDraw(&dc);# \3 X' ^1 b6 l7 {
  8. }
复制代码
# O4 S, @; s6 o; @. Y5 ~+ T  C
  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。
' _0 z! |  ~; ]1 W- |  以下列出了与界面美化相关的虚函数,参数说明略去:
3 d* M& [0 k! C  N2 C; j7 WCButton::DrawItem 5 O' ?  p8 J8 u
CCheckListBox::DrawItem 8 f! H5 v0 a1 ]3 A
CComboBox::DrawItem + n9 n. K4 |0 l# U! O2 o
CHeaderCtrl::DrawItem 3 ~( z5 M6 \+ N+ K% Q
CListBox::DrawItem + P8 X% F# y, Z0 x0 W) C
CMenu::DrawItem
0 G0 r! b( l  u' n, M  i) r: U% m/ vCStatusBar::DrawItem
1 x: f2 c$ a- L% _" f2 D6 e2 T% ZCStatusBarCtrl::DrawItem
) p. g7 w( g# @CTabCtrl::DrawItem
7 I! I  l/ x7 Y9 x2 o' A8 Z# Lvirtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct ); 6 f" [+ x9 `0 |- V7 Q+ L! k% T
Owner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。) y- {8 l5 Z% v
  限于篇幅,在此不再附以例程。 . I, l# s0 j% A/ O: z' L/ `2 \

+ L# p1 c# y6 T参考文献
: Z9 l' P9 w* D. l- Q0 T+ u; ]本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2025-8-9 02:55 , Processed in 0.037078 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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