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

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇
6 X3 W. q: @* t# n9 v# }) x  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。  n% }( x  \. g
  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。
  Y# @! \. K. R0 @& A# K, t& k# x8 p& C& r' i
  2. 美化界面之基础篇4 L! s% x7 @; R4 ~
  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……
+ y  O; R* l+ \4 i9 l# P* f1 w( X- J2 `% n  S3 Z8 }
  2.1 Windows下的绘图操作* a" ]; D8 B6 w# u
  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……
4 u; I2 v1 m4 R  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。
% ^- q: R8 S. \# Z& l& X$ F% H( ?
* @6 a+ q1 K$ }+ w' c  2.1.1 设备环境类
0 a  }+ h) m0 b! R) T) b+ f2 U! _5 c  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。
2 d4 x7 H2 j8 m  |) L5 v  h/ H$ `& Q0 J  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:* H! _( H3 a# U, B
  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式
, Y4 c3 ~! Q6 |, }" s# i! F3 l  Mapping Functions:映射操作- ]5 F/ }7 \0 U+ v! ]
  Coordinate Functions:坐标操作
) P4 n( I0 v6 Q$ s9 R3 @! L+ w* F  Clipping Functions:剪切操作
4 U' J9 f; o- f/ E2 ^% z7 s  Line-Output Functions:画线操作
( P; d! z4 p' R" |) G  Simple Drawing Functions:简单绘图操作,如:绘制矩形框
+ n5 M& _  \" Y4 h  Ellipse and Polygon Functions:椭圆/多边形操作
. ]  W0 D7 B7 ]6 T  Text Functions:文字输出操作+ ^6 p3 |- a0 ?- l" [' D% v
  Printer Escape Functions:打印操作! [' }) j  Y; H" K' `
  Scrolling Functions:滚动操作8 D. Z! i, c2 Q
  *Bitmap Functions:位图操作
6 {+ a  B6 L/ J. U% K  *Region Functions:区域操作
+ a& u/ E% k& Q% S+ |4 k- r" V' p" @  *Font Functions:字体操作8 `: M$ |3 s8 C2 E, d
  *Color and Color Palette Functions:颜色/调色板操作- F& \' A% M8 u% @# |+ B
  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。& w% f: O. V0 `& N( y
9 j# J+ ]4 `' u( Y
  2.1.2 图形对象类* _& g  C: C  }
  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。
, a- J9 K* m2 v2 S* Q  c; i2 v  下面的表格列出了MFC的图形对象类:
. S$ }& q# [; m6 d  MFC类 图形对象句柄 图形对象目的
% {" l+ l) m+ ~; @' B  CBitmap HBITMAP 内存中的位图$ R6 v0 F; H; I
  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式. e; l3 ?! E  y. ^) h& |" z8 D
  CFont HFONT 字体特性—写文本时所使用的字体, `+ k3 B- y1 }3 V$ h- n
  CPalette HPALETTE 调色板颜色
2 `) J: G; S" o& D1 `6 X  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细" G4 L9 L% s& y) ]. z
  CRgn HRGN 区域特性—包括定义它的点
0 G# l1 {' E0 u  表1 图形对象类和它们封装的句柄, _4 j& `) |& A; u( e) E$ v9 Y
: k- N+ |5 J( q5 x1 S
  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:
( H. a" m4 L, j- S* ?0 o
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:
) d% F( s1 Q4 w9 M0 C* w
  1. ; P2 C7 X1 q( s9 H) z
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)$ H/ P7 i% ~3 \
  3. {: F9 y* ^/ U5 V' ?- K0 \) ]
  4.         //设置背景色
    ( N0 s# ]9 a1 V& x! ]3 @" ^! x! O
  5.         //CBrush CUi1View::m_Back# q% H  T6 E" k5 |& E& }, k- Z
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));. {$ e3 A5 p) Z3 q
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL);
    8 ^: `0 s$ o6 e; w& c
  8.        return CView::PreCreateWindow(cs);
    , I, T1 [6 E) U; v6 T& A# T4 {
  9. }
    ) @' O( S' E) R3 i) N6 P( D! G
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)& w8 o, ~, ^( ~8 M7 ]
  11. {
    # W) _8 O3 N! G" g. j
  12.         if (CView::OnCreate(lpCreateStruct) == -1)
    ; ~) h; r1 w( w0 B: T/ ]& N. B
  13.                 return -1;2 r% B: I+ f7 ?/ x1 K# s1 ^- j
  14.         //创建字体
    6 U, s9 z/ ]2 v0 s) a8 g" Y
  15.         //CFont CUi1View::m_Font3 t- |3 l1 X- b1 [" Z! G; ]
  16.         m_Font.CreatePointFont(120, "Impact"); 7 d$ A* ]& X: U8 o$ ^4 e. K
  17.                return 0;
    6 v7 V4 ~) K; }  x/ q' e) l
  18. }
    % y5 n2 F3 c2 {: @* W
  19. void CUi1View::OnDraw(CDC* pDC)
    # O2 C. x1 f. K( ^" f& j# n* d
  20. {0 s5 }7 d& M$ ^8 `7 ^& d! F
  21.         //绘制按钮框架% b0 q4 B+ J4 T8 T% C- Z* b
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);. [2 d( \+ s! P: q( X6 `# r
  23.         //输出文字) Z& q6 I7 m! M$ h) R% |
  24.         pDC->SetBkMode(TRANSPARENT);
    ' [' Q$ x7 @6 g7 f; I
  25.         pDC->TextOut(120, 120, "Hello, CFan!");
    9 A- N4 _5 `4 J
  26. }
复制代码

2 T' B- B+ f' c  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。 / G9 X+ N8 H7 e" e8 C. o. b

; C5 j" e" L  E; ~3 w. G- f  2.2 Windows的幕后绘图操作
) y% V7 ~! B/ [6 H( N  在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。! ], v4 z( A$ }' }
  有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。0 T# X4 S4 a; C4 R) e7 j
  所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……& G. Q3 r0 u5 h# W- W2 }
  有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。
9 G8 F* W. Q. ^5 B: a5 C
6 e. J& A9 a! ~; p) d6 I3 b  3. 美化界面之实现篇! i0 N! J3 c1 j, j  ^6 ]* b& A
  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。! t- l9 H9 U4 b9 H# z1 a
) G, E% W; ~/ s6 Z( ~; @. u# Z
  3.1 美化界面的途径  H6 X4 F: Y8 ~- t' f: w# g
  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:' _' O# D  R* F! V% Q
  1. 使用MFC类的既有函数,设定界面属性;
" w1 J. R' P4 A4 z+ ^8 }# Z  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;% Z2 D) r( w" x0 r) A8 ^
  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;  Q, H. I! n. s9 e! E
  一般来说,应用程序可以通过以下两种途径来实现以上的方法:$ |6 g  Y+ o8 S6 Z$ @
  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;
6 Z. G+ e# h8 O6 {  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。9 q' d1 T0 c4 ^! `$ U
  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:
. F/ K+ H3 h! ]  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示:
2 [7 S. m$ P5 ?9 [0 |8 h, N
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;
7 ?" S, J: x, }$ J5 y: c, O+ t1 K  以下的章节将综合地使用以上的方法,请读者朋友留心观察。/ D* D: T7 x  F
  `, L- h; P- C* P8 `* A! F4 V
  3.2 使用MFC类的既有函数+ E# {/ S( `* x9 r; ^# \
  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。$ b( _3 d4 ?% L7 H) ?7 E7 d- I

, u' R# v4 z. P1 wCWinApp::SetDialogBkColor) `$ |) J# ^/ T; i0 F7 y% R
void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );
& ~+ b: ?& A5 |  T: w9 y指定对话框的背景色和文本颜色。5 l+ h$ ]6 L5 S/ V

5 c  ], [$ S# {8 v/ pCListCtrl::SetBkColor# @: ?, I# Z) b; o) j# ?$ K
CReBarCtrl::SetBkColor
' r( w/ B3 h- u/ A- WCStatusBarCtrl::SetBkColor- [1 J5 n" V0 J. L" p
CTreeCtrl::SetBkColor
$ p5 f0 {% M& `8 T" E' C$ CCOLORREF SetBkColor( COLORREF clr );
% N$ w6 `7 S- q4 \; E3 w* ~6 E设定背景色。
4 N  [5 _) P9 Z1 Y" t. A% ^5 k7 \( c2 K
CListCtrl::SetTextColor# Y: f2 L$ Z3 C6 ~; s' M( j
CReBarCtrl::SetTextColor" m: p/ K& E( X9 M2 J# c8 q2 Z2 I+ @
CTreeCtrl::SetTextColor. n& E7 Z7 h/ A+ S& @4 a2 a
COLORREF SetTextColor( COLORREF clr );
4 p. t( J: b. `设定文本颜色。
# A* {8 U0 L$ o' i# \( q1 m$ n* e
5 A: F! s0 }" S7 sCListCtrl::SetBkImage
% P1 b" u/ ]( MBOOL SetBkImage( LVBKIMAGE* plvbkImage );
2 T. K2 V5 g3 w) I0 ABOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);2 f6 H3 F6 H( K: v8 [! N
BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );! f. n8 z' C- d: `9 K( w/ m
设定列表控件的背景图片。
# O* c% K" M4 c4 i/ x
' m+ }; N. [- [9 l: L7 y9 y9 S0 iCComboBoxEx::SetExtendedStyle. V3 V; \$ S  W7 ?8 [" _4 F) n1 y
CListCtrl::SetExtendedStyle
; @2 O+ F; w" d& v% k* Z2 N" s4 K" s& uCTabCtrl::SetExtendedStyle
3 S4 O8 s$ l4 N' J  rCToolBarCtrl::SetExtendedStyle
9 D! k$ l; ^' {  _$ g0 W+ ^DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles );
- i. e. U7 ?+ I& R$ K设置控件的扩展属性,例如:设置列表控件属性带有表格线。
/ C& s& x# e$ e* f  图4是个简单应用MFC类的既有函数来改善Windows界面的例子:
( |% v$ E9 W; h7 m4 w1 i5 Q
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:
5 q  x) Q5 I% t  n$ z, W

  1. 2 n# l* t; T) j7 W
  2. BOOL CUi2App::InitInstance()3 l+ c% d, K6 W. e7 y! R; H+ x) s
  3. {: ^3 H8 ~9 N" a9 m& X5 Y5 l
  4.         //…
    0 J, D3 ~# q% M# B8 C
  5.         //设置对话框背景色和字体颜色- w5 e, ~7 ~- {7 n9 d" z
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
    ; k5 \0 ?" `1 X3 E' `4 }$ l! z
  7.          //…
    ' |4 e" z' j2 b+ n& E7 {# U
  8. }
    , b) x! G; T! c
  9. BOOL CUi2Dlg::OnInitDialog()) ]7 T  A' E4 z; {9 r, w
  10. {9 n- }- N$ S+ W2 {: V$ x& N
  11.         //…
    / P2 L9 h6 p* v) O+ y& q5 s) {
  12.         //设置列表控件属性带有表格线0 P6 i9 W6 N0 Z: X, h
  13.         DWORD NewStyle = m_List.GetExtendedStyle();. R( _* n6 H5 c; r; ~
  14.     NewStyle |= LVS_EX_GRIDLINES;; G  {. Q  h* W8 v7 H& @+ r+ h
  15. m_List.SetExtendedStyle(NewStyle);* R* O( d7 g6 r/ O! q; `' T! R
  16.         //设置列表控件字体颜色为红色6 x* Q* l: l8 R% b
  17.         m_List.SetTextColor(RGB(255, 0, 0));
    " f6 A6 e4 C% a* S
  18.         //填充数据6 h+ \3 D# F4 U& N
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);3 r4 Q  p: Y. T' t$ P6 U
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);4 r1 D7 O" R7 W6 Q% A  O- i
  21.         m_List.InsertItem(0, "5854165");7 f4 [. ]6 j8 X; m
  22.         m_List.SetItemText(0, 1, "白乔");4 c" E, }, F; S8 U$ M9 h
  23.         m_List.InsertItem(1, "6823864");4 V+ N7 a& l" C: t2 T' ~3 r/ D
  24.         m_List.SetItemText(1, 1, "Satan");
    / G$ N7 V1 `! P  l$ I
  25.         //…
    5 R* P- v7 R- [* _+ w. B
  26. }
复制代码

6 P- V. L3 E0 H9 B% j& s8 V  嗯,这样的界面还算不错吧?
8 @# f# P3 `/ d+ t5 v; p% h7 s% }8 L* Z$ T# p) \* I* l5 A
  3.3 使用Windows的消息机制
" `0 L. p, d- j; h  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息:
3 |! S2 {7 X- HWM_PAINT
' u' k4 p5 D/ H+ D4 d' H5 R: d9 OWM_ERASEBKGND
9 `. @( q& p3 u6 |WM_CTLCOLOR*
9 v4 F+ z* Y' b( U6 ^WM_DRAWITEM*
, |* s/ `" x+ x  [3 ]2 h, VWM_MEASUREITEM*
5 `# P/ G3 j- |: @8 S/ @% INM_CUSTOMDRAW*
8 L. ?( J% {- s: U' h' k) J  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。
6 A  k$ I* a; h: n
* C% m1 Q: w& n7 z, l" u5 ~  3.3.1 WM_PAINT4 V: a4 a" g2 }( R1 ?# m2 n
  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。
9 \3 ~6 ]2 Y6 V0 ?" g( R! ]/ c  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下: 0 ^& l- l) L% G: |7 R: u! i( i1 ?
  afx_msg void OnPaint();
' S# E; N5 b  f3 _" x- E  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: 0 Z$ O, g! D7 X- Z' [3 f) I& ~
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单: ! V3 O/ f* P1 g' L
  1. 5 h" U; r: u% ^! k7 j
  2. void CLazyStatic::OnPaint()
    5 i5 q& d+ m1 {! t. m
  3. {" }/ T& P- I6 ?% F8 W
  4.         CPaintDC dc(this); // device context for painting8 y' v  J" M/ u/ C* Q
  5.          ; o: |- r  p, y% g7 D4 s  ]
  6.        //什么都不输出,仅仅画一个矩形框( k* U8 i6 ^& H6 @
  7.         CRect rc;7 B* n( F# {! F2 }: Q  X+ h
  8.         GetClientRect(&rc);
    0 b& s' ]0 n! ^$ o2 D& l
  9.         dc.Rectangle(rc);
    # ], Z4 G( q6 ]1 H) l" f
  10. }
复制代码
- K' _8 n3 y0 }! Z
  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。( I% Q& a% M* L  U4 K
) {! c* ~/ n3 U
  3.3.2 WM_ERASEBKGND * V+ \, a$ M0 ~: E" H
  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。
/ J3 V4 ]$ n# I  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:4 @+ A- V$ a8 ?3 w0 J2 S! l) B0 I
  afx_msg BOOL OnEraseBkgnd( CDC* pDC ); , \6 o3 E4 I0 t$ ^4 ~) G
  返回值: 5 ?. v# P3 ?/ T
  指定背景是否已清除,如果为FALSE,系统将自动清除
( F7 m) V3 r7 F( t7 Y/ f4 X9 B& a  参数: pDC指定了绘制操作所使用的设备环境。 7 J; U2 i  X0 P, j) m9 u, N" n
  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景: " G  h, m1 X& w+ B: W6 Q
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单: ; R2 A) P. u# j3 {7 C9 E. |

  1.   Y" ^0 l% n* w7 X. Z. U
  2. BOOL CUi4Dlg::OnInitDialog()
    : g6 H' o8 f- V. M6 g% o8 U
  3. {" k! P7 G7 t. B) P7 @: t( b
  4. //…
    ! b  f/ W$ Y6 k
  5.         //加载位图3 |. }# |( f7 q
  6.         //CBitmap m_Back;3 s5 M7 B$ p: a! b. v! l: H+ P
  7.         m_Back.LoadBitmap(IDB_BACK);
    . D( y$ x1 u, l$ y3 C! _
  8.         //…
    3 Y4 Y1 L* ?4 A3 R
  9. }
    8 ]* G, p9 f. p: Y" V
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC)
    1 M2 i  R6 F& ]3 ~' Z
  11. {% M' x: k* K8 G3 U$ M
  12.         CDC dc;
    ! f8 J  x7 T: O& C& x& \
  13.         dc.CreateCompatibleDC(pDC);  G+ Q3 }- y2 t9 ?% y* J
  14.         dc.SelectObject(&m_Back);
    ) F  P6 q5 _: B1 u
  15.         //获取BITMAP对象
    7 E+ o3 _' A# d
  16.         BITMAP hb;
    # i7 p3 a4 k& r  q
  17.         m_Back.GetBitmap(&hb);. ^7 }9 @$ L% A7 G7 u+ u: X% h& n
  18.         //获取窗口大小8 [# s/ s7 ^, W* w
  19.         CRect rt;
    9 D, F, j- ~, \9 {7 d; k
  20.         GetClientRect(&rt);; L  w5 l/ g& C: P1 E" _
  21.         //显示位图
    & C- w( k& ~* _0 o
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),( z' K  {: P0 i) b1 }( A
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);
    ) Y3 @; ]. T/ A2 \4 j; A% T+ b
  24.         return TRUE;
    7 t' B+ K- d- ^9 ^  A* i
  25. }
    - L1 j( c9 {2 U, V; q; U0 I
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) ! [5 r! s  O1 a
  27. {, P, L( U& X/ l% }0 y0 Q
  28.         //设置透明背景模式
    1 R. V, P. Q9 p* E. b
  29.         pDC->SetBkMode(TRANSPARENT);3 c7 c) J8 S" ]7 ^9 J7 \
  30.         //设置背景刷子为空7 [. l9 c$ V( t' v
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);1 k* O$ z- R* G' E$ }( ?1 J) A
  32. }
复制代码
. U, `/ C+ c  Q' {2 x1 K2 S
  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。 3 a* e$ E$ r+ F- J% [8 `0 G
# m! R9 c% ?1 c6 e! `) s
  3.3.3 WM_CTLCOLOR 4 O! A; r$ u) ]" w, ?/ C. ^
  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。 ) T9 w! @6 _* ?* m
  WM_CTLCOLOR的映射函数原型如下: / h% u- H3 h. w# _3 L: J
  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );
6 y1 Q, T" q! _' H4 T  H0 q/ h  返回值: 用以指定背景的刷子 % j/ n7 U# C1 \; J1 g6 a
  参数:
! t; u, J, o8 S8 Q6 e' M; c7 U  pDC指定了绘制操作所使用的设备环境。 5 V0 j1 ]1 c$ X% H
  pWnd 控件指针 ; u0 V! g) }6 D2 D+ e
  nCtlColor 指定控件类型,其取值如表2所示:( @# m+ k  S8 |1 c+ }
  类型值 含义
4 k7 m  x9 P' o3 @CTLCOLOR_BTN 按钮控件
' ^: G3 l  C# c! H6 aCTLCOLOR_DLG 对话框 0 w% z- X; d1 v
CTLCOLOR_EDIT  编辑控件
1 g3 T' R% \3 T7 c' fCTLCOLOR_LISTBOX  列表框
; Y$ Q  m  X2 t7 J( O/ J- oCTLCOLOR_MSGBOX  消息框
/ Y- T% _# G5 h8 Z6 h& {! yCTLCOLOR_SCROLLBAR 滚动条 9 F$ N; i6 X/ s# P
CTLCOLOR_STATIC 静态控件 $ q% ~/ q. j& q' I# \
表2 nCtlColor的类型值与含义: K+ U1 C% e# Y" w' ^; U

( |/ X) c- {- _) P  作为一个简单的例子,观察以下的代码:
* E8 k8 j, U. e" p  s4 }& [
  1. + y- n. x& u! v8 @! T. D1 J. l
  2. BOOL CUi5Dlg::OnInitDialog()" F$ M+ _3 O1 A' ~  A
  3. {
    3 r( g* v4 `' t" u' N- m8 P) N
  4.         //…0 Z+ E3 f( q( z# }
  5.         //创建字体
      u! X0 m  |2 {6 L
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font2" ]) S# b6 ?1 g+ O6 T. w% ?
  7.         m_Font1.CreatePointFont(120, "Impact");
    2 ]/ Y( Y! d4 g, S5 H
  8.         m_Font3.CreatePointFont(120, "Arial");
    ( Q7 T' A2 C- N" S1 z# W3 d0 k
  9.                 return TRUE;- T  ~' E0 C  }
  10.   // return TRUE  unless you set the focus to a control
    % c" }" w9 h% X* O
  11. }
    2 k, J) S, e+ E' |: ^. P# c# G
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 3 }" c% j8 K5 D% B* g' Q1 I
  13. {4 K- [9 K1 Q* S0 E
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);& H3 u+ |3 j! V$ ]* T
  15.         if(nCtlColor == CTLCOLOR_STATIC)% `( H* K7 r/ Y4 ~" j6 E
  16.         {  ]! i7 e/ ~* f6 c
  17.                 //区分静态控件
    2 |# F1 _0 z* n1 y
  18.                 switch(pWnd->GetDlgCtrlID()). a6 q& c9 j+ x6 {, @% s4 _! @; a
  19.                 {% B9 R7 L9 e! i4 i" T# o
  20.                         case IDC_STATIC1:
    5 V- v2 _+ }: }
  21.                         {
    ' o5 l( \/ O0 @7 Z7 Q8 C
  22.                                 pDC->SelectObject(&m_Font1);. D: ^2 ?- F2 _7 k. S
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));
    $ c  d' \( P6 {4 w4 F2 n
  24.                                 break;
    . Z  |8 {. x4 a0 H0 q
  25.                         }$ R5 R; D' x) u" `4 \
  26.                         case IDC_STATIC2:" T* u+ h  ~6 p: P: R5 Y
  27.                         {
    " K4 E) a# o8 |: z1 }7 J' Q
  28.                                 pDC->SelectObject(&m_Font2);
    ) u0 s$ F4 H2 Q; {. ~& i
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));) ~: r3 `, z7 N) L& D9 b3 m2 r/ z
  30.                                 break;
    2 g: E, a& s( E* K/ ~
  31.                         }$ X% f4 N2 o+ o4 ]/ z
  32.                 }
    ( b4 v& S7 Y& O" X: P. E
  33.         }
    4 ^: I% g' T0 R) |# k7 I3 U
  34.         return hbr;
    ' U% e, ^* W2 j8 t; C5 M% U
  35. }
复制代码

/ E' r2 W% a2 U8 P9 X   生成的界面如下:
& R/ E7 u: s1 _# O1 E
图7 利用WM_CTLCOLOR消息美化界面

& s. E% A3 V9 v% a1 {  3.3.4 WM_DRAWITEM % A- n# W8 W8 o* \; K' j( |, A8 d
  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。, m6 q; w) w: S5 B% }% r4 {
  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。   W, a( _5 k- x9 h+ c, M  v
  WM_DRAWITEM的映射函数原型如下: ( I1 m& S, \! P* D( a% b6 D
  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );- N7 a  m* [. M
  参数:
0 Q4 V* c7 s' k% g5 K. j) H  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0
% R2 l# l5 S' Y& A5 s6 H3 G" G+ f/ A  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下: . ]8 F& _- c! d, l# b: s  \
  1. 5 Y# k, W+ u; z1 h% W: x
  2. typedef struct tagDRAWITEMSTRUCT
    $ B, Y6 r& H( k  s
  3. {% e( D" F' P* ~- j  N
  4.     UINT   CtlType;
    * C! t9 p$ J; a/ A* L/ m
  5.      UINT   CtlID;
    # s6 T; F9 x4 l  \
  6.      UINT   itemID;
    ) w% g6 f$ ^( q, b9 A7 D, G
  7.     UINT   itemAction;
    : r' C' l; N' N/ K
  8.     UINT   itemState;
    ; x  ?3 w" z+ Q( i9 C, k" H
  9.     HWND   hwndItem;
    5 r* `, i; F( s6 i- O) V4 D
  10.     HDC    hDC;
    - C* L& _5 g1 N
  11.     RECT   rcItem;
      _7 [) y+ V7 t7 h
  12.     DWORD  itemData;
    . N2 X) |5 x: |; H0 v- E- b
  13. }DRAWITEMSTRUCT;
复制代码

' C1 C9 i- h2 M$ t$ w4 f9 ICtlType指定了控件的类型,其取值如表3所示:
8 K: x& y* K# s8 g' c/ v- `1 |" {类型值 含义 " j6 _6 T/ n1 A8 }
ODT_BUTTON 按钮控件
3 K7 {) T# U0 A; R, f) TODT_COMBOBOX 组合框控件
6 x$ X# z# O3 d7 }  m2 UODT_LISTBOX 列表框控件 1 K2 c( Q6 s- E# P. ^' }
ODT_LISTVIEW 列表视图 # T+ ]" g$ H' _' e8 y* T2 i: M
ODT_MENU 菜单项 " H) t& R' g4 c
ODT_STATIC 静态文本控件 " P+ A1 X* I5 P. y* w8 G) ^
ODT_TAB Tab控件 # n( j- b. [5 G, Z8 t5 \. T
表3 CtlType的类型值与含义$ L7 b, X' \: |. u: a7 r' H: ^; x8 x

, ]0 |) S" m' C# Y% n! y6 \! s4 P  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
1 [) k. H  B2 V+ K3 j  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。 " ~' `( x, m. v6 j7 _
  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:. x  O" [3 t! g6 L; c
类型值 含义 9 f3 ?: E* R' t4 e/ N
ODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。 " C+ K/ q2 |: O1 [7 a0 g
ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。 4 X# i& w% a5 _# t
ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 4 m/ }5 _* d; }9 T! r- J) r
表4 itemAction的类型值与含义6 u, P+ W( I3 z  d
  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:/ x/ [( R% h+ u7 v2 S" Z) f" f
类型值 含义 $ p' T8 X' l* g$ ~4 y; h" i) S
ODS_CHECKED 标记状态,仅适用于菜单项。
. L& N6 m1 y' o2 M: KODS_DEFAULT 默认状态。 ; L: R+ y0 k9 S) @5 Y* j0 q1 J! k
ODS_DISABLED 禁止状态。 & t" }4 F6 \6 u
ODS_FOCUS 焦点状态。 / C+ z0 M" a( s) E1 v) X
ODS_GRAYED 灰化状态,仅适用于菜单项。 : {3 ^) a" I1 I# Q# C4 z& L8 W1 r
ODS_SELECTED 选中状态。
/ W+ P" q5 n3 P2 r9 jODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。 ' k0 u4 W$ q7 u' C
ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。
, G+ X3 _  p; f9 HODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。
3 t) v' `: q9 Q5 u! L2 T" D" h7 U) XODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。
" d7 [( s9 ?9 j( M$ gODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。 5 o4 T6 a, l" I* T! _7 w( O
表5 itemState的类型值与含义2 ^# q, y$ _7 B# p$ M$ w
  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。
. @* W* n/ i7 l  hDC 指定了绘制操作所使用的设备环境。 ( o5 S0 G5 S# ]0 g# K2 }
  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。
  p) t: {( H( G) _1 n: l  itemData , W, H0 Z4 T/ {+ s# V0 }
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
1 G( s+ c( q% ^* V2 }) T1 A  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
! i4 O; d$ v  \9 ^: D6 A# F  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。 # o7 h# ]  i1 x
  图5是个相应的例子,它修改了按钮的界面: / q3 U% b  x  n/ u$ b
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下:
6 s4 Z  G8 s( {, y# q8 h

  1. 5 F6 N5 d8 L, E7 Q9 o" q8 D
  2. BOOL CUi6Dlg::OnInitDialog()
    3 x3 C( J1 j' L/ e* k0 ], A
  3. {
    " X4 w4 q5 j! B0 o# J* z
  4.         //…
      z- |( S9 z6 w' w' V7 k: ?
  5.         //创建字体
    ' T% q2 y8 F( p6 d% g
  6.         //CFont CUi1View::m_Font
    ! q2 z8 J$ H2 X9 y
  7.         m_Font.CreatePointFont(120, "Impact");0 p/ q) W& @8 N  a- h( e7 w4 ]
  8.         //…' L' \1 G) \( A8 q
  9. }# k( M2 }8 L3 B8 J- {& W
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) . m1 {+ V+ N* V7 U1 ]
  11. {- ?6 [6 R; B5 Z) a0 l
  12.         if(nIDCtl == IDC_HELLO_CFAN)
    / r! d  n8 G4 i
  13.         {- P! a4 z- q7 X5 f) X) k- U
  14.                 //绘制按钮框架3 A( R3 x7 ^3 O* |
  15.                 UINT uStyle = DFCS_BUTTONPUSH;
    $ ]( k% b7 p$ [* T: M
  16.                 //是否按下去了?
    - ?/ y& N6 n. |% f! G! Q
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)% n/ {% y2 ]; J. M- F6 g+ e% r
  18.                         uStyle |= DFCS_PUSHED;
    ) t. ]2 Q/ {2 B: Z: X( Y8 W
  19.                 CDC dc;! u3 S, v2 h2 S. S! @$ _. p+ B5 G& f
  20.                 dc.Attach(lpDrawItemStruct->hDC);# h! F" _' F8 t4 ?8 ^" l! j
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);
    # G0 d5 i6 v( O* }" o3 J& l
  22.                 //输出文字
    + p% ~, l3 K+ W. Y% g: _4 F; {& i
  23.                 dc.SelectObject(&m_Font);
    8 a  k( G4 r5 _/ S
  24.                 dc.SetTextColor(RGB(0, 0, 255));
      h0 b2 y  d/ ]$ z+ H
  25.                 dc.SetBkMode(TRANSPARENT);$ @" c8 O8 P! l8 u) k& ]& N
  26.                 CString sText;
    ( u$ L. H1 n# W! r4 e
  27.                 m_HelloCFan.GetWindowText(sText);, g0 t# E. T. R; `: [
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);0 w: Z! t$ }! E4 B1 }+ F
  29.                 //是否得到焦点
    . }  m  p! M$ l' g* Z
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)
    + |4 [* s+ K# n# W* t/ m0 E/ P7 N& g
  31.                 {
    - Z6 h/ |' ?( I+ u& ]" A
  32.                         //画虚框
    ( j6 [' b. d5 [  e4 d- t; S
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem; 4 G( \3 _1 ]! X
  34.                        rtFocus.DeflateRect(3, 3);
    2 h$ K: s% n; p, O1 L$ q
  35.                         dc.DrawFocusRect(&rtFocus);
    4 [2 r6 N% A$ b
  36.                 }
    3 f. T7 }5 n: A4 U, h
  37.                 return;1 z2 t% x. |% h- i
  38.         }
    : z: C# H0 ?) Y+ _+ |1 ~0 Q
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
    1 _( U& I$ U/ I& h" @
  40. }
复制代码

2 Z  ^. |( B  `4 w0 i3 o  别忘了标记Owner draw属性: / F7 b) r# B$ B' C2 J0 T7 S
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。
3 E0 P6 \3 ~" O$ j# l. g  d  Z6 k# J0 Y, M
  3.3.5 WM_MEASUREITEM - U7 O, ?5 L7 I$ f6 Z4 w
  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
! \. s9 A0 k* f5 _5 b  WM_DRAWITEM的映射函数原型如下:
% {% Q1 s% r2 L- h7 M4 t  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
/ K! j  x2 d$ E" _  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l0 C- T' V# Z# j( F: B
  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下:
( E" X( H' E$ V9 b/ U, T# Z, U

  1. / C1 z" F7 i3 D4 Q/ P+ b& u
  2. typedef struct tagMEASUREITEMSTRUCT
    - B4 B8 k/ R* ~& u" [: c8 n
  3. {5 X, w% H8 R" W9 S2 X
  4.     UINT   CtlType;: |5 w0 M8 ]3 v8 R
  5.     UINT   CtlID;
    . e9 t! T# {; ^* P  H* |" R' M) @
  6.     UINT   itemID;% ]0 \6 z% C5 S0 G4 n' w; y7 s
  7.     UINT   itemWidth;8 |) c; f. C1 W3 l( N% c
  8.     UINT   itemHeight;
    . y2 o2 s, Z" n% |9 L- d
  9.     DWORD  itemData;
    9 h3 f) t3 [" _) z: q" |( R  z
  10. } MEASUREITEMSTRUCT;
复制代码

, L9 f- N9 w3 i: }8 g1 P5 b% w' g- H  CtlType指定了控件的类型,其取值如表6所示:+ F/ n- R% w1 W2 s7 c/ }+ h+ H
类型值 含义
0 A0 q, s' ], z8 V. |5 HODT_COMBOBOX 组合框控件
( C7 }1 Y9 d0 @; ]- f. N; e% HODT_LISTBOX 列表框控件 " ~8 r; B, |4 m  W0 Q) c
ODT_MENU 菜单项
1 v$ D: z, j- q/ R表6 CtlType的类型值与含义; d7 ?8 |* H% P' Y
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
* X& i) c" I7 Q  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。   B/ H- a& `( }
  itemWidth 指定菜单项的宽度   t8 {1 \. m$ m5 n& I& [6 z
  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255
. S3 c+ }" l5 N  itemData
$ l8 R( R- v5 ], c1 ]( `( _  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 2 U( N+ ]  v7 e* g* A6 a
  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
+ r+ ^. J: z% X! D+ u  图示出了OnMeasureItem的效果:
- p1 F4 u- U; I  S1 Z
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下:
8 M6 \6 Z) v# o9 @. j5 q& V# a' X
  1. - s, n) Q9 Z4 C  g
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
    , k9 V2 E$ f$ J
  3. {
    : S4 l4 n6 Y% T; B% c
  4.         if(nIDCtl == IDC_COLOR_PICKER)
    ( k, ], o1 f. y# Q+ r
  5.         {2 S6 A* a" B( F; w( B0 N8 a5 @
  6.                 //设定高度为30
    - O! f& b2 r6 T6 \- B
  7.                 lpMeasureItemStruct->itemHeight = 30;
    ' Q. u  s+ e9 D: U2 G- N. Z+ ~
  8.                 return;
    # P' u5 X4 S7 ~; j1 @# M  U% v  b8 `
  9.         }: W! J2 V$ Z9 L, G1 a
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
    . D& \" a) m8 y- F+ @- c
  11. }
复制代码
0 K- p" n0 g1 {. ~# P* g
  同样别忘了指定列表框的Owner draw属性: : T" x2 E- d; y- N6 y8 \9 c0 N  \% d: _
  图11 指定下拉框的Owner draw属性  
: M4 L4 t5 M2 P2 ~: V9 y: j7 z5 f/ _2 a7 F' o* e
  3.3.6 NM_CUSTOMDRAW 6 x$ u  Q% f* |2 g9 O) ~- O
  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。 # b' a1 v# I. I2 t, u; j
  可以反射NM_CUSTOMDRAW消息,如:
0 o/ k7 Q: r( |$ }- S* o1 q+ h% c5 S' Y  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw) / k7 V* N* V2 T3 a
  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult);
$ d6 A4 }/ o. O' Z5 p$ H: _* i  参数: ( ~. D( }1 R- T- |  J& p
  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:
) y9 K. d% {8 T( a2 M0 J0 r

  1. . w* K$ T2 m7 r9 Y
  2. typedef struct tagNMHDR
    5 V9 v; H7 T+ v- x6 T
  3. {5 ~( n0 z8 C( Y8 V! v8 c1 p
  4.      HWND hwndFrom;8 z& c" k; r: D! N; a3 H
  5.      UINT idFrom;: _- w, v  c& x& c
  6.      UINT code; 5 O4 y# ~- Z: i) j& l! X( l
  7. } NMHDR;
复制代码

/ m+ i. h6 Q+ M/ X" m  其中:
5 l4 t2 \0 }0 x4 B' }! k5 A  hwndFrom 发送方控件的窗口句柄 - Y1 B* J7 U. @
  idFrom 发送方控件的ID code 通知代码
  O* l) I: l; i2 b) t8 H8 v2 q  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下:
7 ]* H5 d  N) W, K

  1. $ o0 c7 u! l" `! _) w9 Z
  2. typedef struct tagNMCUSTOMDRAWINFO; u+ r6 X8 U0 N: \$ c3 M
  3. {" _, b! b3 @3 @& E+ B' ~9 T9 L: v
  4.     NMHDR  hdr;
    & e$ \- v' I/ y
  5.     DWORD  dwDrawStage;
    + P; X5 S6 ^5 ^. B0 q6 \
  6.     HDC    hdc;( w+ J6 {. K. c
  7.     RECT   rc;
    4 y& m* Q7 e; L5 |
  8.     DWORD  dwItemSpec;) M! m9 E; s" m9 b" t6 J4 k& _
  9.     UINT   uItemState;
    9 h* V* f& L8 |5 E
  10.     LPARAM lItemlParam;! ?, M1 P6 z( N% @5 s0 P
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码
, j1 V) n9 y+ [
   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:
6 v. a, Z) {- B4 e% E; Y# y类型值 含义 2 M2 S$ t  T3 D. S2 j
CDDS_POSTERASE 擦除循环结束 ' ~6 a$ V+ T8 i* N+ O% r- r/ A
CDDS_POSTPAINT 绘制循环结束 * C) ]( [$ I' r1 K0 y  ~$ F: ~
CDDS_PREERASE 准备开始擦除循环
7 z: R# n9 Y' m3 \, b. fCDDS_PREPAINT 准备开始绘制循环
+ |/ q' y8 i/ m& |CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效
7 d+ j3 ^) U" ]& J$ A/ _3 `CDDS_ITEMPOSTERASE 列表项擦除结束 : x/ ?& t: \  u: F( ], E! H' }: g+ w
CDDS_ITEMPOSTPAINT 列表项绘制结束 % N, }6 v: Q5 H  l' R" L) j
CDDS_ITEMPREERASE 准备开始列表项擦除 + r" _% R+ I/ f
CDDS_ITEMPREPAINT 准备开始列表项绘制
) k, i* A. A: |; x2 ]CDDS_SUBITEM 指定列表子项
  v$ n2 ^) z2 V$ s' x6 }1 L! s* T% }表7 dwDrawStage的类型值与含义  I& {, b5 D0 x  E; U' V7 C
  hdc指定了绘制操作所使用的设备环境。
! i; q. n9 f/ x$ f, Z  rc指定了将被绘制的矩形区域。 # S( i+ a( G+ G
  dwItemSpec 列表项的索引 3 ]+ a3 g3 @, u- j# J
  uItemState 当前列表项的状态,其取值如表8所示:) o5 x$ d# Z/ g( a! M  s
类型值 含义 1 t, P: A. s6 s& t6 \- B! s7 C4 v) {* v
CDIS_CHECKED 标记状态。
. J+ l' |1 G1 F3 o9 ~; L1 o2 t: ?CDIS_DEFAULT 默认状态。
" ]; d0 |3 H6 i5 S+ E) U% g7 [# K. OCDIS_DISABLED 禁止状态。
& G  S3 ~1 {; g2 S9 ?" h6 R( t1 T9 WCDIS_FOCUS 焦点状态。 / ?7 K" A- ]& F9 o4 |
CDIS_GRAYED 灰化状态。
0 U: t+ W% _: r# YCDIS_SELECTED 选中状态。
1 y" Q& P% \7 @: [& Q& nCDIS_HOTLIGHT 热点状态。 . n2 S# s/ D! _9 }, [0 _' E) N: G
CDIS_INDETERMINATE 不定状态。
9 l2 J" J% v5 b: N9 r5 U4 D* C. VCDIS_MARKED 标注状态。
  b9 R) o$ l& z; X1 w8 l表8 uItemState的类型值与含义
, M6 i6 J8 A- M1 x+ D9 d/ v0 ~. `- f  lItemlParam 当前列表项的绑定数据
% M! [" O0 d' ]* T& w- H7 D4 E+ |; q  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage:
2 v* W' Y5 X  w1 M* V4 E& J) H2 w  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:% r% u5 [$ I/ N! A! D2 E
类型值 含义
4 j3 M+ E! L5 O0 o/ vCDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。 $ ?& Q0 m8 e. u8 k
CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。
! ^& G/ W) E5 u  LCDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。 , h' o; i: f) d5 X2 T) c" r
CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。
4 e: |; L$ c9 v. H表9 pResult的类型值与含义(一)
4 Z5 ~  v. c% u) A: z$ f  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:6 ?" ~& g8 H* G" i" S" K3 D
类型值 含义 : Z7 G! Z& k7 M
CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。
) f9 m0 m2 @  ]CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。 2 C( {0 W& [8 T+ p/ }1 z
CDRF_SKIPDEFAULT 系统不必再绘制该子项。
. Z: \. ?( k/ F# O, U% b' j$ b表10 pResult的类型值与含义(二)) m! k5 w% O: x; o% c
  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子:
& m* I+ p0 |4 m+ C0 |/ {8 ?- k+ i
  图12 利用NM_CUSTOMDRAW消息美化界面 ! a/ ]3 K8 q! f' e6 ]
  对应代码如下: ) j# x: R/ ?- m0 P6 T4 h
  1. ; {1 Y6 y1 |& j9 W/ @
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult), S5 f, `; a; k! Y5 Y  `1 ^9 q' I, k
  3. {. U( F& X4 G- z/ [1 Z! u5 J
  4.         //类型安全转换
    3 V3 x* N- [( K# {" t6 \& r
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);5 C$ A* l- U% E, s5 x
  6.         *pResult = 0;
    ! b: f  w$ w, S4 v& ^+ W# G
  7.         ) I0 P( ^' l% c
  8.         //指定列表项绘制前后发送消息
    & n1 F5 a+ v  c9 B3 ?
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)& Y- X7 }+ L7 `
  10.         {
    1 Q/ D" s+ N9 E/ o1 w7 Z
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;- B* P% ]; s3 l( |
  12.         }/ Y$ N& H% W5 I
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)
    6 j7 ^, j! U4 g& m0 V6 G  t* M
  14.         {
    & i: R" T, {  Y
  15.                 //奇数行
    , K6 x0 H6 c1 q& L
  16.                 if(pLVCD->nmcd.dwItemSpec % 2)
    0 A$ j: C: y( Z( d; T
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);
    9 g1 s4 F8 W8 x9 k- l$ v& q
  18.                 //偶数行2 V1 a4 h) [( S2 ]3 ~# r! c  N
  19.                 else0 K, Y8 s- c9 k5 M3 u9 x( B
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);9 G! C+ {% z1 h5 I  J
  21.                 //继续" f! I2 N& ]6 g6 L
  22.                 *pResult = CDRF_DODEFAULT;9 @  ^9 Z' F, o5 S0 k
  23.         }
    5 ~, U' L, }8 A. t$ @
  24. }
复制代码
1 H+ k' G0 }4 F: I+ T' k
  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。 8 @5 U4 _* L3 s9 v. f8 Q3 |

; o  m' _& E: f3 @. ^9 A  3.4 使用MFC类的虚函数机制 . x! d9 p+ H* {8 H% R( t5 z" K
  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子: ) h. ]3 R; N: \! A* G6 @, z

  1. : d( Q% A3 x3 e% ], e' y1 I
  2. void CView::OnPaint()$ W' @8 D& Q# ^: r& n
  3. {
    4 I$ E4 p! X+ x4 o" [$ {4 v* K
  4.         // standard paint routine
    7 m" U# p- w  V
  5.         CPaintDC dc(this);; N3 d/ Q" S# |4 h9 T
  6.         OnPrepareDC(&dc);  [/ \, ^1 |% U' L5 e& {, A7 y
  7.         OnDraw(&dc);: o5 K, {7 M; p8 z  j. I% \( w
  8. }
复制代码

8 R# U/ A) B% w) }) y+ Z7 C. p  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。 9 T" ^' ~0 M1 O+ V# _
  以下列出了与界面美化相关的虚函数,参数说明略去: 6 a/ N9 e3 q7 L- l# K
CButton::DrawItem
% O: M; p; t% g$ m1 q5 uCCheckListBox::DrawItem * `: |( c8 R# ~( U" \1 |( r
CComboBox::DrawItem
8 M: P) D8 J  c) ACHeaderCtrl::DrawItem / B2 `9 B2 W- |+ U+ ?' l7 F4 @
CListBox::DrawItem
. }, i3 G1 |7 B- OCMenu::DrawItem - I% I0 w: P8 x' ~+ d* U5 `7 u
CStatusBar::DrawItem , X& r* \  [4 _0 R# U
CStatusBarCtrl::DrawItem + a. A( T* L, r% C. c8 o' B5 V8 b! C
CTabCtrl::DrawItem, J/ b3 N6 f) }$ k
virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );
; F* G% M5 S$ pOwner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。/ n) [3 n5 v2 s. C
  限于篇幅,在此不再附以例程。 ' R3 T5 @5 F- J# R& O* `7 t
1 u3 D" M2 T7 l& z- A  u; T, A
参考文献: [& @# Q% w$ J+ ]  Y
本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-8-14 00:07 , Processed in 0.023524 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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