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

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇
; ?. d5 [/ d) o0 }; Z0 ^) v  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。
1 U5 o8 n, i) y+ w) O  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。* I# \2 b+ ?& P
4 t* `" K& a. ], z+ K; Y7 Y8 V
  2. 美化界面之基础篇+ R# D- v6 u8 i) ~
  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……
- Q, {" }% |4 O5 W$ a1 Z; B# a; r0 c. `
+ @. g0 C1 T2 u, B- X: L9 i  2.1 Windows下的绘图操作
+ u- q7 {- D, M/ n8 q  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……- ?+ S( A4 U9 \. m
  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。, g. e& R% H/ i' {# G

0 x) U- G* _2 a8 I8 t  2.1.1 设备环境类+ F  l5 j0 R+ `  R7 l, E
  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。- ~( r& h% N0 M6 ^' m5 D
  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:
4 C" A2 p8 ]& C' j7 Y7 }' {  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式
2 V3 U  o+ Y0 C6 X1 B6 P  Mapping Functions:映射操作4 X0 Q2 o3 Y0 Y) j
  Coordinate Functions:坐标操作
3 x2 w! `& A+ G; f5 O6 o  Clipping Functions:剪切操作
; }: a3 {; d4 V& y0 ^1 }8 V  Line-Output Functions:画线操作
, b/ R) R5 A/ k$ i  Simple Drawing Functions:简单绘图操作,如:绘制矩形框
" X$ s/ e5 B7 T* i  Ellipse and Polygon Functions:椭圆/多边形操作2 @5 _9 G/ U) c; H7 h' ]7 }  X! _" P
  Text Functions:文字输出操作
$ U/ {) ]  B6 u& _' n$ U3 c  Printer Escape Functions:打印操作
6 b- ^4 V4 h$ h% e2 D) \  Scrolling Functions:滚动操作
$ g1 B) }* E. R6 u2 y$ z. R% }  *Bitmap Functions:位图操作
& P& T- T- P4 v/ s8 B2 k  *Region Functions:区域操作
0 l% e4 \. E% K0 Y& }  *Font Functions:字体操作  e" o% ~, r; }
  *Color and Color Palette Functions:颜色/调色板操作
0 M  E% |* T$ A  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。: V$ ~! ^; Z- A3 ]! s
0 ]( ^* N4 {/ {, x
  2.1.2 图形对象类  ~; w4 W/ B& n
  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。
( m2 `. S- m; G9 H( _2 n  下面的表格列出了MFC的图形对象类:3 J8 H4 V! ~/ B# U) E1 f9 h
  MFC类 图形对象句柄 图形对象目的
' D% B( l! M4 F# d6 K  CBitmap HBITMAP 内存中的位图
( C# Y1 T5 g" l  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式' e% |& @+ m. w9 R4 b! }
  CFont HFONT 字体特性—写文本时所使用的字体4 G4 [6 G7 @/ e1 d) w, Y3 Z1 T% V9 M
  CPalette HPALETTE 调色板颜色. w; m/ U4 a8 e" O3 I# M5 b
  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细
3 p/ _. K/ p* l7 f  CRgn HRGN 区域特性—包括定义它的点/ V" _' m6 C2 V) _5 |
  表1 图形对象类和它们封装的句柄
6 }9 ^8 u: A7 {& K) z7 t8 s2 ]: |' G+ s8 d5 N2 g1 s* \% O
  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:
: N6 @. v$ d3 R6 g
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:
* `* }/ p0 S3 t0 I# K

  1.   L! M3 Y6 r/ M$ C  ~- {
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)& K2 f0 j/ I& Z! q3 X6 O
  3. {
    2 K9 H! R$ z6 k2 T3 C0 P* ^
  4.         //设置背景色
    6 `6 H% W# r( V) {4 S' H' R
  5.         //CBrush CUi1View::m_Back; J; f1 J; A/ I8 J6 l
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));+ s' ~8 |& W) q" b
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL); 5 N% U: Q) v! r& d7 P$ D
  8.        return CView::PreCreateWindow(cs);0 e" {' B" y$ Y1 j1 [
  9. }" ?- c0 J# x, I4 R9 A
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)& i2 J3 d) e( P; x: y: m( c4 @
  11. {
    0 {$ C" I7 M1 Y8 u( J4 M. s/ \
  12.         if (CView::OnCreate(lpCreateStruct) == -1)% Z8 ^8 w7 l! l; J& r; l1 y; t
  13.                 return -1;( ?0 ]/ V( {1 ~' L1 [: @; l: a
  14.         //创建字体
    4 i: Y& g  w2 e
  15.         //CFont CUi1View::m_Font
    * D$ w& c$ z  @! l" H% v
  16.         m_Font.CreatePointFont(120, "Impact");
    ' N& L9 {) q7 d6 c
  17.                return 0;, v9 S% v5 V1 H
  18. }2 f- ~1 m' Q4 y$ L
  19. void CUi1View::OnDraw(CDC* pDC)
    * S% `) Q+ r' B4 s3 ]
  20. {
    . j6 W$ A& d4 r, m7 G+ L
  21.         //绘制按钮框架
    , u& n1 p/ c1 ?9 T
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);
    * _& {. h' u+ n& G
  23.         //输出文字% [, h0 S& Z0 w$ `
  24.         pDC->SetBkMode(TRANSPARENT);
    8 t. E  A) \& c3 S* n# J3 j
  25.         pDC->TextOut(120, 120, "Hello, CFan!");
    ; R% N" G4 w$ F1 Z, f0 b% {' @) r3 q
  26. }
复制代码

/ m6 B: N3 F7 i& t0 n; e  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。
9 i( y; j. Z$ w+ D( s; s
, i! b' O  p' O  2.2 Windows的幕后绘图操作
: |0 m$ Z: k5 k5 b, \  在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。: s& p; T# n% c; J+ {
  有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。
. X6 z. U1 f& |/ q( }  所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……
" P7 H0 Q+ U' f, }/ d. P% b; M& z) P  有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。# q2 ]  z5 |* v# B% {
9 ?' v; ?/ |3 P1 f: p* Y! l( O
  3. 美化界面之实现篇
* s) l" w, S7 C5 C2 \  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。4 G2 I# `2 F  j' `0 w4 z
# z( q! Y, ^% L3 b3 T/ `- D
  3.1 美化界面的途径( ^, t& u$ H' H3 e
  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:8 y( r! }  F. |. q9 ]
  1. 使用MFC类的既有函数,设定界面属性;
6 m0 u+ \$ i$ V) t0 i# k  g  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;' l% U* O. m3 a, _2 p- E
  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;
7 C7 _, V5 G3 R" m* Q$ g) p  一般来说,应用程序可以通过以下两种途径来实现以上的方法:
7 e& L  e) V& m: v, i7 D8 Q  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;# |7 `7 `) z6 A' \4 c: V3 R, |
  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。5 m/ H" L5 O# K1 Z/ [: [
  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:8 L/ [; N1 m+ v5 w
  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示:
; K; V5 d. q! g2 C( ]8 I$ i1 V* }
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;2 B' ^! J4 ]- X8 f
  以下的章节将综合地使用以上的方法,请读者朋友留心观察。
' ]+ L( n/ N! ~7 M- R3 z
$ p& B0 p- T  N; @7 U  3.2 使用MFC类的既有函数- H) z7 V& E* @/ H
  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。. q; L7 ~3 F9 b2 j* V
7 m! F0 J3 v6 k  ^
CWinApp::SetDialogBkColor
$ Y- K& j5 \  b( O3 I( S" qvoid SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );% V% k9 r2 ~. G9 Y6 V& t
指定对话框的背景色和文本颜色。0 y8 Y4 o  p2 [* i; r
  s$ ?. [" x6 m" _9 J0 |2 H
CListCtrl::SetBkColor
+ {; ]6 U3 Q$ v8 U9 N- uCReBarCtrl::SetBkColor- ?1 T' J" \" ^2 }
CStatusBarCtrl::SetBkColor2 I4 U& I, e9 i/ t$ e* J% G+ z
CTreeCtrl::SetBkColor
9 f4 k5 @/ d; {7 Q. m9 qCOLORREF SetBkColor( COLORREF clr );0 v- ~. ^$ ^* g1 m5 ?' ?
设定背景色。
$ h+ c3 W  ~6 A
; h3 R1 G$ m* d- bCListCtrl::SetTextColor
; I% K. y0 L# m8 ICReBarCtrl::SetTextColor
4 }1 o3 N8 ~. h2 V* dCTreeCtrl::SetTextColor
% \. V) X4 A: }' cCOLORREF SetTextColor( COLORREF clr );
- c; i: _! ]$ u3 v设定文本颜色。
( {* S0 v& b3 A
0 x% l2 {, y! S& c  K( pCListCtrl::SetBkImage- v# E) `. X4 X/ G' r" y. F' Q
BOOL SetBkImage( LVBKIMAGE* plvbkImage );
. W, P3 I% |" oBOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);7 G0 z- a  {4 A- ?. K) t5 X& C
BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );
$ b( ]; ?4 A, Y# c% R$ T* K5 M设定列表控件的背景图片。
3 A6 ~! T- K8 m: n6 g9 j' X9 q: E) ]1 a/ ?: J
CComboBoxEx::SetExtendedStyle
' \  N, v9 Z4 M* o8 ?0 DCListCtrl::SetExtendedStyle
  j/ N( ]" m% ?, S0 UCTabCtrl::SetExtendedStyle
8 f, T0 i9 C! m" z7 FCToolBarCtrl::SetExtendedStyle 7 ^, n3 Y# k0 j5 G8 Q
DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles );
. f& f) R( b/ k% l' V* G设置控件的扩展属性,例如:设置列表控件属性带有表格线。
) `" u1 {/ s# U- O" Z2 P  图4是个简单应用MFC类的既有函数来改善Windows界面的例子:
. j0 L4 d7 a1 b6 N9 P9 R/ J4 X
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:; c: _" n7 k, g$ L
  1. 8 X5 U6 u! W$ }7 Z8 V2 u- H
  2. BOOL CUi2App::InitInstance()9 [9 T. h; F. p% P8 B
  3. {" e! }% C! d" U4 ?* s, U
  4.         //…
    ; _1 d) E& x6 n4 f: c
  5.         //设置对话框背景色和字体颜色9 a' y% m- W' k* I% S* L* M1 R
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));. }/ N. j7 G( U; K2 d" C
  7.          //…3 y6 C% N' N/ j: O# X/ G
  8. }9 _9 ~$ A6 b; H" k/ Z
  9. BOOL CUi2Dlg::OnInitDialog()7 g" \  J9 s' c6 K& N+ V
  10. {1 O) d4 z* ?4 O, x- l' `0 d" a6 v
  11.         //…' l. w& N( j6 K( D
  12.         //设置列表控件属性带有表格线  b, T+ c0 _( ]1 n8 D- k2 w" {
  13.         DWORD NewStyle = m_List.GetExtendedStyle();
    7 E( V4 R1 o3 `2 z6 q
  14.     NewStyle |= LVS_EX_GRIDLINES;1 z: ~" A% v+ W; y( W# T8 B  c
  15. m_List.SetExtendedStyle(NewStyle);
    4 s% r# @0 O3 A7 {- a
  16.         //设置列表控件字体颜色为红色$ {6 q. j' Z" a
  17.         m_List.SetTextColor(RGB(255, 0, 0));
    7 ], B6 U* U1 o6 t
  18.         //填充数据
    8 |  Q, d. ^5 V, n# y. s: c6 _9 K
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);
    : i8 K; d0 r: i# i  t% b0 k9 p
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);( X, p( K9 l9 i
  21.         m_List.InsertItem(0, "5854165");
    . q9 _  i, O. a, O- U
  22.         m_List.SetItemText(0, 1, "白乔");% k5 g% {  `% T) g+ B6 X9 V
  23.         m_List.InsertItem(1, "6823864");" A' o8 R$ r* P5 @1 P
  24.         m_List.SetItemText(1, 1, "Satan");6 t8 {% E2 O5 {$ C
  25.         //…7 I# U! ~% ]7 H; {5 T* L
  26. }
复制代码
. x1 b2 C, a6 O2 N
  嗯,这样的界面还算不错吧?
, @" W0 r+ R' `8 c/ `' u: r% X" I
  3.3 使用Windows的消息机制
. B9 \$ V$ p) {, V  \9 T  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息:
4 Z( j! f- |8 w1 fWM_PAINT 4 c! {0 L" Q( N! l$ G, s" [" [
WM_ERASEBKGND 9 p6 D: h0 ^. \2 A9 E, S! a
WM_CTLCOLOR* " t# N$ Z" X* }
WM_DRAWITEM* 2 I7 W) r: D' T+ R
WM_MEASUREITEM*
$ s) n" j$ S6 n4 NNM_CUSTOMDRAW*
+ n' W* Q1 j6 \8 p) v( N  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。
3 H( k  P$ q  K3 T% `# o% \2 \8 p& [/ [$ l
  3.3.1 WM_PAINT
( @( m/ [6 Z1 ~; Z) [: F# _% o( B+ }& {  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。0 X, e; H3 V" q7 E, M
  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下: 6 M% u9 G) w; V) b9 ~- M
  afx_msg void OnPaint(); : I0 e9 E' Q' G
  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: 5 D4 W4 C! a# X* t) d
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单:
- W, F8 K: S9 c* `- v/ D

  1. 4 A/ F$ l' s2 G5 p9 ~' {
  2. void CLazyStatic::OnPaint() ; C: n% D& d; C- J$ Z. {; B
  3. {" R5 Q1 U! G* ?, K( b/ j+ k& ?
  4.         CPaintDC dc(this); // device context for painting) f' o; M0 O. K8 _5 a6 u& Q/ K
  5.          7 J% q; E% a6 k' `* h
  6.        //什么都不输出,仅仅画一个矩形框5 A8 I4 R! v! c5 m& J
  7.         CRect rc;/ s' Z9 s: x' G
  8.         GetClientRect(&rc);; h2 e$ S; X' \2 F5 Y2 Q. x, l7 W; p0 X
  9.         dc.Rectangle(rc); % X8 w% \. {- x
  10. }
复制代码

3 @$ Q9 X6 a  D) N* u( f6 e- `  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。+ F3 @6 b' X6 |" g$ j; c( ~9 Z( n
0 R) T: M$ h" o2 ]2 O6 \
  3.3.2 WM_ERASEBKGND
, a/ |, d  }% \# c. w9 \) h4 f  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。 0 y7 _, n& g' ^' x
  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:
' M! ]0 o/ R. N. i$ D3 Q* ]  afx_msg BOOL OnEraseBkgnd( CDC* pDC );
; [! J: z2 o3 ]) c3 `9 s  返回值: 7 G# j$ l. h( o( K0 o) |5 d: y
  指定背景是否已清除,如果为FALSE,系统将自动清除   n% H2 F" A: }' {) c9 Q) y+ H
  参数: pDC指定了绘制操作所使用的设备环境。 * M1 H. r5 W* A, N4 r1 w
  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景:
0 {. `& }- S  M& Y5 G
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单:
% _$ B$ x) l" Y: N# c. T; U

  1. 3 ^+ z1 v' Z- |, `
  2. BOOL CUi4Dlg::OnInitDialog()
    1 W$ A! R% {0 P4 ]; H0 c" i
  3. {4 d6 q  U- Y0 `. a* D" u( k! u- E
  4. //…
    $ @  R( t# a3 x6 q. t
  5.         //加载位图
    ; T' F- B2 ?5 H: e# l, s/ l
  6.         //CBitmap m_Back;# x: ^) k$ Z5 S
  7.         m_Back.LoadBitmap(IDB_BACK);* ~0 d- g  f9 x0 M7 x8 ^0 e
  8.         //…% O. r/ X. B9 l) \% v
  9. }
    9 c& \/ P' x4 D. m$ H! _; f
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC)
    8 V: }4 b) ~/ `! p5 x1 \1 x
  11. {
    " }; z4 F& V  U6 X4 b4 ?
  12.         CDC dc;
    ( i& n( [3 V! N4 l' o
  13.         dc.CreateCompatibleDC(pDC);
    : \/ ]- H' {% K  u/ }2 v
  14.         dc.SelectObject(&m_Back);7 x" E! n7 n) P: }
  15.         //获取BITMAP对象
    & i9 T# H- v( U  d) g6 _% C
  16.         BITMAP hb;) ]5 Q. R0 a/ o4 B! X" d& r
  17.         m_Back.GetBitmap(&hb);
    7 x/ C' f2 r- y- S% k) _5 k# T
  18.         //获取窗口大小- \8 U% N2 `( Y* ~2 j0 M
  19.         CRect rt;
    7 o* P. h: g) `
  20.         GetClientRect(&rt);0 J0 i9 q1 ]) u) V' p
  21.         //显示位图
    # @3 a$ i4 g1 o& Q) c1 Q! p
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),/ D" S3 z3 V& k$ v( F
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);8 c- U) w- N1 R% ]
  24.         return TRUE;
    / C' r" ~0 n6 C1 g# c
  25. }
    3 N% @0 [8 W- [9 r8 y
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    1 F6 [0 ~; m! y0 K) q7 S# F8 k
  27. {
    6 m- @) Q& S9 P! \6 Z% x
  28.         //设置透明背景模式
    ! f, r( t% n7 ]- q$ t" W8 h) b
  29.         pDC->SetBkMode(TRANSPARENT);
    ( t3 q( a& p3 K( K0 l0 T
  30.         //设置背景刷子为空* r" j# ]+ z2 ~
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);* B9 p( p$ H8 B6 ^2 A' w* ^2 G
  32. }
复制代码
2 z* o0 y# G. e% S5 A# E' M
  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。 , S) @, e+ R8 r  |% ~% e7 @4 E

5 W+ V  u; [: o# F( y$ A1 b9 \  3.3.3 WM_CTLCOLOR
, s) R& f3 W% B) s  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。 3 o% l" j3 }# W2 `) |2 v
  WM_CTLCOLOR的映射函数原型如下:
! w/ ]% C7 T5 i6 s. c8 o  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );4 i! a, R: i0 Q! I% I; W
  返回值: 用以指定背景的刷子
, S7 `, S9 l0 j$ Q" I4 I  参数: 1 P+ x& Q' \: S, ?3 K& B* ~
  pDC指定了绘制操作所使用的设备环境。
' p; Q/ E6 P6 B7 E. i  X  pWnd 控件指针 ( y4 r/ r" d: r! \: o
  nCtlColor 指定控件类型,其取值如表2所示:
6 J/ h: z- e, ?" ]  类型值 含义
2 z- M& W2 ^1 C! _CTLCOLOR_BTN 按钮控件
: J* @  R: |0 p/ v  oCTLCOLOR_DLG 对话框
! ]" b( O: K0 [, b0 d% LCTLCOLOR_EDIT  编辑控件
( u; [9 c9 W$ H" f6 }% c) sCTLCOLOR_LISTBOX  列表框
2 }& x" o9 _. u% [; C4 vCTLCOLOR_MSGBOX  消息框 / H9 c$ c  e& X4 j( n( m2 I' T
CTLCOLOR_SCROLLBAR 滚动条 : |- g! s! T- u6 i' |9 b" Y2 x
CTLCOLOR_STATIC 静态控件 $ i$ T2 M( Z' Z  i# G
表2 nCtlColor的类型值与含义
; @2 M  ~+ L4 E1 U6 n* f& O
" R0 n5 b' y5 |7 D* f  作为一个简单的例子,观察以下的代码:
2 E8 a! g& Z1 I# W0 W9 q; ]1 q8 F
  1. * J4 u6 r! u3 n1 V
  2. BOOL CUi5Dlg::OnInitDialog()3 g# ]# I. j6 v& v# m& @
  3. {
    # u7 M! C! H8 ]* S+ B2 Z# M1 c" i
  4.         //…
    % @6 h+ B7 X2 R3 b1 C
  5.         //创建字体0 j6 u# V6 W  B2 q) X# W1 Q0 K
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font2& B8 @! Y" V, B! _, T' Y: _9 J& z$ N
  7.         m_Font1.CreatePointFont(120, "Impact");
    + |+ F1 d1 G( A0 B4 U  L
  8.         m_Font3.CreatePointFont(120, "Arial");
    ) g/ f3 `/ G2 \* W/ J9 v, k
  9.                 return TRUE;
    ' [1 R0 I2 B8 N: a; Y  K5 r; D, ~& F
  10.   // return TRUE  unless you set the focus to a control
    , {& [7 V7 `) R/ D7 Z. x
  11. }: S1 ?! i# r0 @# P# i) ?; ]
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    9 l' {$ [. G0 F, d: c
  13. {. {2 o1 ?) L" A  v8 {, C( `5 e& n
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
    5 D+ E& ~& ]$ j: Y+ Z0 S/ _
  15.         if(nCtlColor == CTLCOLOR_STATIC)
    & |8 l! T$ i# H7 t2 X4 F
  16.         {& p1 l- A; ~" N. C
  17.                 //区分静态控件
    9 Z/ ]* m+ g! G' g2 S! n6 \
  18.                 switch(pWnd->GetDlgCtrlID())9 k% i; }. p, I$ E
  19.                 {* I) R- r- n1 n5 d
  20.                         case IDC_STATIC1:2 S$ J+ D8 B/ Y
  21.                         {
    + R: o9 r$ s1 m, k- {3 K/ ?
  22.                                 pDC->SelectObject(&m_Font1);$ B1 T' [1 M. e( E4 j! d6 h, X* s
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));  T) x) e/ A8 N
  24.                                 break;" a7 B7 a2 C6 u, o  g/ f
  25.                         }
    " n. b, d: V6 ]9 w; h
  26.                         case IDC_STATIC2:3 J# {7 e0 ]! F1 J! \( _. g. F) [3 P. R
  27.                         {5 c: H9 D  E+ Q$ T7 [
  28.                                 pDC->SelectObject(&m_Font2);
    * c& o: m5 ^  X. V8 v# x
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));
    + E0 E4 }- J( x
  30.                                 break;* j) ^  Y& \. Z9 e; r
  31.                         }, \/ o7 `6 x% s! V/ Z
  32.                 }$ B! y% F2 H% }8 e) g* E9 ?: w  w
  33.         }
    ; Q0 C2 F  E: E0 D3 F; w% }5 G
  34.         return hbr;
    " t; n; V) q# ?: q. m
  35. }
复制代码
2 l$ N5 ?( Q# [2 g" N' `. B/ }, L
   生成的界面如下:
; b% z. Z6 `0 _- n. o# W
图7 利用WM_CTLCOLOR消息美化界面
1 O3 q+ O! i9 v* q" A" s
  3.3.4 WM_DRAWITEM
" x0 m7 k) x3 R3 ~9 ~  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。
( C1 y' }; U  m7 d' k4 ~% g5 J2 C1 F  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。 - J) H$ T3 K7 u2 ?, v
  WM_DRAWITEM的映射函数原型如下:
- b3 A" H) g, v2 j0 v% |! ~  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );
9 I, p0 J) r! a) |/ H  参数:
5 `8 }7 S$ A3 q3 ?7 [) W0 @2 V  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0
5 k& O: x* t' f4 G7 A  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下: 9 W# ^, i0 E2 B  g+ V

  1. ; G' ~  D% u/ w) T
  2. typedef struct tagDRAWITEMSTRUCT
    : l# \: W! B# \, t3 n& D- W7 A
  3. {0 ]0 Y3 q/ K8 e, M: q/ q/ l- w
  4.     UINT   CtlType;
      z7 C' I: D: l& z
  5.      UINT   CtlID;
    ! O+ I/ \, Y! e! p0 j0 x
  6.      UINT   itemID;
    " d3 m0 M8 H# h& ~  x
  7.     UINT   itemAction;, H! P5 w6 w- }$ F- @7 k* z* l
  8.     UINT   itemState;
    & B$ c: K6 W  s+ s* }# t
  9.     HWND   hwndItem;1 H- E2 b8 `  o4 h1 S, e- l
  10.     HDC    hDC;
    : ]% I* t! }) O; v" }' D; G, _9 w* b
  11.     RECT   rcItem;. x5 r, _4 V1 K% `3 ?% o2 Y" v
  12.     DWORD  itemData;$ D9 s; X4 J3 [  C8 F9 d  c( k
  13. }DRAWITEMSTRUCT;
复制代码

( a/ P7 Y" t, PCtlType指定了控件的类型,其取值如表3所示: , m7 Q2 e4 S( Z1 B" \) n3 w3 W- J
类型值 含义 5 r- D7 Y, l: F4 k& g5 f
ODT_BUTTON 按钮控件 * u7 Z( u. R5 p* g8 X, }) b
ODT_COMBOBOX 组合框控件
  ?' }& R: y, I+ [1 v+ jODT_LISTBOX 列表框控件
% m, W- u- R8 n0 oODT_LISTVIEW 列表视图 & O8 y+ C" v- n
ODT_MENU 菜单项 * c$ ~2 B6 H, u$ N4 I, u3 _; X- n) U
ODT_STATIC 静态文本控件 4 E% S+ r/ m- N4 J( X( C
ODT_TAB Tab控件 $ O# j+ \0 _6 s! ]
表3 CtlType的类型值与含义% d+ [4 f( F$ G5 R/ O7 a5 X1 z2 }
! A" F* z- r8 m- e  s! M7 p
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
5 s- I3 R' i2 w9 o; u' y& W( J  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。
- Z& j0 U, W* |# x  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:5 v/ m1 {8 M, y) ^( z
类型值 含义 6 N! `. L1 z4 Q5 _) [
ODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。 / Y  A' A! F' Q' f- k/ i
ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。 / t& @$ o, A' }3 [. A3 V* ]- a* z
ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。
, S9 j+ m3 M# `3 `3 H4 @( ?9 b表4 itemAction的类型值与含义9 C/ y6 i9 z# [
  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:
- ^1 _" N/ ^0 _$ ^3 J类型值 含义
6 h2 N! l% E% K, fODS_CHECKED 标记状态,仅适用于菜单项。 2 w4 D5 y6 N2 P9 i$ S; Q. e
ODS_DEFAULT 默认状态。
2 ~+ y( O5 S/ [* S5 @- |2 WODS_DISABLED 禁止状态。 ) s/ _8 I5 K" Z6 X4 V' m4 l& d: O
ODS_FOCUS 焦点状态。
! R: W4 U) F* P$ s+ T- h: SODS_GRAYED 灰化状态,仅适用于菜单项。
0 h: @% G' A. P" y0 N$ P& J4 G) h- |ODS_SELECTED 选中状态。   \" J! I* B/ x% f4 i) G: S
ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。
# p- S" Q- Y: k) ]ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 7 y) Q& ]7 L4 G
ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。
6 x- Q4 ?5 W0 ^+ ^/ AODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。 3 j4 H, o$ @+ v; n
ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。 # ?& A5 k5 s' e. |) i; [: `
表5 itemState的类型值与含义% ^: l& ]% _  i" c
  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。 7 K( N7 _$ c* ?
  hDC 指定了绘制操作所使用的设备环境。 3 Q4 j4 @9 b" [9 r, f
  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 ' C' R, z3 I1 b: R( _
  itemData $ E" A6 O% X0 a0 I  |% `  K$ y
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 7 E3 o; g+ S' a  W7 ~6 ?$ K- r) @: m
  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
& F) G( n/ \( c( A2 d  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。
+ |" t+ ]) H, ~  图5是个相应的例子,它修改了按钮的界面: 6 o* V2 e% V, d5 a8 s& }% M
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下: 1 B4 x: m5 Z& B0 I4 E" a$ h* d
  1. 4 u9 v+ o+ U0 Z/ U
  2. BOOL CUi6Dlg::OnInitDialog()2 P; F1 m1 y+ [& I
  3. {' A' k$ F9 i% W, ^6 y* C$ F4 l" j9 P
  4.         //…
    2 s+ u. H+ N+ d2 p0 z
  5.         //创建字体- l2 w+ o+ C2 O4 k( Y
  6.         //CFont CUi1View::m_Font! @5 |( F' c: m( T
  7.         m_Font.CreatePointFont(120, "Impact");% X0 Z. s& w( i2 ]5 }* }7 q
  8.         //…
    + D( S- d. {2 z8 D' u
  9. }
    , f3 ]9 g- B- D  K# S2 @
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) 7 Y8 t3 O- G' G: x
  11. {
    5 C3 H* t5 `: k
  12.         if(nIDCtl == IDC_HELLO_CFAN)
    2 w5 m! S0 R6 }) G4 Z4 p' D
  13.         {% w7 P6 x' N' m) D' H4 I
  14.                 //绘制按钮框架7 z2 G) M; t5 m* n& F2 m; e, P
  15.                 UINT uStyle = DFCS_BUTTONPUSH;
    " |8 q7 w3 Q, m$ Q% P8 D2 P
  16.                 //是否按下去了?4 l# O/ d7 l8 `/ S
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)( M9 h  Y- J9 ?* P+ f" v1 G
  18.                         uStyle |= DFCS_PUSHED;
    ; `5 `# |1 j7 ^' |
  19.                 CDC dc;
    8 }+ \: @5 E4 P8 S: [* R
  20.                 dc.Attach(lpDrawItemStruct->hDC);# X+ Y. ^; G/ @
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);
    0 _4 ~! P4 F/ V& {8 u9 r
  22.                 //输出文字0 e2 L6 i6 y- o5 l; a- S; l1 G
  23.                 dc.SelectObject(&m_Font);6 Z2 r) K" f$ b3 ]- D
  24.                 dc.SetTextColor(RGB(0, 0, 255));/ Z! f' O: n# L; ^' O2 Q+ L
  25.                 dc.SetBkMode(TRANSPARENT);
    & k" S1 o; t9 n
  26.                 CString sText;
    3 I( b: K( D, A5 ]# l) @
  27.                 m_HelloCFan.GetWindowText(sText);  `  a$ c9 P7 ~
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);
    * a, E& D2 B$ m. a0 B
  29.                 //是否得到焦点
    : l1 d3 ?: B) D% p% C
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)
    % ~, n" r0 ^/ J3 ^' n
  31.                 {$ B$ y! m. K$ Z8 e
  32.                         //画虚框  \& H% c9 ~7 E! d8 L
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem; ( ]$ F8 }# }% J+ g$ y+ |7 m
  34.                        rtFocus.DeflateRect(3, 3);
    * I4 U0 c, X* K' J" E$ ]9 s0 ^
  35.                         dc.DrawFocusRect(&rtFocus);
    ! x* G$ j% P- T/ Z
  36.                 }
    * u& c& \. _2 B! B3 y  f, i. d$ Q
  37.                 return;6 V2 `- y+ ]' ?% g( ]  o
  38.         }
    6 j+ i8 j3 Q, Z* ?; |6 O
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
    ( E/ s4 ]) m5 Q3 ?  R
  40. }
复制代码

. u: l5 ~+ q5 l% B" j$ T, o8 Y" D! G  别忘了标记Owner draw属性:
  h2 Q% \5 h+ c& M
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。
0 N, b2 t6 [& S" x9 h5 ?! t- |! I% E  M6 B% D, {: V1 |& t
  3.3.5 WM_MEASUREITEM ! P; w! p8 S5 f' q8 c
  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
) w, V$ K. m5 Z6 T  WM_DRAWITEM的映射函数原型如下: 5 z  q4 u7 M! q  [2 c
  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct ); : h$ t, i8 r% I) W0 l9 ~& w3 \0 X
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l% C: a' b) c* l. \  x; s
  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下: ( `- F2 L9 `9 L" E$ r
  1. 2 k! _  x$ t# x; L3 q
  2. typedef struct tagMEASUREITEMSTRUCT8 d2 x0 V* V/ H# C  [# z. P% e
  3. {
    9 K% z  y; J- e  {, [
  4.     UINT   CtlType;1 p/ Q# J$ a, H9 [
  5.     UINT   CtlID;
    " U  Y* V. k. \+ E. O
  6.     UINT   itemID;
    $ O& n; U+ z1 f5 w% h
  7.     UINT   itemWidth;
    ; m9 l( ^: l4 Y, I: t. d
  8.     UINT   itemHeight;2 U* j: M. s' t8 k3 H( P& e
  9.     DWORD  itemData;8 l7 y. o; ~2 F2 [! I  T8 D
  10. } MEASUREITEMSTRUCT;
复制代码

2 b" g9 k) O0 g+ [  CtlType指定了控件的类型,其取值如表6所示:7 \9 Y/ d1 y' {4 k. d
类型值 含义8 d$ u5 S/ ^- v8 n3 W  h8 c3 Y
ODT_COMBOBOX 组合框控件 & L, V" F1 Y7 o: |
ODT_LISTBOX 列表框控件
/ F3 q& D' K( R/ |" mODT_MENU 菜单项
( r% [) {  b' k表6 CtlType的类型值与含义4 h! x" @" T; }4 q: M- U$ l
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项 8 F( w* e8 x" a6 S
  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。 8 ~4 G7 C8 F( N& `' c5 \5 \3 D+ M
  itemWidth 指定菜单项的宽度
5 f% d: b( L: `9 L: x3 Q* H# @  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255 2 t2 m0 {8 ~4 f$ R" ^
  itemData
/ z( g  `7 \" c( O! I% |, ^  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
# A5 Z5 I) [8 E) e$ f; K  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 / F1 c  i; X% B* n
  图示出了OnMeasureItem的效果:
1 k. i/ ~% ]5 k+ u; Q4 Z
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下: # b+ L; ^0 t2 N0 a2 F8 D1 ?* [
  1. 6 m! q, w8 L, Z/ ]; P. r( _7 A
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) 8 M& \* G- v* G* G/ l7 R
  3. {' M6 u9 V3 H) e1 N
  4.         if(nIDCtl == IDC_COLOR_PICKER)
    , X( F4 p6 p* L
  5.         {
    ( q: e: X) g9 ]+ M) Q
  6.                 //设定高度为30
    6 w. z/ S  T; ?9 _1 E2 l
  7.                 lpMeasureItemStruct->itemHeight = 30;0 Y8 R- d6 z. K7 V
  8.                 return;
    6 @1 ~8 B' h' Q( b
  9.         }4 ?- e( s0 Q' k1 g
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);( h% G- o. C( a
  11. }
复制代码

( B3 e+ h5 |6 {; L  同样别忘了指定列表框的Owner draw属性: - l' q# A* K9 [; J1 [3 O1 {8 f" Q
  图11 指定下拉框的Owner draw属性  ' K% ?3 P& X. k- u7 ^9 x8 u

+ p7 G& G& O, j0 l, [  3.3.6 NM_CUSTOMDRAW
$ \  h4 {/ X$ t  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。
9 S( `  E" @5 N0 y$ ?( {  可以反射NM_CUSTOMDRAW消息,如:
0 s: ]* A* ?% D# Y2 C; {  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
, R% M$ n" Y; U  _, l* m  w8 P8 R  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult);
; g2 w7 h  y: o* W5 A7 `) p2 a% v  参数: 2 V2 B" O7 d$ b; b2 I
  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:
, M* J- N8 S& @

  1. % N; E$ `( f0 ]9 L% O+ T
  2. typedef struct tagNMHDR
    : k1 x/ t5 q: X; @* D' P
  3. {5 i! {' [/ @  k, y) O
  4.      HWND hwndFrom;# L) }) w3 P( U1 l1 t
  5.      UINT idFrom;
    0 h( s' `- @5 f: ]% Y
  6.      UINT code;
    " t3 {2 Y- j5 H$ {/ k; ^
  7. } NMHDR;
复制代码

% ^3 c0 b6 M. S( T: R( P. e, \3 g  其中: 3 U/ C/ F  t- W5 o$ \
  hwndFrom 发送方控件的窗口句柄 9 H; \' T2 i5 ]% M9 X
  idFrom 发送方控件的ID code 通知代码 : Y( E& p1 Z: S! @
  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下: - c! b4 ^2 e2 F9 e  T- R0 g

  1. 1 t0 \" s: d' F: ^+ G4 P, Q
  2. typedef struct tagNMCUSTOMDRAWINFO) {: {+ ]) ?9 j. _$ Z0 w
  3. {# Y& A, p$ s$ b- @  S  |0 e( o2 {
  4.     NMHDR  hdr;
    ' W6 {2 s8 h% h/ f9 j+ H" |4 J
  5.     DWORD  dwDrawStage;
    5 Y, N( R8 V0 z) q, T8 ?
  6.     HDC    hdc;
    ! F4 d3 O4 W; m2 J0 F
  7.     RECT   rc;  `1 N' }$ j* ~' U
  8.     DWORD  dwItemSpec;0 t, c% i' O, c0 o
  9.     UINT   uItemState;  w' v' Q1 b: ~7 I  y( A
  10.     LPARAM lItemlParam;5 E$ k+ l2 I# b8 P- i3 `" r
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码

& i7 C( y( m5 X& y" c% z' e# E+ U   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:
0 P* L$ v' l/ G; i类型值 含义
/ z% f8 D  G. F. F  t  QCDDS_POSTERASE 擦除循环结束 & H4 r1 j6 ]- C
CDDS_POSTPAINT 绘制循环结束 9 \3 ?5 L; C3 P3 k! k  r
CDDS_PREERASE 准备开始擦除循环
* ^; ]* `( D7 BCDDS_PREPAINT 准备开始绘制循环 9 F. g, ]# k1 Z2 F
CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 $ E3 Z0 d* {1 F3 d* {9 y
CDDS_ITEMPOSTERASE 列表项擦除结束
8 H& r- T1 o% ^4 |+ zCDDS_ITEMPOSTPAINT 列表项绘制结束
5 u% g( l2 x' Z2 ACDDS_ITEMPREERASE 准备开始列表项擦除
+ i. ^! t, B) K4 v# J- s" ]( }7 uCDDS_ITEMPREPAINT 准备开始列表项绘制 $ m6 Z7 x' F9 u9 a% Q3 [% r
CDDS_SUBITEM 指定列表子项
1 \0 Q( n) i" `  b, J: r表7 dwDrawStage的类型值与含义& c0 \) ^) C6 i. @4 G  ~9 F. ~/ s
  hdc指定了绘制操作所使用的设备环境。
5 w9 P5 N7 w  @# V  rc指定了将被绘制的矩形区域。
4 ]) N& B3 `2 E8 F9 z' i1 H  dwItemSpec 列表项的索引 8 _. f2 ~3 W/ Y
  uItemState 当前列表项的状态,其取值如表8所示:: F- |4 U6 v" f/ l+ @/ V. v: [0 p1 {9 h
类型值 含义
; M7 Y2 c  R8 Z* h- ZCDIS_CHECKED 标记状态。
# ^( ]% t8 ], s- cCDIS_DEFAULT 默认状态。 - {3 A3 O5 S. u0 o
CDIS_DISABLED 禁止状态。
1 X- t8 B2 V# i: `9 tCDIS_FOCUS 焦点状态。
; u( A8 T. M1 ^" FCDIS_GRAYED 灰化状态。 # T5 f7 \$ Y, Q  h- H0 R
CDIS_SELECTED 选中状态。
3 z2 X4 m) s+ I& w. K; Q" Q, RCDIS_HOTLIGHT 热点状态。 " B8 [/ w: ]7 |6 Z1 I5 E; V, Y
CDIS_INDETERMINATE 不定状态。 ! @) T% q" E2 n
CDIS_MARKED 标注状态。' a- y/ i1 t, s1 Q
表8 uItemState的类型值与含义
) ~2 s, c0 M0 X2 ^& N# Z3 B7 A2 O  lItemlParam 当前列表项的绑定数据
0 s, C9 w- l, b- N; D  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: % y: B# _& g9 f1 h  J! w5 g- E" ?
  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:
5 |# D8 Z. N1 N1 I/ n类型值 含义 - B. L+ h" N$ Y2 X+ p
CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。
; b( h$ `' s$ c# D+ A$ x7 cCDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。
) b. O2 ]+ \& K/ HCDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。
2 W4 H6 E8 S. n5 j6 T$ wCDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。1 [9 U* {. B2 A, L" y6 z6 B) F1 \
表9 pResult的类型值与含义(一) / _( s: ~. Q" g2 j
  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:
. L; @- P8 X+ Q类型值 含义
# t0 d6 K1 ~% pCDRF_NEWFONT 指定后续操作采用应用中指定的新字体。
- ~6 t+ R# ]" A. _- xCDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。   ]8 R2 \( Z7 g$ @! P
CDRF_SKIPDEFAULT 系统不必再绘制该子项。
# I) l: O# c3 N' e& {1 R表10 pResult的类型值与含义(二). z+ o7 P" Z& B! U  m
  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子:
6 L! {. C: S3 L* |
  图12 利用NM_CUSTOMDRAW消息美化界面 5 m% T  j3 Z/ {
  对应代码如下:
- Z& w! O$ [) b6 b

  1. 4 a2 R3 K1 ]7 d+ S/ H
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
    7 D  `1 A/ C  V1 |  b4 P
  3. {
    ) H4 o# ?5 L' v% B( E
  4.         //类型安全转换* u$ ?" [) r) D. B8 Z( V- N2 `" y+ {7 f
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);
    1 d; q/ o3 J7 ^9 t% z
  6.         *pResult = 0;. C+ [2 A6 H4 _$ }6 V3 c
  7.         
    & I" {9 e, h0 h1 ^& P8 {
  8.         //指定列表项绘制前后发送消息
    ' h. Q1 @: b. K. P& U* w/ n
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)
    # i6 L; z! S1 g1 y
  10.         {2 d# r8 a0 `) X8 G( v+ u4 t, F# C7 E
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;
    ( d( v' a6 T; K7 H; s
  12.         }
    . d) q) J5 [) A7 ]- m! [+ R0 p
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)6 a# B$ c" U" ?' k2 b
  14.         {
    4 q$ F# f4 U/ D( O$ j- d
  15.                 //奇数行
    $ H9 X, a7 F$ ?. n
  16.                 if(pLVCD->nmcd.dwItemSpec % 2)/ A- M5 [0 z6 f  w$ s
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);1 Y, X# Q. T* ?5 o- h, f8 M
  18.                 //偶数行+ v) B9 r( _$ l8 i
  19.                 else$ `4 U$ f- h: B5 M8 w1 B' b
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);
    . v% V9 `' }. `! f5 I
  21.                 //继续0 ~0 S6 [+ a$ |$ u
  22.                 *pResult = CDRF_DODEFAULT;
    / x2 c" }- i. j# ]- P3 K. f' S
  23.         }" d8 t# X( A& h
  24. }
复制代码

5 F9 N7 W% Q, R7 F  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。
' g- U6 E( ?  g' u7 }% Q, l1 {6 ]; O( ^9 k! ?" m- d2 h- `
  3.4 使用MFC类的虚函数机制 6 `6 w- B. p; @  w
  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子:
" x7 K( c6 G1 m! B
  1. # a* h# o, n" }3 h0 E
  2. void CView::OnPaint()) D/ D3 F5 v9 n& _. z9 B) m
  3. {
    3 v: H, n2 H/ W# B" q) d1 ]7 o
  4.         // standard paint routine% L2 \- \8 K, }9 [4 j1 [. U
  5.         CPaintDC dc(this);
    # [( k6 i7 u0 L: ^- X+ J& u. m. @
  6.         OnPrepareDC(&dc);- ~/ Y/ L" h) Y( w( _" y
  7.         OnDraw(&dc);
    + k" h2 [( W) Q: T1 L7 J' F
  8. }
复制代码
* m+ l  M9 k# ?
  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。 ) I3 Z; L: L" D. `0 y4 \3 r9 V- J- j, c
  以下列出了与界面美化相关的虚函数,参数说明略去: : [: V. O, B5 Q% C0 t% t
CButton::DrawItem
& o7 f" h3 h( \4 XCCheckListBox::DrawItem   P1 }7 T2 c( o9 [" Q6 c
CComboBox::DrawItem - y7 d( C/ G1 U
CHeaderCtrl::DrawItem ! b8 z  y! Q, r  q
CListBox::DrawItem
% x/ t! ?. \) k1 tCMenu::DrawItem
0 [2 V1 ?: K8 G  a7 l- e3 OCStatusBar::DrawItem
4 N! {2 _8 y' D  [CStatusBarCtrl::DrawItem ; W0 }3 p1 J8 z
CTabCtrl::DrawItem( a/ C: G& I; k5 G* ]7 m
virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );
' _( ^" Z. m1 {9 b6 FOwner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。0 y9 c# z5 `% V8 q! X
  限于篇幅,在此不再附以例程。
" x8 t! u' }# B
+ J; g" @+ L6 X参考文献
1 i/ g/ \! Z6 {& E0 C7 v1 A本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

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

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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