找回密码
 注册
查看: 6741|回复: 0

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇; L9 i* {$ s) e
  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。0 `7 s; p' }9 e8 x4 a+ Z
  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。
# u6 g4 Y4 U6 j/ `' m; \5 i' \9 [' s1 {: p- U+ V, |
  2. 美化界面之基础篇  A% n: c2 u6 Y. G$ s
  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……, J4 Y4 D3 t" k) s

" @+ C' {- j- X$ E# s: T/ ]% o$ V  2.1 Windows下的绘图操作
# h3 X0 C5 h0 K- L8 r  V0 ~* \  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……9 e5 e( C7 t3 t8 \
  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。
: E" X9 p% L" A* u/ @8 R' N9 r* ~1 g8 r* x: W
  2.1.1 设备环境类2 s4 F+ Y( ^* \/ v7 W- W4 H1 r6 {
  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。1 t: [) e8 u& Q  R& t6 A8 ]6 ~
  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:6 t2 ]* C$ B' @( x" Q
  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式
* _/ f$ `2 `* f4 o8 k# Q: p: X1 v  Mapping Functions:映射操作
/ s0 n  ^7 X) Y9 Q  j+ p  Coordinate Functions:坐标操作5 C& a; Q& J; D' e
  Clipping Functions:剪切操作
$ p& O/ B: z* J. D# I  Line-Output Functions:画线操作1 S8 z9 T6 n5 z9 v1 k' ]; [
  Simple Drawing Functions:简单绘图操作,如:绘制矩形框  ]" }1 k- \9 \6 ~/ C  Q$ d3 O
  Ellipse and Polygon Functions:椭圆/多边形操作
$ K$ j# s+ O$ V: ^3 ^) A  Text Functions:文字输出操作
( A; I3 n" X( h! U, Q$ A* t  Printer Escape Functions:打印操作$ A: @" M" K7 m- e
  Scrolling Functions:滚动操作
& z" i8 z, y+ o" P- ^7 v8 ]  *Bitmap Functions:位图操作
$ e  r7 _2 l- Q; `( c6 N. c  *Region Functions:区域操作
& L6 `4 X; z9 m3 f! R  *Font Functions:字体操作9 ~9 `: Y, p. }+ y6 k2 y' b5 q9 r- a% ~
  *Color and Color Palette Functions:颜色/调色板操作  N5 o& h' m- o" e2 F4 f6 J+ O& N
  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。
+ P, ]0 C- E4 w
/ I- t7 b4 o( A. v3 d5 ]  2.1.2 图形对象类# X3 E; h* M2 y+ D2 x3 h$ x
  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。
2 D8 n' I% k# ?8 ], x' [: V0 k  下面的表格列出了MFC的图形对象类:: F: x* H; N% ~5 [
  MFC类 图形对象句柄 图形对象目的
% {# Z$ G( \  y3 W  CBitmap HBITMAP 内存中的位图
1 k/ H' u/ ?% A4 W* j) p  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式+ e# W5 W4 X; Q6 b( A& `& Y
  CFont HFONT 字体特性—写文本时所使用的字体
+ i- Z% d2 r; [9 {+ u2 I2 b  CPalette HPALETTE 调色板颜色% c+ y- e+ X, G1 z8 O) \2 @
  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细
8 P3 @. m5 o  w+ i0 ?  CRgn HRGN 区域特性—包括定义它的点7 \6 s  u0 g7 ~$ Y, [
  表1 图形对象类和它们封装的句柄$ f3 K7 o( f5 |: P

9 R) s- f4 p# `( ?( A. c+ a  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面: $ l' E; ]3 v' g( X# r
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:$ d1 o: D& t. n8 X
  1. " c% |- Q7 ?/ @* b
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)1 Q3 B0 `: W: J
  3. {
    5 d/ I( j+ y" l5 u) }3 l
  4.         //设置背景色
    : R4 B5 P5 X/ z8 t+ S
  5.         //CBrush CUi1View::m_Back* X9 j# S* W  X- X- f
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));
    + x( {9 Z4 Y0 C  ~, y# V
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL);   p0 l7 E2 r9 X: |
  8.        return CView::PreCreateWindow(cs);
    + c5 M: x3 f7 G0 \: `! s( b
  9. }5 Z1 D! Y- B, X* v% M* A$ W  E
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)* K: W2 Z( ?- l  I
  11. {: x# }% C% K6 C1 {( G3 I+ J/ f! H
  12.         if (CView::OnCreate(lpCreateStruct) == -1)6 J7 D0 [+ X! j( O% J" {. l5 m
  13.                 return -1;% P# f% K& l3 F2 E
  14.         //创建字体! ]9 T: j9 T% T; v% r* A
  15.         //CFont CUi1View::m_Font
    8 f$ @/ c' L/ E% e' s
  16.         m_Font.CreatePointFont(120, "Impact"); / p  q+ ]3 }" \
  17.                return 0;; m2 A2 D6 t! Z2 [7 ~
  18. }" i0 j3 X3 ]8 o% @9 }' N' m. }: k
  19. void CUi1View::OnDraw(CDC* pDC). E# o; h. X( \  c
  20. {! X$ I$ q+ [$ W1 K4 h5 X
  21.         //绘制按钮框架
    / j* H* Z, _$ h
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);6 @+ f9 Q8 k4 @( }2 i
  23.         //输出文字
      z  [6 c) c6 F8 d' B
  24.         pDC->SetBkMode(TRANSPARENT);( S) a# S- l- Z
  25.         pDC->TextOut(120, 120, "Hello, CFan!");1 Q% k7 @6 X& O. K) i" x4 r
  26. }
复制代码

% A; N1 B. R/ G3 j% _  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。
" W+ [- z, M' O; o) r/ a- D' s
  2.2 Windows的幕后绘图操作
3 v8 D( w7 D. e, L  在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。  e% i* c( N2 Y3 y- X
  有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。
: b+ k" B* S7 `: _3 K2 j' ~$ Y& T# [  所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……$ X/ E! {- P, F2 c
  有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。. ^% o3 U/ T7 }# T; H. X5 \+ n* k
3 `2 _( Q* ?# F  |
  3. 美化界面之实现篇! G8 m. `: r% Z5 a
  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。" m/ S+ x# j1 c; ]3 C3 k% B; z/ Z

7 @7 v3 P& E+ p7 o( O  3.1 美化界面的途径, g( @/ t* [( Q& K6 L0 q
  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:
/ y" ?; q/ x7 r. ~2 ?1 p8 q0 `  1. 使用MFC类的既有函数,设定界面属性;6 }3 y  y* _  J4 Q9 T/ M
  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;
3 P" j, j) s0 K7 o: m  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;" \; V0 |: g+ Q3 T$ k& J: c
  一般来说,应用程序可以通过以下两种途径来实现以上的方法:3 a+ u. M' o' F
  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;
9 B3 t: P$ Q( v8 U: ?* l+ n, K0 r  b$ h  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。
5 A: U: t- i% }" w  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:
, p& X4 [# W7 I  \6 O9 U  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示:
% t' t2 N& j$ V
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;
  B5 K/ }: c4 [$ l  |- ?* g: Q( D  以下的章节将综合地使用以上的方法,请读者朋友留心观察。0 C  v5 t) \4 A

; t5 z* x0 ?% a) Z5 {  q  T' q, r: O4 Q  3.2 使用MFC类的既有函数
$ K# U' A8 K& _, A9 s( s  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。
: ]/ @' U  r: I6 P0 h. v0 _7 P7 g* t( z' f7 D
CWinApp::SetDialogBkColor  e' k. s& ~  G8 ^+ a
void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );5 N6 [! h! V7 c! O& t& y
指定对话框的背景色和文本颜色。
% E% r: M# m5 }2 [6 I5 y( e  p
& {3 c1 d: `7 WCListCtrl::SetBkColor! b) X; r( y& q  g& M5 M1 q3 g
CReBarCtrl::SetBkColor( z/ E* r# V6 J
CStatusBarCtrl::SetBkColor* h# M! V. g7 a' K3 O4 N2 R
CTreeCtrl::SetBkColor8 y% M  O  W3 M+ C) I3 @% N0 H
COLORREF SetBkColor( COLORREF clr );
3 t0 t6 J( x, s' V; [1 i& w" s设定背景色。
/ Z) x1 ^5 N0 ?7 R8 r9 L  U; \* d, y5 ^  Q1 w  t7 R. _9 n
CListCtrl::SetTextColor( ?' a+ W/ _2 @+ h  J7 z- U
CReBarCtrl::SetTextColor( L$ f; K% N' I/ W& a
CTreeCtrl::SetTextColor. C* G# c  n5 L! O1 O
COLORREF SetTextColor( COLORREF clr );
( l+ @# R/ R/ j( [! m设定文本颜色。
8 w, M6 o' w5 J) F4 a9 _+ V7 x* K5 ?: f
CListCtrl::SetBkImage( ?1 ~& u- x: ~1 L
BOOL SetBkImage( LVBKIMAGE* plvbkImage );
: m: Y8 x) l* l# oBOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);5 J, E  G- w) k$ y. `8 A" z
BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );
/ U# N# G8 z* T7 Q: z. ^+ G设定列表控件的背景图片。. \5 f) H; i4 C; d

1 Y% o) Z; ^" e! w0 f( d+ RCComboBoxEx::SetExtendedStyle
$ f& {' y8 S; n( v7 iCListCtrl::SetExtendedStyle
* s( S5 u) v3 O0 f, z6 sCTabCtrl::SetExtendedStyle
/ J  Z% a+ e- S1 n, b( J( ]2 \3 SCToolBarCtrl::SetExtendedStyle 5 X/ X  Z+ h# }+ W8 Y
DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles ); ; s3 C1 M, D0 H7 `+ |4 A5 r8 E
设置控件的扩展属性,例如:设置列表控件属性带有表格线。
8 t7 J- D9 d/ Y( X2 ~  图4是个简单应用MFC类的既有函数来改善Windows界面的例子: 3 Y7 ]& u  M) R3 V5 [3 g3 X
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:& g: k% z! M9 A" Y; ~
  1. # o+ n2 `) P) O) p( e% w! c
  2. BOOL CUi2App::InitInstance()
    2 g, o8 Q5 ?% `2 x( w
  3. {% ~, r; H6 O+ e6 _; @0 H, y# s
  4.         //…
    2 z. y) w7 C" E# ]
  5.         //设置对话框背景色和字体颜色
    5 f+ F, [4 K. a7 s2 |- h6 |
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
    8 t( h, q4 h- A! R
  7.          //…
    0 W3 u( w4 [6 B0 D. k* v
  8. }
    , E6 O/ B! O6 X9 w0 c
  9. BOOL CUi2Dlg::OnInitDialog()
    * N9 t5 s4 p" Q
  10. {3 E6 p$ W7 k6 n$ W7 w
  11.         //…; k8 I" a! |4 O6 N) ?3 H
  12.         //设置列表控件属性带有表格线
    7 V6 @7 [+ {9 W, r: p) ]! v: ]
  13.         DWORD NewStyle = m_List.GetExtendedStyle();
    , i" k. z# S+ x/ U* \2 u
  14.     NewStyle |= LVS_EX_GRIDLINES;% ]- W8 x+ v" T5 y0 j
  15. m_List.SetExtendedStyle(NewStyle);2 y* H* ~) T0 F& C4 d; R& n
  16.         //设置列表控件字体颜色为红色+ G" ~/ O$ S* z( a1 O/ l7 f
  17.         m_List.SetTextColor(RGB(255, 0, 0));
    . ]0 X" a& Z7 R# a( J
  18.         //填充数据
    1 r8 M& g, j7 f% V8 X' W7 }$ c! b
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);
    * @( h. X% a1 R8 b! C+ D2 C
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);3 Z$ y# g& D5 ~7 D
  21.         m_List.InsertItem(0, "5854165");) Q, ~0 `+ I6 O: k' V% g
  22.         m_List.SetItemText(0, 1, "白乔");2 n5 _8 k( K3 N0 a# m) f+ v0 R' b
  23.         m_List.InsertItem(1, "6823864");5 Y8 W: I( @( r: y+ y3 v3 x
  24.         m_List.SetItemText(1, 1, "Satan");
    0 w, P% n# l# h
  25.         //…6 e3 B* T" g; Y/ l/ `
  26. }
复制代码
0 e* {* {5 n  C0 z4 c, z; ?
  嗯,这样的界面还算不错吧?
/ G# O5 \  s5 A
# [+ k, T1 ~% @' B  3.3 使用Windows的消息机制
( a* I3 Z# N7 p+ ~- Q  t8 P% X  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息:
  m* t) M/ k6 F: L: BWM_PAINT
& |) J4 e. _; G6 l4 ^WM_ERASEBKGND # N$ z3 m5 ~+ L& S& f( J8 a
WM_CTLCOLOR*
1 m' ?1 j; b8 v& A4 KWM_DRAWITEM* * ~7 l# d, B! J4 x1 ^1 i( _6 p# g
WM_MEASUREITEM*
. S8 c7 z6 [( J/ }3 z2 sNM_CUSTOMDRAW* 1 [0 A# s; f5 p7 S' M5 Y7 y
  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。
2 k, {8 B  b6 N' X0 c
$ P: d3 p" O9 U  3.3.1 WM_PAINT# y! w7 s" ~+ P* z8 s7 n
  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。
5 g3 @% S# g1 @% |  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下:
' W) C( h' q. s# M' o, C, O+ J  afx_msg void OnPaint(); ) y+ v+ p# G" H8 `( ?0 j" l" ^3 p
  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示:
/ ]* h! j' V- L6 F" g) N# ~
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单: 3 u$ @) j8 M, |! k" V

  1. ) }7 u9 G9 D2 ^% E) [
  2. void CLazyStatic::OnPaint() $ \! R0 L  P7 Q  Q
  3. {
    $ l, o/ C7 A: u" h7 D  [
  4.         CPaintDC dc(this); // device context for painting
    9 L9 ]5 |' X2 o
  5.          
    5 c9 E  Z1 s$ {5 l
  6.        //什么都不输出,仅仅画一个矩形框
    ( G0 d1 }$ W' {: \
  7.         CRect rc;
    8 T& V2 p3 ?2 _& ~
  8.         GetClientRect(&rc);
    , C1 t5 M  J- E  U( I7 {" N% U
  9.         dc.Rectangle(rc); - |7 G" M3 r& x+ m
  10. }
复制代码
. i( f! d" v) ~2 S2 n
  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。
, Q. e8 t* y, J$ s: N, C: V
+ ?8 y4 s+ c7 z) @/ ]$ m$ m* N  3.3.2 WM_ERASEBKGND 8 N2 E* H  K& [/ f3 r0 ?6 S
  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。 ; ^) _; J/ S0 P: O. Y4 f- }" A
  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:* n9 |/ D9 A# m  n  _) ^
  afx_msg BOOL OnEraseBkgnd( CDC* pDC );
% D; M% I% V2 e2 f# M3 a  返回值: . q2 V8 T) X: g4 }
  指定背景是否已清除,如果为FALSE,系统将自动清除 & R  g6 E; A3 ^1 B4 D8 i
  参数: pDC指定了绘制操作所使用的设备环境。
( z1 ^& V" {& S7 ^) P  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景:
0 x0 T" B4 }6 T  B2 T; p
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单:
3 e: f: a& {4 P" R0 v6 E" p3 x! h

  1. 9 y8 u0 S4 V# ?
  2. BOOL CUi4Dlg::OnInitDialog()0 F# D2 w+ x3 \" F0 ~
  3. {
    : y1 o& u2 I+ [6 m1 A
  4. //…
    $ `  Y: h' c& y$ o
  5.         //加载位图
    0 n5 P# R& `9 r9 J$ L  i* f4 M' H0 W
  6.         //CBitmap m_Back;
    # n& o1 G4 h9 T+ T  {
  7.         m_Back.LoadBitmap(IDB_BACK);
      d, B3 W8 |( G: T
  8.         //…1 u( w5 L' _& G8 L5 I7 K0 m
  9. }5 D5 }2 G8 }: R) r  p( v7 t2 w. w- L
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC)
    % m- [2 y1 L/ e4 u/ Y
  11. {
    3 b* p% |! V+ R
  12.         CDC dc;
    8 i: K' i) L) {% ?9 Y6 p$ k
  13.         dc.CreateCompatibleDC(pDC);1 b1 u( j! c/ O) a3 {8 J  ?4 T3 E3 L
  14.         dc.SelectObject(&m_Back);
    * }: g! L3 a, A. @7 Q/ T
  15.         //获取BITMAP对象
    7 o: y- ]) D/ H' {1 i" u$ y% e7 F
  16.         BITMAP hb;
    & F% ?5 K9 H" G8 E1 @% L2 r
  17.         m_Back.GetBitmap(&hb);
      `$ _: M3 t9 c# n0 ?
  18.         //获取窗口大小( h% N* A1 d7 y1 u! a/ l
  19.         CRect rt;
    0 B: O: b( z2 ^4 t4 z
  20.         GetClientRect(&rt);' W7 t1 N# C( t! U( G: Z; y5 v. @+ z
  21.         //显示位图
    . e; F& E% r* T
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),/ `) G# D+ A8 Q  P% j2 d! n
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);
    , B3 v. i. l/ p+ {' Q
  24.         return TRUE;
    1 q! E3 W" E: o7 d4 w- I
  25. }8 G- o- s9 j6 p" y' L) v
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)   u2 [- Z2 K" c1 G) C, G
  27. {3 {+ h& S4 X/ n
  28.         //设置透明背景模式
    - p% D6 y+ D1 d- ~8 o% U# t7 W
  29.         pDC->SetBkMode(TRANSPARENT);, H. r' j5 m3 j% N2 E2 F; C
  30.         //设置背景刷子为空
    7 p+ r  n0 ^2 z0 x
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);
    5 x$ V/ }- I/ ^! A3 H6 X- W8 X
  32. }
复制代码

0 q" f/ E" O2 ?3 h/ K1 z  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。
1 ^, z7 g; H4 a" y
7 U" i- }; R9 ]* v* h  3.3.3 WM_CTLCOLOR
. C9 `- c9 ]2 @1 T9 P5 b! N0 O- M  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。
& Q5 P! {9 @" I, w7 B  WM_CTLCOLOR的映射函数原型如下: , M) x# _* o- m/ ?" x- }* k, j
  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );
* B3 J) s' g/ p) u: n- ]  返回值: 用以指定背景的刷子 * k1 F  w8 _$ G) j
  参数:
  Q' v9 O- ^6 A5 Z9 m% {  pDC指定了绘制操作所使用的设备环境。
2 i8 v5 a' ~4 ~5 D  pWnd 控件指针 . V  y9 x* o; W" N
  nCtlColor 指定控件类型,其取值如表2所示:1 X1 |5 N1 E- j* w
  类型值 含义
4 a( {* ]* A6 `CTLCOLOR_BTN 按钮控件
2 g1 @6 p; H* G4 fCTLCOLOR_DLG 对话框 6 }5 l% c& Y/ K9 @1 Z8 T9 Y4 t8 N
CTLCOLOR_EDIT  编辑控件 7 M3 g% y6 K6 _- l
CTLCOLOR_LISTBOX  列表框
7 S& x% @- U% J# bCTLCOLOR_MSGBOX  消息框 % w  |2 u* n3 F
CTLCOLOR_SCROLLBAR 滚动条 & I% B8 b& s7 I+ e. H- \* [
CTLCOLOR_STATIC 静态控件 5 B- u. p' s7 R/ {, Y! ^1 M
表2 nCtlColor的类型值与含义
" t+ n" v; v0 E. R3 S* r
! d% w9 O5 l+ e: `+ b1 A  作为一个简单的例子,观察以下的代码: 4 R$ }, s4 S9 h0 ^9 }

  1. . ?, i  {5 a7 v4 ^' A- }! ~# }0 V! O( \
  2. BOOL CUi5Dlg::OnInitDialog()# |+ Q5 o3 K  ?5 P
  3. {
    5 N( X' p! @4 I  a
  4.         //…
    4 r8 Y. y$ l& T( ^# X
  5.         //创建字体$ x+ w: s0 c$ a6 L9 s+ g4 c  f: g
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font2
    ( @& U" F/ b* {+ n- C$ b
  7.         m_Font1.CreatePointFont(120, "Impact");
    2 p, _: B3 G# {9 {. o) r' T
  8.         m_Font3.CreatePointFont(120, "Arial");
    ( b- v4 A; ^, ^% @) x, d" S
  9.                 return TRUE;
    4 C+ H* |6 e! P9 N% |7 u
  10.   // return TRUE  unless you set the focus to a control
    " v2 f$ ?$ n2 c! L2 y6 V2 f" K
  11. }2 V# [$ D+ k' E" e5 O
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
      n+ ^2 W! M  C6 J9 [( {
  13. {
    4 U+ r$ ?  f6 _1 `
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
    9 i8 }. n8 L8 K; _( U9 c: v+ c
  15.         if(nCtlColor == CTLCOLOR_STATIC)1 P( m; z9 }: q
  16.         {
    3 O. N# X$ T2 k- F" N+ f
  17.                 //区分静态控件
    & q* `7 A- V9 J4 V( h
  18.                 switch(pWnd->GetDlgCtrlID())7 L# x8 s1 g$ R9 }
  19.                 {/ P, j/ I- ^" q  ]
  20.                         case IDC_STATIC1:8 U1 f2 K# V6 t
  21.                         {; s( o% S! h' {9 }# F
  22.                                 pDC->SelectObject(&m_Font1);
    6 ~$ S  f. r  p5 K/ y, h/ G
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));% U8 w3 L2 T/ j* m8 P0 e# H
  24.                                 break;
    1 t* W# v+ ^" P
  25.                         }
    1 O9 p8 ~  j, t# y0 y
  26.                         case IDC_STATIC2:
    & X! H& M6 I' g+ m8 N- \
  27.                         {/ S& L6 g, ]6 Q/ `) @- M
  28.                                 pDC->SelectObject(&m_Font2);: e+ {0 t3 _( Z2 z! {# y
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));  A/ Q& Y7 E# }7 }. B
  30.                                 break;# |/ R( @2 `) P. f; j2 ~
  31.                         }
    0 d$ }2 l+ H$ q& e8 s$ u; a
  32.                 }/ T2 V& x, X6 a7 ?: Y/ C
  33.         }/ t& t# ]: F) G
  34.         return hbr;9 a$ h) k9 `9 e
  35. }
复制代码

6 @0 \. D. a8 |& r* _   生成的界面如下: 0 H% @/ I/ N" a: v4 C' t
图7 利用WM_CTLCOLOR消息美化界面
0 W3 {$ E! Y3 B! k: d8 P; N, `
  3.3.4 WM_DRAWITEM 0 t/ Q8 \) [  g1 q
  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。
$ ?# G# W+ L! R% z8 n3 t; ^' ^5 c# x# N  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。 4 o; t) a; T7 T, j
  WM_DRAWITEM的映射函数原型如下: * W% l2 w+ ?( f1 h" b
  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );
/ N& \% v. h+ k+ X& H) n  参数: / E; f- ^9 F( Y. M( Y& D) k; f8 z
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 5 i  {7 H+ [5 J1 K+ b! G5 {5 P
  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下: 4 M  Q# `( q2 H7 R2 o
  1. 5 C5 X- H3 o2 B4 d3 B6 T% m
  2. typedef struct tagDRAWITEMSTRUCT9 K0 g6 u, F. ~" ~- m
  3. {7 t' y, b1 v8 E; N' h9 P6 q& h" ~
  4.     UINT   CtlType;
    % D, t$ t9 w1 @, Y9 ^
  5.      UINT   CtlID;( u! _& M7 z" z! I1 `& C
  6.      UINT   itemID;: r9 E. i" S8 R2 `! l
  7.     UINT   itemAction;
    7 L. O% x' V) U6 D! g/ k/ |
  8.     UINT   itemState;
    : l% }& z: y$ x1 k
  9.     HWND   hwndItem;
    : ]5 g* \! ?6 @! S. v) F6 n
  10.     HDC    hDC;
    1 z8 ~; l) T# m( M% B/ v3 V' U
  11.     RECT   rcItem;
    3 r  R  @$ H4 y2 J/ C
  12.     DWORD  itemData;  O4 g( @' ?3 ?# v9 g* |2 Z
  13. }DRAWITEMSTRUCT;
复制代码
6 U! [0 l+ S# Y, ]& d
CtlType指定了控件的类型,其取值如表3所示:
3 `6 N+ O6 c4 f# W0 D7 h类型值 含义 ; y2 _: M6 ]  ^! S
ODT_BUTTON 按钮控件 5 K) ]: t! {- }
ODT_COMBOBOX 组合框控件
3 _8 o- z$ O- ?" rODT_LISTBOX 列表框控件   b9 L( w; ~3 [$ M. p
ODT_LISTVIEW 列表视图 6 S0 E# s1 ?1 O: {  a
ODT_MENU 菜单项 5 i0 V5 p2 U& b5 U3 E
ODT_STATIC 静态文本控件 # j% u2 w9 f' z$ R& y
ODT_TAB Tab控件
* ^# Y4 r) |% I0 _. n表3 CtlType的类型值与含义
- ?5 h( t  ]. t( M: ?
5 D+ I$ r/ y! `3 r  CtlID 指定自绘控件的ID值,该成员不适用于菜单项 * n1 g1 M$ |0 ^2 }; @9 ^/ l2 i1 l
  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。
' S1 W0 V/ n& E* O) O  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:
. z4 m! {3 m8 ?, Z) W3 O6 ]类型值 含义
  T& v; x& x0 kODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。
1 y+ ^& {' w  D6 j1 |5 N5 MODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。 7 {- @5 D/ e) f; i
ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 & v# [4 y" A& H2 p6 {* \" I
表4 itemAction的类型值与含义" |! T$ b  @# w" ?6 b! F# d3 `
  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:* n& i7 W9 F8 a6 [1 E
类型值 含义   j7 Y5 h+ O+ b4 _6 |9 k# N; C$ u) l
ODS_CHECKED 标记状态,仅适用于菜单项。
2 y, H& u: G, Z% E3 D$ I% w: {. DODS_DEFAULT 默认状态。
2 W& e7 d$ H2 B- x  E. \! c) iODS_DISABLED 禁止状态。
& Y' U" R( H% FODS_FOCUS 焦点状态。 $ @; n8 d% [- l. T. T* _
ODS_GRAYED 灰化状态,仅适用于菜单项。 # K  U. ?  Z; i
ODS_SELECTED 选中状态。
# T5 R4 r  L  Y6 l5 jODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。 # C/ Y5 e+ q" r. {9 y  @
ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。 5 |% i3 G$ f2 f6 X
ODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。 ! ~( L; P+ Q" d
ODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。 $ S5 L0 o9 Q- C9 a8 f2 `3 X
ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。
$ l0 j( C! |) ^表5 itemState的类型值与含义% y. F& e7 N+ }: H4 S7 b# K+ S4 y
  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。 ; c5 a7 Y: k9 {# t, B! U3 s+ I& g
  hDC 指定了绘制操作所使用的设备环境。
* m5 P' e5 a4 [( s. }; j7 @( X; h  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。
' P( J8 p8 t7 Y" y6 G2 I  itemData ' ?  ~, [. v/ g* O: z
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
; F; a7 I' N& J- L  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 6 p) B& {) t; x5 x
  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。 ( h! x0 D! L* ^1 i% f) N* Y
  图5是个相应的例子,它修改了按钮的界面:   g5 m8 P3 E" h7 f
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下:
8 p9 b% |3 [+ u1 |0 e
  1. 3 N! f. W, ?3 E( s, g4 I" n
  2. BOOL CUi6Dlg::OnInitDialog()
    3 v/ t0 ?5 e/ R# U: w+ e- i1 N
  3. {
    7 S. f% _! u& _9 _$ l; K9 C7 b
  4.         //…. Z8 W# n( Q! G
  5.         //创建字体& w  p. q  g" F: z. s
  6.         //CFont CUi1View::m_Font+ `; H! Q, k& m3 `3 r  t( A4 @
  7.         m_Font.CreatePointFont(120, "Impact");; t3 A  i0 Q- D: \$ K& S% ?
  8.         //…
    & o, ~- m, Y# O. i. `  s$ z" ?( E
  9. }7 y- G+ U4 x- y! w( R3 {. ^# Z
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
    : _8 Z8 i, i. c4 A* ~* q
  11. {" Z- l9 P  {. W4 r' c, A5 o
  12.         if(nIDCtl == IDC_HELLO_CFAN)5 v9 U' x- F8 n  T( N
  13.         {
    % u/ i, }, M4 ?1 c8 e! l% x/ _
  14.                 //绘制按钮框架5 }' o' T. u, j# ~2 c' ]9 o- Z# o
  15.                 UINT uStyle = DFCS_BUTTONPUSH;  n, r6 n. K# T) ?+ g% J+ M) O0 v
  16.                 //是否按下去了?7 z$ K! u' s$ v; D, t
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)
    6 B6 a8 U- v3 I5 _5 Z; a/ e
  18.                         uStyle |= DFCS_PUSHED;
    ; W; R/ K8 q3 l2 C' |5 Y
  19.                 CDC dc;
    + O" T; v/ ]( M) p
  20.                 dc.Attach(lpDrawItemStruct->hDC);
    - Y; z: Q: L6 m  W% K
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);
    & y. g& R. k: G+ B+ O) n7 b+ V
  22.                 //输出文字* A4 K/ c% v6 X) n0 n3 l
  23.                 dc.SelectObject(&m_Font);
    7 w% x# T7 T  e, U: b& s
  24.                 dc.SetTextColor(RGB(0, 0, 255));5 p5 w5 ?8 [$ x7 _0 i. F! I
  25.                 dc.SetBkMode(TRANSPARENT);; ^; L/ J# G& J8 N2 ?4 g7 X0 w
  26.                 CString sText;6 H" Q5 W& [# L3 k
  27.                 m_HelloCFan.GetWindowText(sText);1 t+ A2 ?/ o, I1 v) u/ ?$ F
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);8 _2 S3 w* P  {
  29.                 //是否得到焦点
    9 p- U( q  _1 o6 \
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)
    & d% e) g$ m4 e7 y6 I
  31.                 {8 D% a) g6 d: g  w! ]% S7 V" g
  32.                         //画虚框( V2 G3 w) g3 N' ^0 o7 \
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem; $ `' s* Y1 p# s; }) H. M6 g4 D9 @
  34.                        rtFocus.DeflateRect(3, 3);4 ]6 ~9 m1 Q$ m3 X; q9 }2 L
  35.                         dc.DrawFocusRect(&rtFocus);& c8 ?3 K% x/ l" c% u) N
  36.                 }
    . p0 }. o; y* D/ R5 M: L' X
  37.                 return;
    ; M$ j2 A( Y5 x4 n' n2 e1 ]- `5 x
  38.         }# g$ K$ W7 t1 @8 P2 y8 P
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
    6 N7 u, h3 q7 x" j
  40. }
复制代码

4 y" q; ~; P1 C6 W  别忘了标记Owner draw属性:
6 z2 y. c, e8 R& \; [7 E9 C
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。 8 I2 A& J' Z* k' v% A% X( }
0 A9 {5 A! m( I
  3.3.5 WM_MEASUREITEM
8 b; S5 z/ X9 d9 l9 ^  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
7 u3 u4 s- X4 O  WM_DRAWITEM的映射函数原型如下:
9 P4 r& V! f' Q3 e( B  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );
( e6 V( K) Y. q8 u+ q7 c/ I  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l2 \) B5 E' [2 O$ J3 n, e
  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下:
: Z9 w6 Y) q- k' d1 ~
  1. 3 U0 J! h& u& A# _" X5 X. Y5 E! [) j
  2. typedef struct tagMEASUREITEMSTRUCT
    . o) U4 }* \2 t! m$ U7 t0 M1 \9 e
  3. {
    1 b% I) P9 Z# {  p) P; G5 q
  4.     UINT   CtlType;) Y$ w0 x1 K: k+ R9 d# z7 v" J% n) G: q
  5.     UINT   CtlID;
    * v+ l$ \7 |8 V7 O! o
  6.     UINT   itemID;
    2 E& X' C+ t4 L* _
  7.     UINT   itemWidth;" W3 b8 w( W0 ]- S
  8.     UINT   itemHeight;  J$ z( a$ N8 [" Y( @6 S
  9.     DWORD  itemData;4 O7 x" i& o# e+ a" w$ l  n
  10. } MEASUREITEMSTRUCT;
复制代码
; z' T5 v( G+ @/ ?: }6 \
  CtlType指定了控件的类型,其取值如表6所示:
( {) ^$ }- Y# D( Z- _6 f! s' e: ]类型值 含义
6 ~4 C- Y4 J6 K0 m7 a. Z. B4 ]5 RODT_COMBOBOX 组合框控件 8 ]8 I; `& y. `0 y; v" F
ODT_LISTBOX 列表框控件 ) t5 B# \& `% G
ODT_MENU 菜单项
* S& B2 ~5 q3 d/ o表6 CtlType的类型值与含义7 l& e% @5 \. X% V
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
1 n$ k: m0 C! i6 Q! v  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。
6 U/ l" C, }5 i1 u6 [: I  itemWidth 指定菜单项的宽度 1 z2 T/ j2 u: e
  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255
( c0 e  D) ]& j  itemData % Z) F; i. t" z
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 # E4 i8 Q3 _% @" J- G) s2 H0 M* D8 @
  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 / V1 P: g( c, b
  图示出了OnMeasureItem的效果:
# @5 w& s2 G; B, Y% t
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下:
3 x% s/ ~( G+ q3 o! M7 z
  1. ! T8 ]2 C7 L1 O0 I5 A
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
    0 O* w$ _* \# h
  3. {8 s* v( M6 D5 |
  4.         if(nIDCtl == IDC_COLOR_PICKER)
    $ s: H8 E$ r/ Z$ x6 X0 l4 G3 x
  5.         {$ U; F; N3 L: L: b8 h! q( P& \& Z
  6.                 //设定高度为30
      H6 |1 x8 n7 O1 W2 }3 k- N: d# D
  7.                 lpMeasureItemStruct->itemHeight = 30;
    / a8 d8 s' P+ L+ n! f. |1 U
  8.                 return;
    $ x& E% c9 G8 L
  9.         }
    ; e6 B9 B( x, |  K& D- {
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);# S3 @; k' q$ d4 a" {
  11. }
复制代码

3 g7 }# k& a% f4 C; \4 u2 Y  同样别忘了指定列表框的Owner draw属性:
1 k& @+ c! i2 c& [. Q
  图11 指定下拉框的Owner draw属性  % w5 `9 @8 i* V
8 a( Z* `2 s  r3 k8 p
  3.3.6 NM_CUSTOMDRAW ( g5 E" [6 n& s+ t6 s* ~
  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。
- j; _! P; z* x( e  可以反射NM_CUSTOMDRAW消息,如: / `: H% r$ r; E+ f0 g  d  {
  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
& K, @& H0 T  l1 H( E  }  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); # H2 F" p) r( B& W3 p1 q0 I5 h
  参数: 1 X, D+ ]4 p" M+ i3 T& V# h
  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下: / Z. `. Q! Y7 N7 \
  1. 5 z) q8 f+ K$ t- Z
  2. typedef struct tagNMHDR
    4 Y* P. a; j3 r$ W
  3. {- Z" L' N0 p( e8 c( B
  4.      HWND hwndFrom;3 O  ?% ]& j; h5 j
  5.      UINT idFrom;
    9 c# g& d( J, G$ z1 y
  6.      UINT code; 0 F8 T6 j9 Q9 R5 W
  7. } NMHDR;
复制代码

& q6 N7 Y! x% y* F4 j/ D& f3 m  其中: ! V8 L4 F, J( Z: ?* l9 Q4 j" d
  hwndFrom 发送方控件的窗口句柄 ( g- W, V) K' q& p4 u9 j- Z
  idFrom 发送方控件的ID code 通知代码 ( W& Z. H% h9 [3 b# a3 n
  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下: ! X4 g5 m2 @7 z3 `

  1. : M* ]& }! p" K5 @
  2. typedef struct tagNMCUSTOMDRAWINFO3 ]; o) J# ^6 R6 I
  3. {1 M8 [3 |, y' @$ p8 w/ r  b' [$ N
  4.     NMHDR  hdr;
    8 L1 C3 L" m0 o$ o0 i2 i
  5.     DWORD  dwDrawStage;2 I- Q) G- F5 F1 I; B
  6.     HDC    hdc;) r0 `! C; g/ B3 Z; a" {' l9 M
  7.     RECT   rc;
    * Q; J0 H8 Y. `# P( R8 e0 y
  8.     DWORD  dwItemSpec;0 A3 ~/ H; N4 A' o5 Z! S4 f. b9 n- s
  9.     UINT   uItemState;
    + g( \4 _/ f+ L$ a# g
  10.     LPARAM lItemlParam;
    + K4 u( u4 B! A! `
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码
* |! z! D1 r, ~3 K1 Z: e
   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:
1 P- K+ r5 `9 E1 D+ Q8 z类型值 含义
& i. p. _- K: R9 X. j# OCDDS_POSTERASE 擦除循环结束 , \" r, \; q. X& u: ?
CDDS_POSTPAINT 绘制循环结束 8 y7 H& [  V( v) P8 L$ V
CDDS_PREERASE 准备开始擦除循环 ( s7 h4 r  z1 @6 {, g
CDDS_PREPAINT 准备开始绘制循环
: _9 n7 _9 X/ L9 b; t- DCDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效
+ I* t9 U, R! m. c( P- B% SCDDS_ITEMPOSTERASE 列表项擦除结束
9 \" s- w! u0 Z6 JCDDS_ITEMPOSTPAINT 列表项绘制结束 2 L& s; C! X8 v8 L' z. y
CDDS_ITEMPREERASE 准备开始列表项擦除
& o! R; t) b4 ?" {6 D3 WCDDS_ITEMPREPAINT 准备开始列表项绘制
8 u+ M. O& j  ICDDS_SUBITEM 指定列表子项; R: D5 N3 {, F+ ~# B
表7 dwDrawStage的类型值与含义
: h2 {* m3 U5 D  B& t  hdc指定了绘制操作所使用的设备环境。
# d: ^% ]8 I/ z8 n, Q3 D5 S* W  rc指定了将被绘制的矩形区域。 2 {+ z5 |  N4 H! X# \; i/ g
  dwItemSpec 列表项的索引
# C4 a  o* z0 Z  uItemState 当前列表项的状态,其取值如表8所示:
/ e+ W; y! V1 o- z类型值 含义 ( s5 m0 u) U' w/ h1 K
CDIS_CHECKED 标记状态。
# Z) s( p# v$ `% Q/ @CDIS_DEFAULT 默认状态。 3 }( s# N8 D( q: X  j) x$ x- S! t
CDIS_DISABLED 禁止状态。
$ @# i, m% ?, [& L! t/ P2 |CDIS_FOCUS 焦点状态。 9 W- h7 J8 S+ L' s4 f
CDIS_GRAYED 灰化状态。
$ ~$ i# \  o6 T* ~, |! CCDIS_SELECTED 选中状态。 - I& l2 d9 [/ s4 F1 D( _/ p" C0 }
CDIS_HOTLIGHT 热点状态。 " O5 Z7 }, {% }! e% d
CDIS_INDETERMINATE 不定状态。
% V9 q) @; ~9 p$ s9 m6 ?# r- \CDIS_MARKED 标注状态。' e: L# L1 ?, b0 ~" w0 _! l* l& m8 A" D
表8 uItemState的类型值与含义
' ~' |2 I* F1 f: U3 u! [  lItemlParam 当前列表项的绑定数据
& q/ J# y! T, i+ l# j% C  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage:
2 o4 k1 I  W7 k2 }  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:- ^: H3 d% B3 L& G' E
类型值 含义 % }" ?6 }9 H) X; Q1 v6 L* G
CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。 4 l0 s& h5 D3 M0 W/ `
CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。
) m6 n  \/ K" a7 qCDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。 ; A" [: V. v! z# O8 |. D! M$ F
CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。# e# k/ Y4 l' ]- `7 X
表9 pResult的类型值与含义(一)
5 q7 e. {' t" F- _( R, z# w9 S! w  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:
6 a: S) k6 v/ i8 O8 W0 w  o类型值 含义
% x' Q! [/ V/ G( sCDRF_NEWFONT 指定后续操作采用应用中指定的新字体。
  v0 H& X" M- W1 Y6 m- d0 rCDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。 ( d) k0 y9 c/ x
CDRF_SKIPDEFAULT 系统不必再绘制该子项。
8 \" D+ V8 b" H. q" K5 [表10 pResult的类型值与含义(二)4 D% a; d2 `6 `' U& ]
  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子: * L4 Y$ t$ b, N  a3 x( q! H- K
  图12 利用NM_CUSTOMDRAW消息美化界面
) E: N  i- ]) B# x  N0 ?0 w  对应代码如下: 7 D; E" f  ]4 r+ R! i

  1. 5 c+ W9 X  b% @5 C
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)( C+ i2 x# H' Y; t0 a" x/ m
  3. {" l2 c. d3 O9 o$ O  x$ I
  4.         //类型安全转换
    ' ?( H; |+ R: f
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);
    & @. w/ L# z; w
  6.         *pResult = 0;
    . ]! ?$ a( A% i  y) F% }' @( g
  7.         2 t1 I' ]* u6 z+ B% e: r5 u
  8.         //指定列表项绘制前后发送消息5 \# K* l  B6 ]/ l  A- I5 q4 X5 b/ ^
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)) M9 W6 X: P, |$ X! `+ e& T7 M
  10.         {
    + ~+ a2 g" F6 U2 t* [. |
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;0 P3 k! u9 n6 b: u
  12.         }
    & ~- Z8 h1 ]$ G/ t1 R- ~4 B
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)
    ! |7 l( z8 R8 B/ [( ?' L8 b
  14.         {
    + E$ |5 h2 J3 `0 _: c# ?' d
  15.                 //奇数行
    9 ]. U- O  k+ y
  16.                 if(pLVCD->nmcd.dwItemSpec % 2)
    4 R' X5 {7 r' n
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);' f! R0 [$ r3 L' o" y
  18.                 //偶数行$ m" y- B0 `/ `# |
  19.                 else
    0 |( h( c3 F+ n6 ]6 A7 M/ Q) d( A
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);9 \/ y* Q+ n; m7 S
  21.                 //继续
    2 s9 W. s1 w, G& M
  22.                 *pResult = CDRF_DODEFAULT;
    . Z1 c7 y# p) k
  23.         }
    4 x1 ]3 K: w4 O4 v+ Q* Z/ o
  24. }
复制代码
3 g8 F. |3 c* c5 W: X: l3 r6 _
  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。 , M# t/ x, Z" `. {& k7 t8 N

4 K. W/ B0 s0 C5 ?% {4 O1 q$ P! u  3.4 使用MFC类的虚函数机制
6 u, }4 @: Y. ~" l; L% x  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子:
) _; N$ R% Q* f2 Z
  1. / t' E( ^1 i' s% L
  2. void CView::OnPaint()6 x6 v4 q; E( S& x
  3. {
    " ^. I2 J, h  d( t5 A5 x- w; v5 e
  4.         // standard paint routine! q$ c. R, Y* |
  5.         CPaintDC dc(this);
      _7 J2 r. g8 A+ j# Z( r
  6.         OnPrepareDC(&dc);0 Z0 e# q3 B1 ?# z/ p
  7.         OnDraw(&dc);1 l3 f) L! F. t: ?! i, y7 p% c8 M
  8. }
复制代码
2 M4 p4 ]" [- W! u
  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。
8 S! `9 J( U. C5 j3 Y1 m  以下列出了与界面美化相关的虚函数,参数说明略去: / h* q9 o( J2 `, Z1 f
CButton::DrawItem
# l  S; e: |  ]CCheckListBox::DrawItem 7 R7 N3 a; k# \
CComboBox::DrawItem
( T# Q, ?" W& U3 Y. h6 q" \7 ]7 L( f* zCHeaderCtrl::DrawItem " T$ w# v/ @; i0 h
CListBox::DrawItem
9 D+ i( N: h1 O- L) `CMenu::DrawItem 5 ]5 q% r5 M# i+ P
CStatusBar::DrawItem
- o5 _* x9 P: p+ m7 NCStatusBarCtrl::DrawItem " ?4 L# H' o) _* A( G5 Q) ^
CTabCtrl::DrawItem
% p% I1 F4 K0 y$ a$ V$ p0 avirtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct ); ( x& i8 A# r1 |0 l# g
Owner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。
/ E' g3 X, m1 k  T" z8 q$ t; Q  限于篇幅,在此不再附以例程。 : S& v- l' w: v' B2 U  v
$ ?# [" M6 r5 u" H3 t  ~% U
参考文献
0 z% _/ J* d+ i2 k4 r本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-10-1 20:50 , Processed in 0.022494 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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