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

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇
0 X/ y2 F. P, p2 H' c$ {: m1 L  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。
* d9 I7 C7 M* o' N6 w- K  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。* P  Z' ~' w3 W  v

( ]( b4 i6 t2 {  2. 美化界面之基础篇
6 H4 T7 L$ Q: P% W* V- K$ t: J  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……
* A6 u" c- Z* @5 L, M: E/ ]* S
" d( g2 x' X* O# l( K0 U  2.1 Windows下的绘图操作
4 Z& ]. x' Q' T5 j7 A5 |  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……7 A7 A& W* w6 O3 _: B- `: J( Y
  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。
4 _  w# f7 E0 _6 G" E0 R2 s0 f' R' u. ^* k* j7 C" u
  2.1.1 设备环境类" b2 c7 y& Z  J5 F  L$ o! h
  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。
- r6 k" D! Z/ j3 K  s& I6 ]# k  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:
7 t$ U# v/ ?: r: r8 K  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式
1 s  s3 ^9 O+ m6 r0 M  Mapping Functions:映射操作9 m* q$ Q( w, u: N* U
  Coordinate Functions:坐标操作8 @1 w1 W  |" X' V6 F6 h
  Clipping Functions:剪切操作+ z0 \5 ?* [4 R$ Y
  Line-Output Functions:画线操作+ h* ?+ _# [/ U
  Simple Drawing Functions:简单绘图操作,如:绘制矩形框
1 G) Q6 _. h; s  Ellipse and Polygon Functions:椭圆/多边形操作! M, T$ O3 w9 J0 B2 G+ Z* [
  Text Functions:文字输出操作1 b  `# A0 \8 s, K3 K: N# S
  Printer Escape Functions:打印操作8 g  }3 m3 j. p, g
  Scrolling Functions:滚动操作
: X0 R- y9 N3 _6 q# Y  c8 |  *Bitmap Functions:位图操作6 d1 ]% `. ~& K# Y+ a) @' j) R
  *Region Functions:区域操作$ i) {; H0 @7 q& ]* o! i5 g- M
  *Font Functions:字体操作
5 [: j) e: |7 P  b4 |/ K  *Color and Color Palette Functions:颜色/调色板操作
5 M0 s2 m# r; y+ R, g/ L  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。3 o- }! I8 s+ o& m! v
5 S8 K- \2 |: y6 P8 p# P/ ~3 G: c: `5 ^) R
  2.1.2 图形对象类; v' ^- o% t5 ^: e" u, v- e* S
  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。& V# Z& L3 |; @# h/ \  \
  下面的表格列出了MFC的图形对象类:
8 \% C% O+ |* U# l* J4 E  MFC类 图形对象句柄 图形对象目的) X: A) g" \, J1 N2 w
  CBitmap HBITMAP 内存中的位图0 R3 M# t. B$ k" S+ [
  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式
# u3 G/ q8 Q% j  CFont HFONT 字体特性—写文本时所使用的字体
7 u$ d, s/ t$ |+ N" i  CPalette HPALETTE 调色板颜色8 I" f- E+ W! K5 T- J! c6 P
  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细
3 H6 p- L- q1 n$ p; Y! F& u/ r( N  CRgn HRGN 区域特性—包括定义它的点+ e( g" I6 S% O. S2 Z
  表1 图形对象类和它们封装的句柄
+ n& U9 W: B' J' J/ s
1 L" A3 e% ], E% L# o  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:
% Z% b4 Y* H. Q2 i6 y) [7 L
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:, ?8 s# H5 h8 J' i6 M( e8 j
  1. * j7 H$ H- P$ J5 O2 B7 c2 |
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)* o! _( p* ]  T4 q9 O% g6 }
  3. {$ _  o3 ~' o9 v% E: S' ?
  4.         //设置背景色
      u0 }) i; V3 c0 `( {
  5.         //CBrush CUi1View::m_Back
      K" x: w3 \; G0 |6 N
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));
    / A: Z6 k' d/ a8 s/ R1 a
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL); $ j2 p( B& T, R0 p$ |
  8.        return CView::PreCreateWindow(cs);5 r9 E9 c- l  I; D; v3 W7 y# l' V7 r
  9. }& M+ M0 D0 I% G$ A2 Q9 J$ I) v# D8 B7 C
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)/ K+ B! s% z1 w  `7 ]+ V
  11. {3 T+ y+ a$ T0 w  r9 }; }+ K' S
  12.         if (CView::OnCreate(lpCreateStruct) == -1); q5 f9 o! G: l. y1 H/ Z
  13.                 return -1;
    ( ]8 A5 Y+ U; p
  14.         //创建字体
    1 T, f7 _; W! I
  15.         //CFont CUi1View::m_Font8 ]% X7 N+ u6 [" ^# M
  16.         m_Font.CreatePointFont(120, "Impact"); # O5 C5 r# L9 V2 y8 q% C4 N
  17.                return 0;& U2 B, q6 @7 A2 P
  18. }: p* d2 o! ]+ b  ^, F9 ?2 ~1 B
  19. void CUi1View::OnDraw(CDC* pDC)3 }/ t3 V; E7 n$ v4 C
  20. {- I/ h: h$ o' V$ ?, K, L
  21.         //绘制按钮框架4 i( d) D8 P8 M8 m/ u
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);
    & i+ L, h5 f1 Y" ~! U
  23.         //输出文字& O. n! ^9 [3 \
  24.         pDC->SetBkMode(TRANSPARENT);/ Q" E# U: r2 K- N3 s5 j
  25.         pDC->TextOut(120, 120, "Hello, CFan!");
    0 x7 f* N; E1 V: c0 h
  26. }
复制代码

: w: n4 ?8 ]6 M6 F# ^# M  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。 & l0 {2 r! B" E+ o
( o: |& y, D# k' L
  2.2 Windows的幕后绘图操作 3 h" Z5 I7 o7 ~2 T
  在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。5 G; q9 G2 I7 A2 P9 ?/ b, A" @; z* o
  有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。
- o3 p/ ~5 Q+ G* v8 `( a  所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……/ K) N) N3 ~$ T# \+ ^3 S! z# v6 _. ?. o
  有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。6 M5 p  E7 E  M9 ^
" V0 f! Y+ i: C: A1 `0 @
  3. 美化界面之实现篇9 R, ^4 `& E0 J5 J1 Z( O
  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。
, |/ t7 x* Q; A2 v& ~0 E9 X1 Y+ K
2 J+ p0 d/ D5 k3 m! z# a6 a( R' {0 K  3.1 美化界面的途径$ G- t8 d8 t: y9 n4 n
  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:0 ?6 g& Z5 v' ~8 c" d3 y9 I
  1. 使用MFC类的既有函数,设定界面属性;
6 j# b& }" h3 P4 W  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;" ~  L4 Y$ F+ d4 f8 H& P/ ?6 c
  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;
# X, n6 B: W; b5 Y. ?  一般来说,应用程序可以通过以下两种途径来实现以上的方法:! I* V% H8 l0 f" j9 _
  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;
! K5 }" g' W2 a* `+ ^" c  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。
7 B9 q* c: V2 {7 I2 b% C( k  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:
+ E! S7 I9 C' K) U. s5 V/ i8 ^4 M  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示: % \. C: H) _2 ^0 o- g' _
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;
( G1 }' k3 {4 z1 q) v$ l  以下的章节将综合地使用以上的方法,请读者朋友留心观察。. W. F3 V8 z& @. E' {

2 Z8 K' E9 l( T  a  3.2 使用MFC类的既有函数
1 c6 E7 I0 U" A2 Y! I6 H' B  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。" l6 d% g) s/ W! [" U" L% H, {
9 K# U9 j0 i. p2 ?: m
CWinApp::SetDialogBkColor) E1 ]* T, l5 a9 M; Q" c
void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );7 V4 Y  l9 Y$ m. p
指定对话框的背景色和文本颜色。- [" B- o3 p2 E/ B; j7 s; d+ \

/ X/ m5 F* D. {% G4 l( MCListCtrl::SetBkColor$ \& \2 m, X4 i9 ?: O* ~4 W* Y
CReBarCtrl::SetBkColor
. \' \. Y( S" H" z! jCStatusBarCtrl::SetBkColor
- b8 P: ?) S  z/ H+ kCTreeCtrl::SetBkColor
0 _8 U& p' g: p) C! cCOLORREF SetBkColor( COLORREF clr );
+ J( g7 t. A3 }9 V设定背景色。6 e3 d4 Z' K$ p; N' ?# u3 p  u/ D
7 d1 _) s5 |% B
CListCtrl::SetTextColor  O; n: m7 d7 J9 V5 C# N
CReBarCtrl::SetTextColor4 W" U; r" R4 v) b. u( q) ]
CTreeCtrl::SetTextColor3 _2 y3 R6 D5 d( x" k% `
COLORREF SetTextColor( COLORREF clr );( `3 `0 B/ v" w3 m
设定文本颜色。) @" ~' `3 x( A2 ]% w+ O7 n

* C& ?4 U  ~; U& ?' t* FCListCtrl::SetBkImage
" _& f3 k1 |6 u6 DBOOL SetBkImage( LVBKIMAGE* plvbkImage );3 G1 C/ q! w6 ]9 R7 ]
BOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);# e- \  V* {6 R- `# r7 {
BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );
8 I5 }, p% |$ O  |2 {0 Z6 f设定列表控件的背景图片。
- O5 T' O; q; G  }% J1 F) [( B- `4 ]7 E% Z
CComboBoxEx::SetExtendedStyle+ B/ C9 N. I, l) R3 A
CListCtrl::SetExtendedStyle
4 l/ E, {+ D. o( Q) d1 l) oCTabCtrl::SetExtendedStyle
4 C# Q1 g. h. U) `( f& A; }" HCToolBarCtrl::SetExtendedStyle " ]$ L) Q! V* \2 Z3 O
DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles ); 4 V8 Y! l. ~" T, o7 b
设置控件的扩展属性,例如:设置列表控件属性带有表格线。
- [$ @/ D8 v. i; w- d) g/ b2 j  图4是个简单应用MFC类的既有函数来改善Windows界面的例子: ' W, N; q+ [) q
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:
# K! U; r4 [$ Q; d- S6 L2 A" w, }: h

  1. : k+ F. A9 i* P: X/ H7 Y( B) a- S
  2. BOOL CUi2App::InitInstance()
    $ o9 b, s2 U3 c" @% q, Y) U
  3. {$ ^: H% K0 X4 @
  4.         //…
    / A3 k9 E  y5 c: C
  5.         //设置对话框背景色和字体颜色: L  ~, X0 z7 Q8 B# Q2 Y
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
    ; G' q: l) d1 d% w2 G6 Y1 f
  7.          //…/ D& K# t& F; t( ], I* i
  8. }
    , W! O% B$ q/ w) `
  9. BOOL CUi2Dlg::OnInitDialog()
    ) p3 @( f+ x0 S: G! s. E+ `! m
  10. {/ m" c1 u! q5 M
  11.         //…
    7 u1 x0 N7 v+ v. a
  12.         //设置列表控件属性带有表格线
      X# K0 I) K# q' x& e
  13.         DWORD NewStyle = m_List.GetExtendedStyle();
    - C2 R% ]( p8 _
  14.     NewStyle |= LVS_EX_GRIDLINES;
    - ^8 L& d# ?; B
  15. m_List.SetExtendedStyle(NewStyle);  T5 I# P- c, C/ M" h* I. o
  16.         //设置列表控件字体颜色为红色
    ' n, F4 `5 D. f, T6 }5 h) g
  17.         m_List.SetTextColor(RGB(255, 0, 0));9 W# T( C. w* x0 C6 f6 a
  18.         //填充数据6 ~9 A, Z1 j  ]" E: W
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);
    , v7 k1 _& ~, H! \0 l
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);. J) d, i. B) [* y
  21.         m_List.InsertItem(0, "5854165");0 l" ?9 ?/ z, q1 Z
  22.         m_List.SetItemText(0, 1, "白乔");
    ' T+ s: _' w& G/ }! B% }
  23.         m_List.InsertItem(1, "6823864");
    6 Q) x8 q* k' K  T1 q
  24.         m_List.SetItemText(1, 1, "Satan");. O$ c! I9 I. O4 \& P% a( l
  25.         //…
    9 _' s5 i6 Z" D6 V" R
  26. }
复制代码

$ N1 \; ?4 N5 s/ a  q( ?1 k. q  嗯,这样的界面还算不错吧? # P, U/ y$ K- D
. C! J( M% e: g6 n. D; F+ a! |
  3.3 使用Windows的消息机制* F# Y2 B- _5 @( g, G0 \( c
  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息: ) W9 k( m* C( T9 ]3 Z. K
WM_PAINT
4 T) G4 `7 j( u, H1 z, B# OWM_ERASEBKGND
8 Y; y3 J' J) [/ X6 uWM_CTLCOLOR* % U2 `6 c; V; y* c( V% A, Q
WM_DRAWITEM*
/ }, E+ K3 g3 Y  j: xWM_MEASUREITEM*
9 A. ^  }# z5 a, `4 x9 W8 h2 v( r' j9 iNM_CUSTOMDRAW* 0 r8 [5 [1 M, k$ z  W+ }. i- |
  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。
/ E- v/ l& s6 x& a8 E. U+ c2 o$ r: M2 _- z; K* }2 O! L5 m3 w0 h
  3.3.1 WM_PAINT
9 U2 G5 Y0 c- _  ]7 P4 L& h5 e  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。, a7 s; j6 y* R8 n
  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下:
1 {" P9 F% r! r( b: i. R  afx_msg void OnPaint();
" r2 d' w# d6 [6 Q9 B3 V  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: 6 M$ ]5 q. T, n5 [$ ]2 c) v" A
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单: ) h/ d+ r. a2 V/ r+ S9 K, V
  1. ' }' w5 p6 y* ?- D! m, t
  2. void CLazyStatic::OnPaint() * c2 s# `0 d+ B
  3. {4 ]# `7 Y0 V7 r) H' x- U: U* t
  4.         CPaintDC dc(this); // device context for painting
    6 w, g# o9 Y( v; \$ e/ c) d: n6 E
  5.          
      f& S0 l9 w3 |$ @. B- x! o. D# E+ m
  6.        //什么都不输出,仅仅画一个矩形框
    * w* q5 H$ C8 k; s/ r0 l* s
  7.         CRect rc;
    $ n/ n# s2 E$ X7 O3 c- o
  8.         GetClientRect(&rc);
    8 X7 L1 I; W& v$ q, u/ \
  9.         dc.Rectangle(rc);
    & }7 L; l6 A4 k7 l" L0 `
  10. }
复制代码
1 ]# N+ R1 f) Y' b# o" s' B9 V2 y
  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。
+ I! m5 m! L  ^7 c, {2 q* x, |& S; K7 c5 I1 h6 D( p
  3.3.2 WM_ERASEBKGND
; P' \$ O/ f) ~9 s  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。   O7 f+ Y0 c  q* o% r( {4 q
  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:
/ {2 y' K8 m* P% c  afx_msg BOOL OnEraseBkgnd( CDC* pDC ); , E: o  _- Q( u6 b, X; a
  返回值:
) W/ h5 O9 z) @% p' Z( T" u6 q  指定背景是否已清除,如果为FALSE,系统将自动清除
/ o0 r6 x2 R& \2 |- u" {# n6 W  参数: pDC指定了绘制操作所使用的设备环境。
% n# J: J4 p) u; A  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景: - U) z: P2 X" o0 E
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单:
- G1 l; a- E( N

  1. % r% P  k" O+ K, {# R# {& y+ s
  2. BOOL CUi4Dlg::OnInitDialog()
    ) Z4 i, y4 E1 U2 F( B1 g0 c0 ^0 G2 ?
  3. {
    ; c$ G; f: R2 m- g
  4. //…  M) j) h  m% f3 p8 `9 f
  5.         //加载位图
    ! k# K( E; U  W! Y1 \
  6.         //CBitmap m_Back;
    . l' g  w# |: k' ^5 r, U" _
  7.         m_Back.LoadBitmap(IDB_BACK);
    % t; i4 O# H) l/ G3 {5 q* b/ A
  8.         //…
    0 x: z: t" z2 x+ E# }$ b: D  o& H, h
  9. }
    . a# Z6 F+ N1 X8 {9 u
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC)
    4 i  {# N8 W# Z4 h. N+ N
  11. {/ z9 w4 ?, d# m4 P0 A$ y& U
  12.         CDC dc;
    ; m! i% O+ z5 v; J  H0 @7 |6 \$ A$ X
  13.         dc.CreateCompatibleDC(pDC);$ `. {# L4 e2 B  D* e# A0 n2 E
  14.         dc.SelectObject(&m_Back);/ \0 J- D4 `9 M+ Y
  15.         //获取BITMAP对象$ f* }  p. a  A: H) V2 G7 ?4 v
  16.         BITMAP hb;
    $ y) a% {  a5 K( i2 H* h8 N$ I
  17.         m_Back.GetBitmap(&hb);
    , l- f7 l9 U7 n4 w/ T6 J
  18.         //获取窗口大小" `# ^- S' X/ }; [' t) o, ^" F
  19.         CRect rt;
    4 c' f. t, l8 v0 s7 O# j0 \& i' S
  20.         GetClientRect(&rt);1 i5 P$ _, w7 a# X7 o
  21.         //显示位图0 M" X, {- {+ B8 b6 Z1 A6 Z
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),
    8 r3 j$ r+ [, D6 y( u
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);
    ' i9 j' [( I8 f' E
  24.         return TRUE;# Y) M- D( _& S4 a7 \
  25. }/ M) L' F% C+ S3 f# \9 N5 V
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 7 i% f! P4 P7 P8 C# o
  27. {
    . B. S) H3 Q- m& J8 f* z& ]
  28.         //设置透明背景模式
    8 ~! S7 p  ^7 g* ?' I
  29.         pDC->SetBkMode(TRANSPARENT);
    , `4 G/ c' C- C
  30.         //设置背景刷子为空
    6 E' l  Z& T1 ?. K2 y: Y& J8 |
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);
    7 ^- B" W6 d9 \
  32. }
复制代码
- d) S$ J+ j; s  m9 t/ n
  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。
+ c! ^0 b+ R' t! N% ~$ W' v
# E" C0 l/ f" H6 Q8 Z1 T  3.3.3 WM_CTLCOLOR   A/ Z$ g$ X) c# Z3 S' B1 Q
  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。
9 ?+ J" `3 S# |1 e) v0 w3 t: t( K  WM_CTLCOLOR的映射函数原型如下:   k0 O! I+ q1 F7 b4 ?/ o+ d  d
  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );
9 m3 u0 r7 {  U& H) B8 j  返回值: 用以指定背景的刷子
& I; C7 o5 L, N1 b+ k# i3 l7 T3 G. Y  参数: 9 ~5 [$ r4 Z. S( P+ E
  pDC指定了绘制操作所使用的设备环境。
, S( \2 N2 H: H$ y8 s- C# I  pWnd 控件指针 + b3 [  f! f* q& m
  nCtlColor 指定控件类型,其取值如表2所示:
. k" |& D, G) j9 U2 v& P# \  Q3 G7 l  类型值 含义
% f& q, Y/ L$ n  x  J/ w- x: NCTLCOLOR_BTN 按钮控件
) R. Z! {! @- R0 MCTLCOLOR_DLG 对话框
. _5 E3 n& X  H2 `/ y! K8 d" k; ZCTLCOLOR_EDIT  编辑控件 . d: o7 V0 Q$ |  @9 F8 ~
CTLCOLOR_LISTBOX  列表框 # B: t6 A4 j  }' q' h( n7 ^
CTLCOLOR_MSGBOX  消息框 4 O+ u( j, O5 s3 V/ J4 I
CTLCOLOR_SCROLLBAR 滚动条
, b6 l$ |2 u& d4 yCTLCOLOR_STATIC 静态控件
3 ]6 j' a* W! A5 l: ^; s表2 nCtlColor的类型值与含义. @4 Q( A1 L/ }) B8 U
# @/ Y6 z  `$ T
  作为一个简单的例子,观察以下的代码:
3 I2 y% b3 r4 v' r% Z1 y9 q
  1. $ X/ z( Y1 ^2 @  U" l) J" o3 @& F
  2. BOOL CUi5Dlg::OnInitDialog()4 J1 B5 Y9 z9 O. i0 k
  3. {
    7 n6 c) U* Q5 m6 S! N( M5 W
  4.         //…: L- K: E2 u- i6 S0 J7 z6 W
  5.         //创建字体. H! s/ A$ @  c( v
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font2
    1 `! H- K" ~7 K: ]( n
  7.         m_Font1.CreatePointFont(120, "Impact");7 |7 d6 C' r$ w) X8 d( y  S  T* ~
  8.         m_Font3.CreatePointFont(120, "Arial");4 B3 S; x# Z% g: H; D. y
  9.                 return TRUE;
    ) ?' D% d  H: _% C  e+ x7 q
  10.   // return TRUE  unless you set the focus to a control 1 u: g2 d+ v9 E; L2 A
  11. }
    ! Z9 O: |4 c. N/ E% m6 c8 Q
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) & D/ Y0 b: n( C: |; L6 w
  13. {& H$ @5 Y, s8 v3 u6 X: [
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);. W. l* w( A2 m0 F- _+ C  Q
  15.         if(nCtlColor == CTLCOLOR_STATIC)
    6 G" W  L8 @! ^  r
  16.         {
      G  |5 ^% Y8 c; L  F9 D/ g
  17.                 //区分静态控件
    ' P/ V3 A8 @0 x% X
  18.                 switch(pWnd->GetDlgCtrlID())0 n; H% u2 @* a2 L
  19.                 {* z3 r+ G! e8 e. A
  20.                         case IDC_STATIC1:
    ; e& E4 I6 v1 y
  21.                         {
    9 H4 v( s/ o7 r
  22.                                 pDC->SelectObject(&m_Font1);
    4 \+ r4 O5 B0 d0 A1 ?9 Y, H
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));
    ; R- h9 J8 H1 q6 G1 i5 }
  24.                                 break;
    0 o! L9 C+ J! t
  25.                         }4 L( Q0 X! [# S4 I3 B8 H- p! X$ g
  26.                         case IDC_STATIC2:
    ) {; K' c4 ]! @1 m
  27.                         {
    : @5 s: x2 Y) i! A4 b
  28.                                 pDC->SelectObject(&m_Font2);
    & U1 R- ~. f' `0 M7 M( G
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));) n9 `$ \0 m) @: o2 S
  30.                                 break;0 r. _  b7 e9 R
  31.                         }! l  Z: f9 R  L
  32.                 }" k" @( i' D6 d0 Q& F+ z) H
  33.         }
    ) W( U/ k! ~  H
  34.         return hbr;9 L5 e) j5 W; m' s
  35. }
复制代码

) o2 @5 o3 k* D7 s; j9 X   生成的界面如下:
: |2 n6 o  v( `3 Q& P( [/ Y
图7 利用WM_CTLCOLOR消息美化界面

, @( F7 ~/ {/ v- R  3.3.4 WM_DRAWITEM
: K% }& e' L6 F+ v1 _+ ^& e' T0 M  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。
8 J6 i- \2 q: _: i$ b6 T; D  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。 ! z& O, B& B. q2 g) Z, j1 f
  WM_DRAWITEM的映射函数原型如下:
0 j5 V% P: u* g# b+ ^& ]: ^2 @3 K  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );% Z6 V9 Y6 C3 U) S; O" z
  参数:
2 [" @: v( \9 m: R  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0
* d) T# m7 R% O9 P  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下: ! C! X9 M, d3 U

  1. 5 e* x& l# s6 z: Z- P$ t- C+ s
  2. typedef struct tagDRAWITEMSTRUCT1 S' \, ~" M. b& b
  3. {
    # Q; q7 e1 j& W
  4.     UINT   CtlType;
    5 m  E7 m3 B! u4 H6 Z6 j
  5.      UINT   CtlID;
    $ j& \+ h& ~7 F: X/ G+ @
  6.      UINT   itemID;
    4 T! k- ^5 k) g
  7.     UINT   itemAction;5 J: `) o6 [/ b. `" \
  8.     UINT   itemState;* W+ a* {+ f8 t  z  T# E5 Z
  9.     HWND   hwndItem;+ o$ E  W  V. y& @( J) C+ G
  10.     HDC    hDC;
    $ y$ F9 t" Y/ L9 ^8 [7 X
  11.     RECT   rcItem;4 Q# |4 {9 L6 |! ]; x  d0 P/ r, y
  12.     DWORD  itemData;
    & y: l$ i9 z/ \7 ?" q
  13. }DRAWITEMSTRUCT;
复制代码

$ [: r$ h$ X7 QCtlType指定了控件的类型,其取值如表3所示:
% w/ ?, a. G/ x( `0 j类型值 含义
/ u/ M) v$ r; A9 J7 z. T0 aODT_BUTTON 按钮控件
6 O* x7 p8 ^  W( q6 UODT_COMBOBOX 组合框控件
  U1 Y! L) K& n6 L$ Y/ S" {( u& bODT_LISTBOX 列表框控件 ' D2 l# a- p. \, E$ g2 q
ODT_LISTVIEW 列表视图 5 M, S3 q( ]5 o) m4 C+ M
ODT_MENU 菜单项 # x. d8 r" V: c* Z- B' t  x
ODT_STATIC 静态文本控件
! s3 ~2 i, D2 [( s8 A; uODT_TAB Tab控件
9 Z5 K) G  U# B+ d表3 CtlType的类型值与含义
! Y8 e8 y6 r* M5 g+ W; l! G9 v4 ?& |! X( X$ A+ z' L+ y7 I& j5 G& Y
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项 " [* a8 p9 ^6 _0 k$ o' ?7 y& U9 |
  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。 * J+ t/ F# G( G1 p$ X* [9 m% c
  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:, C2 x* V  Z3 r! ^% H
类型值 含义 & f" ~# U1 T+ _# `
ODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。 3 }1 f6 a. U8 Z6 b  {
ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。 ; _# l' |& B' N& s
ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 $ J& f9 x8 d' l) K& E
表4 itemAction的类型值与含义
( v. R) {, f5 d' P* w0 w$ n1 |1 ~  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:; r. u1 w- {: o/ {% J: j6 r4 i
类型值 含义
3 ^+ G, S& b3 ^2 `6 z& rODS_CHECKED 标记状态,仅适用于菜单项。 : Z5 i9 }9 o  b+ ~# L" X5 t6 y% f0 Z
ODS_DEFAULT 默认状态。
( N% h) Y( O# I$ {- mODS_DISABLED 禁止状态。
0 z' W9 m0 [& c, J$ m; oODS_FOCUS 焦点状态。
; T+ V, C* K7 \# e; B. t* ]# ?ODS_GRAYED 灰化状态,仅适用于菜单项。 / t: H& F9 S- z4 I
ODS_SELECTED 选中状态。 ; {( K0 _0 E( R5 {1 S
ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。 , Z7 J0 o/ Y7 z! i, _0 e& W( P; E
ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。
- S3 t6 }8 m+ A' j! `ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。
: R& Z; K( }) U+ \+ m2 eODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。 / I2 V! J0 a1 [, I, a: w' J# L
ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。 2 b+ V& O. M( j  N
表5 itemState的类型值与含义
/ {/ q7 Q: [( F# F  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。 6 ^' x) k0 |3 Y* D# s
  hDC 指定了绘制操作所使用的设备环境。 ) Q( b! y; D! ^
  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。
1 Z7 N: B; W! [! l( a  itemData ! t: W! j+ y7 G  ?
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 ) R/ ^. Q4 i, T- C+ B0 p" K
  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 : i9 \) F0 L' ~* E# J/ |- k& h4 B2 Z
  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。
5 r: C6 k+ u- i$ N6 m  图5是个相应的例子,它修改了按钮的界面:
7 H6 w) r' Z# x8 T5 q! j9 ], C
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下:
9 s; n) e+ j4 I" P' u% ~+ E" Y8 [
  1. / {" R+ \2 S- H/ ~; Y
  2. BOOL CUi6Dlg::OnInitDialog()
    : z6 k4 L# ~, o! u* O0 a5 Y
  3. {
    6 o& S0 o/ d8 z7 g6 T9 h! u* }
  4.         //…6 x) J! T, y) B* A: y! L7 G' o
  5.         //创建字体5 _; O4 x$ P% T' ]
  6.         //CFont CUi1View::m_Font9 u% l2 G5 ~, N% Y) l
  7.         m_Font.CreatePointFont(120, "Impact");
    - x1 O% v9 y6 Z5 c9 Z$ R6 v; C
  8.         //…7 `3 V) Z5 |2 R* K
  9. }, j" l' R* P8 `% l; a6 {6 M
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
    0 M7 a6 U/ U2 x3 g
  11. {
    2 e. p4 E! \% r% [0 U! I# X
  12.         if(nIDCtl == IDC_HELLO_CFAN)6 S4 P! J( U  h( j6 g
  13.         {
    ! w$ t* @, f6 O" g9 F1 u+ ]
  14.                 //绘制按钮框架( B9 c! z& \- u; [$ T3 A# f
  15.                 UINT uStyle = DFCS_BUTTONPUSH;& ]- n  \. i1 @2 S0 q% Q
  16.                 //是否按下去了?* }0 O  c, }: F2 X  C
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)" b, F/ F& u0 {% h" _8 e" P
  18.                         uStyle |= DFCS_PUSHED;
    ( e4 t/ ~$ F) @
  19.                 CDC dc;" Z$ t  r* y: K& h0 m& g) Y) F
  20.                 dc.Attach(lpDrawItemStruct->hDC);% V9 ]1 U. p" A# o% J! c
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);* e8 W/ S6 p0 J' K' G1 }6 A) Y
  22.                 //输出文字
    ; d; r' e& Q3 U2 u% s
  23.                 dc.SelectObject(&m_Font);
    % l$ m5 c* w7 ?8 @0 V% r2 Y+ m5 B
  24.                 dc.SetTextColor(RGB(0, 0, 255));. ~/ [$ [6 n4 I- V' V* ?
  25.                 dc.SetBkMode(TRANSPARENT);
    5 B$ f8 f; _, g6 V7 Y7 S* z
  26.                 CString sText;( I" \& _* s/ a3 p& A4 h
  27.                 m_HelloCFan.GetWindowText(sText);
    - f/ f3 n' P( @$ f
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);/ \; G2 R, p  i/ p& k  ?4 o
  29.                 //是否得到焦点# l) F! O5 o# X% H$ m( w
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)- [% O# Z. a1 m* a* B9 N4 a
  31.                 {: L. C: s4 `- m" @
  32.                         //画虚框# b; E* d7 Q, e% |5 L% r2 c
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem;   z/ r! d7 T+ }8 C9 Y' T. [, T
  34.                        rtFocus.DeflateRect(3, 3);
    , X$ S  R! ^( [" I1 K# l! b7 i
  35.                         dc.DrawFocusRect(&rtFocus);
    $ a4 I7 I6 S/ a: w( N3 _! {
  36.                 }
    6 f+ C8 J9 v+ ]+ N* Y; d
  37.                 return;4 _( V. e9 o/ {2 A
  38.         }
    " k4 q! y% x9 |) N
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);( d% z& q# ~0 b9 }/ C4 z
  40. }
复制代码

9 X2 q$ ^. Q3 M1 a$ o' g  别忘了标记Owner draw属性:
0 F+ D& Z  [8 ]& |+ M
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。
" V0 x( @1 N  @7 ~; y: d/ d! D0 q  V
  3.3.5 WM_MEASUREITEM 9 E- @. [4 w* H) V
  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。 . V3 e( \: U) M7 a
  WM_DRAWITEM的映射函数原型如下: . s8 K! a* N, t7 h# m/ |
  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
7 s% Z9 s4 L( m+ H0 |- J  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l# K% x) c: J5 o# z  P; h' X& [
  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下:
" x: R9 u" o. \5 k

  1. * l  H. a8 ?3 N3 o7 U
  2. typedef struct tagMEASUREITEMSTRUCT
    % u$ ]; d9 a, i4 `0 ]0 s
  3. {0 p" y9 r: V. I- ~5 k! |
  4.     UINT   CtlType;) x: k" {, ^) \. c1 c9 y4 f
  5.     UINT   CtlID;1 i9 I0 e/ M& R- S
  6.     UINT   itemID;! {% T9 c' K4 @) t
  7.     UINT   itemWidth;& s+ {" ?5 [+ Y9 v- {* h7 I% b
  8.     UINT   itemHeight;
    # h% c7 d8 S+ u  z. T2 {, y! D
  9.     DWORD  itemData;# {3 n0 ?0 Q% a5 s
  10. } MEASUREITEMSTRUCT;
复制代码

8 A$ b! ]2 _7 ^3 Z  CtlType指定了控件的类型,其取值如表6所示:; p: p0 N. M% M, C# [# n: n: a
类型值 含义2 R$ I' W. z6 T+ n( p7 f
ODT_COMBOBOX 组合框控件
: l  M; u- f% B. s6 D5 H! }6 n* dODT_LISTBOX 列表框控件 & I3 z! {! y, ?( f4 K$ C
ODT_MENU 菜单项 - C+ Z; h8 L3 C+ U. I
表6 CtlType的类型值与含义
7 K) W+ I" ?$ S5 j6 F  CtlID 指定自绘控件的ID值,该成员不适用于菜单项 7 w5 \. e9 i- ~, l2 j
  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。
3 c0 m+ x* j/ l) K1 f+ Z* ~& h  itemWidth 指定菜单项的宽度 : |/ y7 Y+ l6 M" C+ E
  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255   m# [; T& S. X
  itemData
- L7 {. \5 }3 M  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
/ h; j) b! ^4 I. w! ^4 u+ P1 N* s  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 - j" a2 q1 z9 V" l% E6 o% _
  图示出了OnMeasureItem的效果:
* @0 y! f: z! ]2 S; x' M
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下:
7 o" j, O+ p0 N5 a% _1 H7 G
  1. 0 j% w, U  f" k& \
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) ! Y. k3 N) A5 E) Z% z$ Q
  3. {
    - C/ E5 }9 ^: \4 p
  4.         if(nIDCtl == IDC_COLOR_PICKER)
    * q! c0 N; p2 W7 |1 |! w6 I
  5.         {9 k  T  a) f% F3 t
  6.                 //设定高度为30' w5 I# X9 V+ D. Q4 X
  7.                 lpMeasureItemStruct->itemHeight = 30;$ z1 V# Y$ _$ e) p* K) u5 }. u% w$ V
  8.                 return;
    % l+ a3 D  l, {- u
  9.         }+ r7 L1 s' N0 e7 U* U7 k# h- o
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);, a8 E4 @7 U. C/ O
  11. }
复制代码
' s3 L( _- K3 Q% j5 o+ b) e) z
  同样别忘了指定列表框的Owner draw属性:
8 [0 {4 r. h: V9 g
  图11 指定下拉框的Owner draw属性  
9 Q+ f# c7 P, b' Y' R3 ]
! m7 C1 D- n7 o0 S  3.3.6 NM_CUSTOMDRAW
  \1 D- }. @7 \3 Z  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。
- r# |  p$ a9 B8 q. R/ {  `  可以反射NM_CUSTOMDRAW消息,如:
" s+ _# t) x( N6 Q# b: U  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
# j7 o; W) v" r! K  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult);
& T4 w/ |2 z# F: S' U/ H  参数:   g8 b3 }# A. W, f# S3 [$ z
  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下: + U' N" \& P% n8 ?6 H
  1. " G& T' g7 y6 n: A: \6 ?4 Q
  2. typedef struct tagNMHDR4 n6 S* G) }' E5 ]8 E( y2 I; `2 M
  3. {
    2 x3 q2 I) i, X, q/ l
  4.      HWND hwndFrom;
    2 r0 p# ^! J) @- q, \
  5.      UINT idFrom;
    5 d. A+ c* o9 F  T: K* z' z2 k
  6.      UINT code; $ t( X; K9 T. a& C: W
  7. } NMHDR;
复制代码
4 `2 q2 r+ G) t2 E+ }
  其中:
8 H0 N1 s, D/ l  I/ q6 h  hwndFrom 发送方控件的窗口句柄
- v" h+ ~( @# P! m% G: J$ S  idFrom 发送方控件的ID code 通知代码 * k  H. [9 i4 ^5 r
  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下:
+ Z3 |% `! Y# W2 B  ~  h6 T  Q

  1. ( T; K$ ^. S0 `; Y$ m# u% P
  2. typedef struct tagNMCUSTOMDRAWINFO
    / Q5 J8 M, P4 K# L. o/ I
  3. {
    - P3 F  s3 m, N  ^% f% j, o0 l
  4.     NMHDR  hdr;" F7 {; G& [6 \$ Z% N- \
  5.     DWORD  dwDrawStage;4 M6 i3 X/ Y6 @  b/ [2 v" Z) v
  6.     HDC    hdc;9 X& X9 C) d; j% U0 ?( Q# Z( m$ H
  7.     RECT   rc;
    ; E8 Q. h" }! d5 T
  8.     DWORD  dwItemSpec;
    0 L6 k7 }5 H6 Z$ ?
  9.     UINT   uItemState;
    ( W0 p8 a! S4 S: u2 K
  10.     LPARAM lItemlParam;+ V+ `" c4 e: j! v- q1 T9 f  m
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码
0 Z! r6 r3 Y$ _4 R  M* |
   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:
8 X5 K- r. j! w类型值 含义
, U0 n" j: r9 D$ n9 e; pCDDS_POSTERASE 擦除循环结束
$ z8 I8 @* X7 G+ m; J" _CDDS_POSTPAINT 绘制循环结束
2 U1 B7 Y* _8 f. h% [/ lCDDS_PREERASE 准备开始擦除循环
, O  ?/ ]$ J5 UCDDS_PREPAINT 准备开始绘制循环 1 F1 I9 ?2 ^' \6 v! H1 E& q
CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效
2 A. N" ]9 n, ^) YCDDS_ITEMPOSTERASE 列表项擦除结束
( M5 p; W4 o; K  r1 h; _) VCDDS_ITEMPOSTPAINT 列表项绘制结束 4 ]( u: U& y  U, ~; \. y
CDDS_ITEMPREERASE 准备开始列表项擦除 - Q) I2 |: U' B% W. ]1 X5 P. |
CDDS_ITEMPREPAINT 准备开始列表项绘制 / ~# }! z7 q+ g! p
CDDS_SUBITEM 指定列表子项. Z% p2 t6 k- ?' |
表7 dwDrawStage的类型值与含义
# ~- v  P% I5 V7 Q- a! q  hdc指定了绘制操作所使用的设备环境。 5 l; @* b& r& V0 X* g) L- o
  rc指定了将被绘制的矩形区域。 3 n% o  M: g, i3 m  R
  dwItemSpec 列表项的索引
+ G. }% Q3 g$ I4 @, S7 n& [. O  uItemState 当前列表项的状态,其取值如表8所示:4 y) p, G: f' u7 F  E
类型值 含义 6 P1 Q) Q2 ?; Z& T) @+ q6 g
CDIS_CHECKED 标记状态。
6 K  ~# W: L# Q# g5 XCDIS_DEFAULT 默认状态。
( p4 d0 s1 [8 X5 sCDIS_DISABLED 禁止状态。 8 ^1 T, F& T9 G: s6 N
CDIS_FOCUS 焦点状态。 * p% A) V0 p# r4 k
CDIS_GRAYED 灰化状态。
) u; D3 Q8 a7 I, jCDIS_SELECTED 选中状态。 # \9 x; m4 t1 q3 L" A' e
CDIS_HOTLIGHT 热点状态。
0 ~7 k- T% u+ K' g9 h; Z- @! NCDIS_INDETERMINATE 不定状态。 ( d& R9 q& B7 s
CDIS_MARKED 标注状态。* m# i* c' r0 W; \3 v
表8 uItemState的类型值与含义/ h7 m+ x3 ^. h  L  M
  lItemlParam 当前列表项的绑定数据
8 T- I) x+ u. x1 [# U/ g  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: : R* V! }( a/ b" U0 e* H! R
  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:$ {) {7 [+ u( d8 H6 j
类型值 含义 ; Z! k- ]' U0 o& U: K- @8 \
CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。 3 `6 u2 p7 X) E3 }: n
CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。 $ t3 p" c: t, i
CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。 - V$ c1 z- \5 ]0 e7 l5 X
CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。* @: _" ~9 C! ?8 G
表9 pResult的类型值与含义(一)
! A- u; g" D  O7 C4 `  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:/ q7 g4 r4 [" k' o1 f2 R
类型值 含义 ; ?! w) D! V1 y( S" A
CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。
- e: D% d$ K# p+ V* @0 r7 w. k: oCDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
% f0 l$ A  D& B$ e0 I% w  fCDRF_SKIPDEFAULT 系统不必再绘制该子项。
# U. Z; N% F& o# H% Y表10 pResult的类型值与含义(二)
' ?: E1 i" Q: H# H2 n% o, p  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子:
. h5 T; C6 i; i: y
  图12 利用NM_CUSTOMDRAW消息美化界面 7 v% F; H( D) m6 J1 X$ _6 P
  对应代码如下: 2 d9 ?# n, q' x9 E8 ]

  1. / R5 V8 c9 m. y. W% v6 R
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
    ' H0 C, J. t3 |6 ?8 n; F
  3. {
    6 D1 M5 q) I: w* _; ?; J
  4.         //类型安全转换8 m3 T1 _5 `8 \' |7 F* ]
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);0 r; C& M0 w% v
  6.         *pResult = 0;
    5 @$ G- U# v. r8 I  x! J
  7.         ! M7 ]0 G2 S/ g" E
  8.         //指定列表项绘制前后发送消息/ L( z4 }5 V0 y+ p/ z' I% R
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)8 y* Y/ U6 g1 A, M
  10.         {- G, T, M: X/ K' X
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;% Q6 K! ?: C1 q
  12.         }
    - h6 c+ I5 T8 {: P; M( S) e# r
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage); d! p2 U! E% W0 L4 O
  14.         {
      e( f* T' `" y: _% B; A
  15.                 //奇数行
      d, R. q: v' l7 _8 R& a
  16.                 if(pLVCD->nmcd.dwItemSpec % 2)' n! U: x) m/ r; C) [1 ^0 a
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);
    9 c2 j$ y9 m& o: H) {/ w/ m" p
  18.                 //偶数行& U- L* i6 p/ F1 k# ]/ X
  19.                 else
    / b4 j: f. V# s6 ]; b+ X$ U6 v
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);/ y5 V3 @( K" K' S' \# a, x; s6 x
  21.                 //继续1 p! b, o, @" }" {. d" S) ]
  22.                 *pResult = CDRF_DODEFAULT;
    ( P1 v/ ?& I. z+ V2 f  l( s
  23.         }1 h( X. ?5 @( a( ?
  24. }
复制代码
$ k2 r+ h8 }5 s* m
  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。
" N6 y+ p! x9 G
8 E0 V  I, d" {  3.4 使用MFC类的虚函数机制
$ q! `. \, G% x+ }: |  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子:
! R# P, _9 Y- q3 C3 ?: E

  1. 7 y6 \" z$ S' @8 E" n* l  M' t7 x
  2. void CView::OnPaint()
      A8 X* y; M9 D/ R" s
  3. {/ l  C% e  e8 G1 j, W
  4.         // standard paint routine2 Q1 \! n  z2 d5 k7 n+ l
  5.         CPaintDC dc(this);
    5 {+ |; m* ]) k
  6.         OnPrepareDC(&dc);9 `# j$ [; w0 `" ~' T3 S* ~4 r
  7.         OnDraw(&dc);4 h  z/ m" T2 T5 m
  8. }
复制代码

" ^7 u3 s) d7 {  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。
8 [  Q4 T+ D5 y  以下列出了与界面美化相关的虚函数,参数说明略去:
# Q" g$ J* q3 U7 Z, ZCButton::DrawItem 7 p8 x( w* ~, T( Z* L# J& \* }
CCheckListBox::DrawItem
0 k; b0 S) r# f0 OCComboBox::DrawItem
  [. I" N& w8 h2 z/ U& Q- o3 PCHeaderCtrl::DrawItem 6 G" e2 ?+ d- [( q$ R8 e3 U
CListBox::DrawItem ( b3 g3 a! R8 b: w8 Y$ Y
CMenu::DrawItem * ?" a: l! G0 O6 _: V, G3 j
CStatusBar::DrawItem 0 s7 d  E, w) Z$ r1 j$ H. [7 @
CStatusBarCtrl::DrawItem
  X6 t. }+ w* H5 z1 oCTabCtrl::DrawItem& p4 Q* S" M- \
virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );
$ r3 @! t) f7 I3 }0 I& F, j) E. r' ^- |Owner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。
' V* z. u6 a5 b7 M% q7 t$ T, O) [$ u  限于篇幅,在此不再附以例程。
" _* y7 K% ]! P- A6 b9 R% \0 {) t# L& J" K+ P4 Y
参考文献
$ q, H* I0 X% G) C* q本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-6-18 09:14 , Processed in 0.018814 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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