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

VC之美化界面篇

[复制链接]
发表于 2006-12-14 13:11:14 | 显示全部楼层 |阅读模式
  1. 美化界面之开题篇3 L7 B1 |# I8 \! B- D& B/ I: `( y
  相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面:
图1 瑞星杀毒软件的精美界面
  程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。& G, `( ^+ C) q% {3 u  H( U
  “受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。/ c! H& t: [. Q( b9 M
& g( g* ~6 i3 P  P) X, o7 c2 }/ u* C
  2. 美化界面之基础篇. {" d! W! G& Z1 }' j+ H+ M2 b1 Z% `
  美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……. J# L/ N# Q8 t
( {& v0 _5 N- F3 B0 X( D( w
  2.1 Windows下的绘图操作
( P0 u0 U$ K7 V; ~5 q+ `  熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……
% ~9 L) e0 D; k% G3 [" t# J' Y( D2 |  Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。& C- B6 n; I& C* a
% r: W: w" J. b6 \7 M" `1 `1 p
  2.1.1 设备环境类
0 U+ i1 k$ ]8 g/ l6 k' G% C4 ]  Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。
; O- ?2 F2 w5 c/ y1 T) C: s  MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:
# @1 u1 w5 C+ w. s  Drawing-Attribute Functions:绘图属性操作,如:设置透明模式4 l# I1 k  y8 g& x6 z& Y+ S
  Mapping Functions:映射操作' l2 j7 i* w" H
  Coordinate Functions:坐标操作
" [) v3 o$ @/ j% k5 a% z  Clipping Functions:剪切操作7 l3 T+ D) q# O
  Line-Output Functions:画线操作
* Y5 I" i: P0 [, a+ d  Simple Drawing Functions:简单绘图操作,如:绘制矩形框9 c4 e6 B$ a+ s. ?
  Ellipse and Polygon Functions:椭圆/多边形操作
5 X% M9 v: P/ U: k# Z( X  Text Functions:文字输出操作8 u& o. I* Y5 \/ B) C
  Printer Escape Functions:打印操作4 b& e+ S% S, {% [/ S0 {6 i
  Scrolling Functions:滚动操作
- m. @$ T) M# p  *Bitmap Functions:位图操作+ z# U  X* k; {# F$ t  G: s$ G9 v5 [
  *Region Functions:区域操作
2 `/ e$ h$ ^/ U' f3 j  *Font Functions:字体操作( I2 w8 [6 V" B# W( M
  *Color and Color Palette Functions:颜色/调色板操作
; {. |/ H5 }* |* B  其中,标注*项会用到相应的图形对象类,参见2.1.2内容。
; l3 ?$ s, P$ r/ r8 r( {* k- |
7 A2 |5 ?+ f6 A  2.1.2 图形对象类! }& F# I& t5 m% A0 H0 e
  设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。
& e% a4 m9 ?' W  下面的表格列出了MFC的图形对象类:4 n4 _: T- j$ k! ^% [
  MFC类 图形对象句柄 图形对象目的
/ l1 [7 U1 l0 E( u: X) ]  CBitmap HBITMAP 内存中的位图7 Y- O8 [/ |; I) ?' s4 m
  CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式
3 ]: w4 X3 ^% P1 J; f6 C5 @, e$ c  CFont HFONT 字体特性—写文本时所使用的字体
* [3 T. E# S% N0 k  CPalette HPALETTE 调色板颜色1 v" M5 C' c: {: D6 ]  K9 o
  CPen HPEN 画笔特性—画轮廓时所使用的线的粗细; L: s" |' F  G6 g
  CRgn HRGN 区域特性—包括定义它的点5 Q. B  ]( {$ }2 @" ~% v  ^
  表1 图形对象类和它们封装的句柄1 `+ t- n& k1 b
- H7 e$ I  x! d( v( k
  使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:
+ _  T" b3 h+ M" h* L  T/ A5 C
图2 使用CDC绘制出的按钮
  该画面通过以下代码自行绘制的假按钮:
$ }0 v' C6 L7 B8 m
  1. $ O. ^5 t& P3 d  @1 O: H) e
  2. BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)
    1 T% ], e/ f4 [1 A3 g
  3. {" `/ z; S: n2 P& h
  4.         //设置背景色% R4 p; v& J. ]3 n
  5.         //CBrush CUi1View::m_Back$ N2 R! u& R) }$ u* ~( s  a
  6.         m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));
    0 E* ]( N1 \% _- Z3 i
  7.         cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL);
      [# B2 u2 u! S& z
  8.        return CView::PreCreateWindow(cs);5 j$ C6 o- j3 s) O+ C
  9. }7 ?& n1 A# d* W! Y
  10. int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
    * {: f# c+ O, X! I' I9 S' S  d
  11. {
    # n; j7 ~( E$ ?
  12.         if (CView::OnCreate(lpCreateStruct) == -1)
    1 g/ o/ q9 |0 M; N& s+ W; U. |
  13.                 return -1;
    * W( B1 h4 r, b6 G; n# T" v
  14.         //创建字体0 j) F+ ]# m# w! ]! @
  15.         //CFont CUi1View::m_Font
    & \/ ?. ~! d- q! x+ m
  16.         m_Font.CreatePointFont(120, "Impact"); / e; j, U% @* f: Y( V
  17.                return 0;0 g1 ?. G; X' w, d
  18. }
    3 U8 \6 J$ j) i
  19. void CUi1View::OnDraw(CDC* pDC)
      c, M2 s7 v! ^) H  \& p
  20. {! e5 Z- x! F& s" t
  21.         //绘制按钮框架: T2 ], f, ~0 U3 m1 ]9 }. O- W
  22.         pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH);
    6 i/ V) k/ }0 x3 x) ]8 I5 w7 r
  23.         //输出文字4 o$ h. }1 ^9 J  V" T3 `% o
  24.         pDC->SetBkMode(TRANSPARENT);1 v+ z' d+ L% [" W& s, E
  25.         pDC->TextOut(120, 120, "Hello, CFan!");& |8 F7 V2 d& a8 r
  26. }
复制代码
" Q& }0 v, G( a# s# j* M
  呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。   p8 i3 O& y/ B) D4 M

( U! u$ k! e& w. r  2.2 Windows的幕后绘图操作 & X$ u0 l1 `7 X, I
  在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。7 V) t7 K! v/ a  s+ W8 q
  有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。6 @5 i$ @" S. V0 R9 b. R
  所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……# G+ d+ p# s- i8 _
  有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。' b7 K: f# ?# c6 J, {
! E+ w+ g) Y, @: G7 r7 w
  3. 美化界面之实现篇& o+ Y- N' ?4 x' @
  Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。
& v5 f+ M- O" H3 C9 ~3 }. E) f; \6 c  W& @) ]( e/ D
  3.1 美化界面的途径
& y) X) n, [+ t$ }7 I* W+ H  如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:
5 j3 M, A4 D0 u  1. 使用MFC类的既有函数,设定界面属性;
. B$ e5 s, x& X. B  2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;
: p! ^" e. M, Z4 D: B! g  f  3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;% ^0 Q; w& D0 q0 L; P! v: b8 v7 y2 W3 T
  一般来说,应用程序可以通过以下两种途径来实现以上的方法:
. i9 `0 u( n9 d$ x$ A' j  1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;
, f) k. X0 n4 i2 G3 Y  L7 G  2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。
9 e: Q1 y( }" b! r4 o$ i' x  对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:- @7 k) ^6 C( }( P0 k9 S% v% Q
  ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示:
! G* h- ?# l  O9 A8 A
图3 为按钮指定CXPButton类型
  ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;
. z9 |3 N& f- d  以下的章节将综合地使用以上的方法,请读者朋友留心观察。) C" h; m5 Z: T& y& u" T4 t; I

8 A+ Q. O6 `1 Z0 T1 j5 e  3.2 使用MFC类的既有函数
* L. O; r  X# y1 g6 k# {! B: G  在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。
. }2 Q& j) F" u- u9 s. i7 O0 d5 \( p4 q% L6 J
CWinApp::SetDialogBkColor* E0 s7 V0 u% w
void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );
. u# ^' W6 y: q# J指定对话框的背景色和文本颜色。7 D+ |7 h% ]8 c: [8 B

) w7 h% k; U5 ]( f3 Q/ gCListCtrl::SetBkColor
2 s) t# T* v6 I" ~$ s' WCReBarCtrl::SetBkColor* Q3 U: \! H1 L! l7 |% o4 B! _( W
CStatusBarCtrl::SetBkColor7 m& u8 w+ O$ \4 A, Q
CTreeCtrl::SetBkColor4 r( q+ D5 e$ Q+ p4 Z2 }& d% W* [
COLORREF SetBkColor( COLORREF clr );
  S8 W4 M4 w  H1 H* K设定背景色。- A# j) L) ^3 N3 M

0 u" z( F) b, H7 lCListCtrl::SetTextColor
% I2 E" z. F' F: \7 z" b# WCReBarCtrl::SetTextColor0 f/ P; K+ R5 b& v, @& a+ M, o8 J
CTreeCtrl::SetTextColor
8 Y& K# K9 B+ F8 a; W6 h7 sCOLORREF SetTextColor( COLORREF clr );
8 B# p) n( s# c5 k5 k: o- {设定文本颜色。
7 _4 d/ l0 @5 G4 p% u% ?: }
( J2 w5 _- M7 f/ t/ x- tCListCtrl::SetBkImage
6 b7 F/ m; D: W! i+ yBOOL SetBkImage( LVBKIMAGE* plvbkImage );8 T( v" D. u5 V: t+ V( {
BOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);) e5 }6 `# k2 B8 B
BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );' {% w2 V, }( W8 S6 E
设定列表控件的背景图片。
7 D2 R- [) _7 L' Y1 z3 A* E8 C$ o  V6 w
CComboBoxEx::SetExtendedStyle, s* P' ?- A" Q7 _1 s
CListCtrl::SetExtendedStyle
7 h3 [; `0 m( l' ZCTabCtrl::SetExtendedStyle6 s* p# B  I8 d; Q. w# X+ A9 S
CToolBarCtrl::SetExtendedStyle
# D& L  \: [0 s, B' lDWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles );
, z5 e9 H6 p" t$ L+ q( V设置控件的扩展属性,例如:设置列表控件属性带有表格线。
4 e$ B# R  _, {0 o  图4是个简单应用MFC类的既有函数来改善Windows界面的例子: ' T: X5 U* E6 _- M( B8 ^; x
图4 使用MFC类的既有函数美化界面
  相关实现代码如下:6 ~6 ]% B, L9 y* u

  1. : W( P' Y5 i8 T. g2 }& o; ]
  2. BOOL CUi2App::InitInstance()
    ) L- R0 S+ P" n6 B, K& k, Q  j
  3. {
    ' y% h% j8 f2 b( P- {; Z2 ~
  4.         //…
    2 W; k$ G6 L$ _4 x- U  g: E
  5.         //设置对话框背景色和字体颜色
    $ q8 D* k2 Z+ c0 Y; n0 |
  6.         SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));
    " R; ?* }' a! B3 b
  7.          //…; c* P1 T) {7 y5 K+ n
  8. }9 p* I4 t; h( z$ d
  9. BOOL CUi2Dlg::OnInitDialog()8 @' s2 o- Y) x" e% [  Z2 g
  10. {6 h+ s6 {4 d4 y2 N
  11.         //…
    7 i* \- c2 x7 Y2 f
  12.         //设置列表控件属性带有表格线
    ; V, L- V. K' n$ I
  13.         DWORD NewStyle = m_List.GetExtendedStyle();" Y( S5 E1 X4 A
  14.     NewStyle |= LVS_EX_GRIDLINES;
    # `4 u% t+ @) n" p$ b9 ?) S; n
  15. m_List.SetExtendedStyle(NewStyle);
    ; V" x# T$ p$ S- U: R
  16.         //设置列表控件字体颜色为红色3 }% N0 J! e+ t( Z6 n; ?
  17.         m_List.SetTextColor(RGB(255, 0, 0));
    * Q& p2 O+ o5 [
  18.         //填充数据2 A3 a6 A& j" D$ _0 g+ l1 v2 C
  19.         m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);- |9 K7 h) J! A: \
  20.         m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);- p- s1 T) p6 w1 w3 ~: l9 W8 J
  21.         m_List.InsertItem(0, "5854165");4 h' t& `0 T1 B: a
  22.         m_List.SetItemText(0, 1, "白乔");& \$ Z! m- g' E1 \' s5 j1 b
  23.         m_List.InsertItem(1, "6823864");. W  {: T% e# A' S2 e
  24.         m_List.SetItemText(1, 1, "Satan");8 k! p) z7 R& R; V% r! `
  25.         //…. a5 R) e5 Q( Y2 C1 D
  26. }
复制代码

+ t& g! g5 u, i  嗯,这样的界面还算不错吧? 4 c5 g, Z; u2 S5 B5 O# q
" ^) s( d: X9 l# E6 u' R% ^
  3.3 使用Windows的消息机制
' g% i' d, ]: H% x2 i# U3 [  使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息:
7 B$ g" I$ r% |9 `WM_PAINT # d& O+ c8 Y& y5 ]
WM_ERASEBKGND
  g0 Q3 m( o" q$ U; y* v& XWM_CTLCOLOR*
1 f/ V; |( ?9 ]0 V3 bWM_DRAWITEM*
. |2 D. W( v( w" {, s# J8 J* mWM_MEASUREITEM*
: ^; e$ _' K" M* z1 h" J' u6 ]NM_CUSTOMDRAW* - i; u9 G: U& F) [
  注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。4 d' R6 e0 e* n7 e( s4 S, e

: Y) P0 ~9 Z9 R6 a% c! n# y  3.3.1 WM_PAINT! }! i. ^9 D0 {1 n! k
  WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。
  r0 e: H# p% K3 {6 r7 B  可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下:
6 ]" d3 ?* q" F  afx_msg void OnPaint();
: d  X( E& b) Z2 Z% w4 o) [% F  控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: 9 l& H% O" a' {
  图5 利用WM_ PAINT消息美化界面 实现代码也很简单: , y& }4 h3 F0 y9 U+ f% C
  1. 2 s1 I/ c- L: |( \
  2. void CLazyStatic::OnPaint() 1 U+ |7 J# t* U) |
  3. {
    5 i; ]; t* A8 U
  4.         CPaintDC dc(this); // device context for painting
    % Q5 Z! m# R% g. v; m4 n. M
  5.          
    7 X& v! @! A5 l4 h3 w; n2 J1 V
  6.        //什么都不输出,仅仅画一个矩形框) A9 ~. Y- _2 a+ P& w$ d
  7.         CRect rc;0 K" X  y2 d7 H# o% {
  8.         GetClientRect(&rc);
    6 m# w0 K! a- s1 A3 }. w' t
  9.         dc.Rectangle(rc); 5 A; S3 T* t0 P" M9 A. g$ Z
  10. }
复制代码

1 l: f8 v! Z2 c; b  哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。
8 E( M. M* J. a+ g6 J5 m2 f7 C* |2 }: J7 L5 q+ L
  3.3.2 WM_ERASEBKGND + h: q# i5 T$ ^$ d+ k
  Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。 - N/ |( C8 S% v6 a  F- S
  可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:6 A6 A0 N. Z2 [0 M9 M
  afx_msg BOOL OnEraseBkgnd( CDC* pDC ); & ~2 Y" w; k6 M5 o
  返回值: : g" Y5 g& Z/ v5 d
  指定背景是否已清除,如果为FALSE,系统将自动清除 4 h2 T2 [% T& U# z7 h8 k7 ~
  参数: pDC指定了绘制操作所使用的设备环境。 5 i( C' F' E( f/ Y" c, j2 c$ s
  图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景:
4 M. ^. V0 r2 |1 M6 ]
图6 利用WM_ ERASEBKGND消息美化界面
  实现代码也很简单:
/ l) T" W7 u5 g5 `

  1. 6 q3 C# T! e- q! v, n4 j6 ]
  2. BOOL CUi4Dlg::OnInitDialog()
    * N4 r, B! q8 d
  3. {
    . c$ f" o2 h- O; v
  4. //…
    7 B" S5 x4 ?/ n5 T, g
  5.         //加载位图8 c. a4 J1 a( u3 Y( Q: o
  6.         //CBitmap m_Back;
    " r0 d8 T2 f, ~) j2 {* l3 O
  7.         m_Back.LoadBitmap(IDB_BACK);
    ; B& u  L! c% L6 @0 m
  8.         //…0 Y7 q  R$ E! S  T$ {- I7 W& x' [% P
  9. }  h6 P) F; b& h6 k6 v) n2 N. a4 b3 [; j
  10. BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC) , b7 w6 l2 S& b  E8 |2 d1 k: s# K2 W; C
  11. {8 y; k2 f# k) x; i# m
  12.         CDC dc;2 G" g( [" S+ |; i
  13.         dc.CreateCompatibleDC(pDC);
    ; H* X& c5 E8 b
  14.         dc.SelectObject(&m_Back);
    - _$ m5 S: o3 W# Q3 S
  15.         //获取BITMAP对象9 v4 X+ z! L% J' r5 N4 {2 O
  16.         BITMAP hb;
    " K2 n( ~0 d3 W! o9 R
  17.         m_Back.GetBitmap(&hb);
    ! u! b- t" x- `/ Y  K  }
  18.         //获取窗口大小5 ^4 q; H( p8 c% s" u7 m; F8 F
  19.         CRect rt;" _0 T7 r6 k3 V! C" Z3 `
  20.         GetClientRect(&rt);% R+ O% ^9 g7 }& ?6 T, a- e
  21.         //显示位图
    2 ~3 i9 X( g( X7 \
  22.         pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),
    ' u; R8 x( E4 j# Z# a, p* V
  23.                 &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);
    ; C$ \) G2 H. M$ {
  24.         return TRUE;
    8 P6 `7 Y  m  T! F
  25. }
    8 o8 M$ \5 q4 [) ]  P9 X
  26. HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    5 H+ u- }, N9 [
  27. {' I' [8 |. Y7 c3 ]! k4 y) V- A7 \
  28.         //设置透明背景模式% L# E. H& g/ f7 x& |1 V% W' A
  29.         pDC->SetBkMode(TRANSPARENT);6 _, J( J( I) l
  30.         //设置背景刷子为空
    $ }1 z& b3 [" d4 ?1 B  |
  31.         return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);
    ) T6 E* g3 I, ]( t
  32. }
复制代码
7 K  G2 j+ P$ x( z& |4 \% Y+ k
  同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。
8 o" G5 |3 |$ s, \/ Y, n& b; T+ X; H$ R) Y, C
  3.3.3 WM_CTLCOLOR   ~1 g( Y9 M& l0 ^! _
  在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。 / l# j; ^7 B; Q2 V! i: T
  WM_CTLCOLOR的映射函数原型如下:
) k" ^3 _5 A" L# o4 O  afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );
: |" W: n. k. p/ r  返回值: 用以指定背景的刷子
* k6 X6 u/ L8 G  参数:
, A. R1 k6 H8 y- b: J. V  pDC指定了绘制操作所使用的设备环境。
6 \2 U) v0 i1 y% {+ A  pWnd 控件指针 1 N% {5 |6 F; O3 z. d
  nCtlColor 指定控件类型,其取值如表2所示:" r6 ?# q0 u' X2 ?
  类型值 含义 2 P2 }- T! f( X7 H
CTLCOLOR_BTN 按钮控件 1 i" t: o6 K% Y
CTLCOLOR_DLG 对话框 9 B' a- F  h# u0 z
CTLCOLOR_EDIT  编辑控件
. C( @' r# P, s  ]5 t; S& M7 \CTLCOLOR_LISTBOX  列表框 6 U6 S; Q7 R) e) b
CTLCOLOR_MSGBOX  消息框 5 e- X, |; b5 k; l
CTLCOLOR_SCROLLBAR 滚动条 2 m& @& [( M6 k2 w* o  T* p0 Z3 a
CTLCOLOR_STATIC 静态控件
: o' G/ k* q" k5 c9 `4 Y5 e" Q2 m表2 nCtlColor的类型值与含义( w* u; I+ s0 K4 b; S& l

6 _+ v' b8 K( V  B' T( Y  作为一个简单的例子,观察以下的代码:
! @* B9 K. o2 H
  1. ! J7 d6 I; v$ a& P: d/ [
  2. BOOL CUi5Dlg::OnInitDialog()
    ( Q0 v3 O6 Q2 N3 B
  3. {
    : M- x0 J: m3 v. H$ [. @
  4.         //…! |, S2 t0 z5 a7 B$ G3 y( r! c) ?
  5.         //创建字体
    % f6 n4 g) i5 ^
  6.         //CFont CUi1View::m_Font1, CUi1View::m_Font2
    6 L; z/ i/ h6 B* t$ d7 Q4 N" Y
  7.         m_Font1.CreatePointFont(120, "Impact");; T# G( Q% K" c$ W4 T
  8.         m_Font3.CreatePointFont(120, "Arial");0 l2 y' `# d% g6 h1 W0 r
  9.                 return TRUE;/ e" x+ E# f0 Q: F
  10.   // return TRUE  unless you set the focus to a control * \4 k1 M3 j7 K3 a4 _9 s9 e- ]
  11. }& |, X- j( B1 J$ m* C
  12. HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) & _# q& X. J% X
  13. {( N% h6 b( F& I
  14.         HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
      E( G! M* t( S7 b0 p
  15.         if(nCtlColor == CTLCOLOR_STATIC)9 t) }4 a( N! w
  16.         {* z9 e& b" [0 q) J7 z1 \4 r) \: d3 I
  17.                 //区分静态控件
    2 l) S8 F: c/ C' f9 }7 X; Z
  18.                 switch(pWnd->GetDlgCtrlID())
    * r8 N$ m3 n2 v! I
  19.                 {  J$ L( S$ \7 x  J6 A* H
  20.                         case IDC_STATIC1:
    / W- F9 ]1 F, O3 w
  21.                         {% }" d% ?6 K+ L- S: r+ B
  22.                                 pDC->SelectObject(&m_Font1);
    - }1 G5 _1 m" e1 J4 w; ?' }( f
  23.                                 pDC->SetTextColor(RGB(0, 0, 255));* I0 T3 H) i# w) d
  24.                                 break;0 m8 A2 [: g1 n- U9 n
  25.                         }7 v* n! x8 o1 e9 M2 [7 ~
  26.                         case IDC_STATIC2:
    & b" _; i5 t4 M, @2 u' z
  27.                         {
    ! c" t2 O; v# L: l. r% D! r
  28.                                 pDC->SelectObject(&m_Font2);
    ; V- T% `! a* z. N
  29.                                 pDC->SetTextColor(RGB(255, 0, 0));
    . d2 b) o, R5 z6 k9 `/ ?7 ?. K
  30.                                 break;
    - U2 {' H9 z! D) M  ?
  31.                         }
    ! g1 h1 n  N$ i7 O0 Z
  32.                 }
    ) c/ Y/ @& f& j. R9 o, S2 I
  33.         }
    + x! @1 x- ^$ x2 X8 ?
  34.         return hbr;3 M; R. z$ _# z) F4 @8 P% a" u) _# l
  35. }
复制代码

) r5 c* ~( @- k* y2 L   生成的界面如下:
. y% P! @- i4 r9 I4 ?, X) r
图7 利用WM_CTLCOLOR消息美化界面
5 A) Q& ^( l" ?9 J1 l
  3.3.4 WM_DRAWITEM $ d8 `! G* e5 o$ }: _' r& X: p
  OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。
, u% ~5 q  d& n  当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。 6 A+ ]# }. O0 i: n' H7 k5 V7 U6 J
  WM_DRAWITEM的映射函数原型如下: 1 W7 t5 w# x- g3 h# \3 Y) S) ?. w
  afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );, k/ X8 f' M4 C2 E  \
  参数: ) W2 Z: o% X, Q2 [0 j' [" h
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0
5 ?- x% f, a; F$ b  lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下: % z: L5 `! ^4 Q# E5 @6 w+ h

  1. ) |$ P* Q/ l" a% B% i3 m
  2. typedef struct tagDRAWITEMSTRUCT
    9 L" k8 [; M# k) y! w. y2 S8 J
  3. {
    1 I0 Y& }1 x( K" F9 U4 o% \
  4.     UINT   CtlType;6 T  H# `1 g/ P2 _( B; W
  5.      UINT   CtlID;
    2 f2 k- m. g. H6 t/ Z
  6.      UINT   itemID;; P; h( p. w  R! \3 o* m% i
  7.     UINT   itemAction;) W: I, x- W5 y: ^
  8.     UINT   itemState;9 @$ ]3 y, ?) a1 L3 l* }
  9.     HWND   hwndItem;8 y# _9 B% ?* Y6 u0 b0 \2 {
  10.     HDC    hDC;, Z3 D9 g* R5 C/ Q% N- C4 r  _# B
  11.     RECT   rcItem;
    $ h- i$ j, Q, q; H: z4 S, {$ C) ^3 z6 u
  12.     DWORD  itemData;
    5 N# Q! y  x: _" V
  13. }DRAWITEMSTRUCT;
复制代码

1 {3 e' d' ]* j9 L4 C" ^CtlType指定了控件的类型,其取值如表3所示:
1 V6 J7 Z: ^$ v& [类型值 含义
6 G- Q" t4 N  Q. k1 c+ cODT_BUTTON 按钮控件
6 J  r$ e& ^. y; j* R0 j3 n3 jODT_COMBOBOX 组合框控件 % A' y0 |# O' J) L+ T
ODT_LISTBOX 列表框控件
$ p, k. T/ z: o& T2 I4 \5 l& E" RODT_LISTVIEW 列表视图 8 v" C' D6 A  \1 I% F  K5 I
ODT_MENU 菜单项 5 h; U7 j! q$ D( j+ I4 N
ODT_STATIC 静态文本控件
8 H0 I8 X2 y) g% q# Q8 UODT_TAB Tab控件 0 R$ v& j( C! o# a1 e! g7 }' Z
表3 CtlType的类型值与含义: B  j2 \5 z. X/ {* n

/ h% I0 X4 d% S* r  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
. D' I& m& m, x! z( [  itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。
' B# n9 M- o% g/ U: z  itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:
/ s! v: y, h5 W. B9 O类型值 含义
/ }# J2 F' }" w& OODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。 . T& V& h- v. Q/ P, d
ODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。 : x" {: O2 a0 m, q6 {7 J4 ]
ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 ' I+ _& t/ {! E: p% `* |5 m
表4 itemAction的类型值与含义
2 E- |' [# \' E9 Z/ m& ~  itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:+ ?' m, U2 e" m
类型值 含义 # }9 P, z+ E& H  \9 s+ g" H
ODS_CHECKED 标记状态,仅适用于菜单项。 * x3 K  _8 @3 ?1 H6 r
ODS_DEFAULT 默认状态。 6 `. {  H% J6 B7 D! Q- B" U
ODS_DISABLED 禁止状态。 $ S" U% N' S5 q8 F3 I
ODS_FOCUS 焦点状态。
: q+ e7 e$ k2 g! H' y0 HODS_GRAYED 灰化状态,仅适用于菜单项。
: U1 R+ S# [" ?& |: [: PODS_SELECTED 选中状态。 0 g, P! o# i- d
ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。 ) `0 \+ C7 I( u
ODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。
- Q2 L" E, W5 b. w/ `% h) S* PODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。 / [. n2 A# f% B2 n  u% z. {) y* ]
ODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。 4 T. ~  l5 t/ `( K- X
ODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。 $ O4 [, X9 d8 V& x2 A
表5 itemState的类型值与含义3 ?) w8 l5 h3 g- q
  hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。
* e$ b' j/ @4 i$ g( I+ S! h1 Y' ~, M* r  hDC 指定了绘制操作所使用的设备环境。 ) u- j' T) B$ I3 ~" E7 X- Y
  rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。 ( t; T6 S5 d- {8 q
  itemData 4 F5 A+ C+ D/ J0 p/ j- J# }
  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
6 b0 A, J. N0 X& M! s. k  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 . @9 ^. E$ B  l4 T
  如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。
- E0 h8 O' c" X, ~  图5是个相应的例子,它修改了按钮的界面:
  [- g+ |. w7 c- O5 w6 I
图8 利用WM_DRAWITEM消息美化界面
  实现代码如下:
' T! ]# _  o$ s. o
  1. 7 Z9 c. a% O: |; g  D
  2. BOOL CUi6Dlg::OnInitDialog()2 I: _$ U: ^/ E/ G( B6 Y
  3. {
    3 g+ y* D4 e7 M. q
  4.         //…
    0 h6 B  c9 _' J& i- g: _
  5.         //创建字体
    $ o" J: u) h% s
  6.         //CFont CUi1View::m_Font% r: F- s- m  K
  7.         m_Font.CreatePointFont(120, "Impact");* L2 S) y' m& J& Z' N7 r
  8.         //…+ y+ \% U7 A4 j
  9. }
    7 p* |+ P) ?6 C7 @6 o9 C: l+ T! W
  10. void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) - z% D4 f! e- m  w: Q3 X
  11. {8 g* q4 J7 B3 N
  12.         if(nIDCtl == IDC_HELLO_CFAN)) M' H2 V( l% P5 I
  13.         {7 o# E" ~  t8 S! g, @* `; p
  14.                 //绘制按钮框架
    ; p; w  e7 B" A# n  B% y0 @. z
  15.                 UINT uStyle = DFCS_BUTTONPUSH;
      n0 x- t  B2 }3 Y
  16.                 //是否按下去了?
    6 q. X5 `- t' ~! J: m) Q' M; h
  17.                 if (lpDrawItemStruct->itemState & ODS_SELECTED)
    % ~# y: d$ D0 @. A! `) F2 k
  18.                         uStyle |= DFCS_PUSHED;
    * f- z  j. N, |, b+ D
  19.                 CDC dc;+ G* c( s& Q  K7 s8 C9 a
  20.                 dc.Attach(lpDrawItemStruct->hDC);
    % j, p( T3 q, I  {; }
  21.                 dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);
    % E7 m' b2 _8 ^) a% |( A+ W0 G  @
  22.                 //输出文字* `7 K" m  G! k# H- `  K$ J
  23.                 dc.SelectObject(&m_Font);
    * Z- d& l6 e! o- L1 A+ l; F0 W
  24.                 dc.SetTextColor(RGB(0, 0, 255));, x) x' L% ~2 z7 P4 ^
  25.                 dc.SetBkMode(TRANSPARENT);
    ( u  H' J) B: t; n: O5 @+ M  v( y
  26.                 CString sText;0 ~6 Q2 [4 ?# @3 {3 s$ Z0 C8 U4 g2 |
  27.                 m_HelloCFan.GetWindowText(sText);
    * Q/ Z6 Y7 a4 l5 A
  28.                 dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);
    ; t5 D, ~( j- C. {1 `7 D2 f! r& q
  29.                 //是否得到焦点
    % r" }! ^8 b$ N4 w" O0 m
  30.                 if(lpDrawItemStruct->itemState & ODS_FOCUS)
    + V: f& E+ D. R# a
  31.                 {
    : N* v" `6 @4 Q& A$ @
  32.                         //画虚框5 n+ ~; H5 x' _6 q5 b! _4 m
  33.                         CRect rtFocus = lpDrawItemStruct->rcItem; 8 d3 j3 x% c' G4 U8 P7 W' f
  34.                        rtFocus.DeflateRect(3, 3);3 {' v, J8 [, a9 Q' {* n/ H" `" z
  35.                         dc.DrawFocusRect(&rtFocus);: O3 O( p4 ?! a0 K! `* V$ J
  36.                 }
    ( l5 _& ]& l* n5 G: C
  37.                 return;+ a. `: ~  w" {
  38.         }
    : @# m2 Y& L  k! n( B6 |) t
  39.         CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);: _3 u' z6 ?1 }. r& o2 N
  40. }
复制代码

3 Z. G3 C) C5 B9 j8 f0 ]) [$ O- I  别忘了标记Owner draw属性:
6 a  O8 {2 s0 ]" Z5 X7 {9 d# n) U1 _
图9 指定按钮的Owner draw属性
  值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。
) g- w) u1 g$ S( c6 d; q- l* U+ `# K' x) A( }' f5 r( T% v
  3.3.5 WM_MEASUREITEM ! s9 P, f/ c3 A
  仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。 ! j* v+ f4 j$ M
  WM_DRAWITEM的映射函数原型如下: 3 B- u  P8 U- z% x
  afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct ); 5 ?2 }3 y) X$ {) r/ E2 R9 d
  nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l
+ [4 F) p0 U; [; g9 H6 n  pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下: / q( y9 g3 g) w. R; S: g6 ~
  1. ' p0 T' y% p0 _$ ]$ a& N7 k
  2. typedef struct tagMEASUREITEMSTRUCT5 J% n- U6 o) d. k4 |, t+ W
  3. {
    7 x; H. Z3 V9 j
  4.     UINT   CtlType;/ D5 G& d% u  m4 O$ s! x
  5.     UINT   CtlID;
    , [3 f; _5 i1 ^* t& W; A
  6.     UINT   itemID;
    , C4 `9 d6 \9 t( j
  7.     UINT   itemWidth;
    1 O. j# ]: O& Y& A  p! U1 R: L9 A
  8.     UINT   itemHeight;4 w4 C( D' ?+ R' K. j( \
  9.     DWORD  itemData;: g; y) O+ C( `
  10. } MEASUREITEMSTRUCT;
复制代码

8 V6 j  J' q: o& `" }5 W& B7 Q  CtlType指定了控件的类型,其取值如表6所示:7 R- P  i+ J9 L2 }! |
类型值 含义
3 m6 B7 }" m  Y  K9 \' b) KODT_COMBOBOX 组合框控件
* X. V3 i# T" \  x0 `ODT_LISTBOX 列表框控件 $ F9 t! I% V- w1 G0 V- H  _
ODT_MENU 菜单项 ! y) }. ^' A8 {% y0 b+ U
表6 CtlType的类型值与含义" Z7 Y1 y2 M0 m6 h& u4 @# j! u+ q6 M
  CtlID 指定自绘控件的ID值,该成员不适用于菜单项
7 U4 |0 \4 ~7 X: z  itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。
" \/ H& \/ O/ Y. e7 T1 y  itemWidth 指定菜单项的宽度 8 D- e8 n- j6 W; I+ G) H) a
  itemHeight指定菜单项或者列表框中某项的的高度,最大值为255
  A3 D  X! |2 I, z! j  itemData
: r, Z; G  ~) p4 ?4 Q  对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 ( y" S- T+ k! U) U6 q
  对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。
! @$ O: O8 b/ p+ P9 p3 X) h( P  图示出了OnMeasureItem的效果:
! X) I9 X' }0 T0 v; Z" F6 K& P. B
图10 利用WM_MEASUREITEM消息美化界面
  相应的OnMeasureItem()实现如下: 0 G4 \6 p; t+ _5 Q( Z# M* [' {. d

  1. / G3 }  x/ n* J! k3 J
  2. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
    3 c+ P% f( ~4 u% m
  3. {5 C' t4 Z3 x5 i2 y7 y% C! J, e
  4.         if(nIDCtl == IDC_COLOR_PICKER)
    ) X7 m7 F3 O( z! I. v) T
  5.         {! {: v/ i1 p& l  K# w' Y: w
  6.                 //设定高度为30
    ' n5 D0 Y: K+ Y, H. [; |+ G5 P6 z/ P& |
  7.                 lpMeasureItemStruct->itemHeight = 30;
    ' _7 k1 t. {1 F: n! |9 t! C3 L
  8.                 return;
    3 P; P( V( V: z& }3 G: I
  9.         }
    % ~: p" l- T8 x5 d* d+ W
  10.         CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
    $ O6 ~, i  [% E9 X) P+ V3 U
  11. }
复制代码
3 H( n3 d5 \/ T. S( t
  同样别忘了指定列表框的Owner draw属性: 7 Z0 y0 g1 \# @( K! g' M) m
  图11 指定下拉框的Owner draw属性  ' `) C8 c3 h3 j7 c  v, E
$ O( N' R9 S+ k6 p
  3.3.6 NM_CUSTOMDRAW
, `4 D+ S  P/ Q% _4 H( @- G  大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。 8 F3 X6 `; U  y5 E* J1 W/ Z
  可以反射NM_CUSTOMDRAW消息,如: 5 F* C! _; h8 R( i% d
  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw) ! }: j5 q- m# K& h, }9 Q- {
  afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult);
$ c, _3 I8 ^4 W1 N; m  参数:
8 C1 }" h6 D* B  pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下: 9 |/ P- H( U7 H) Y
  1. . G1 S! ]/ K* i6 k. {( K$ N4 N
  2. typedef struct tagNMHDR
    & ^- F* q/ f# r& Q; w, w6 ?
  3. {
    : |* k; L' L; ]* n( \/ j% }
  4.      HWND hwndFrom;# s- I+ n$ Z! K$ I' ?% T; Q2 y
  5.      UINT idFrom;
    1 F2 i( J. @+ k( Q' _* N( o* x
  6.      UINT code;
    ; ]5 o$ p# I5 z4 X2 d
  7. } NMHDR;
复制代码
6 }, q5 s+ k6 W! w
  其中:
1 W+ {" Z( |7 p  hwndFrom 发送方控件的窗口句柄
/ v. \1 z- f5 e* S! |8 S& J. E  idFrom 发送方控件的ID code 通知代码
4 ]/ }* u* X' L) w9 T3 O  对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下:
; v2 L; f" I+ z. H/ i3 a

  1. : c! `$ c! ?( Y2 `# N& a6 x! Z
  2. typedef struct tagNMCUSTOMDRAWINFO. T! L+ K4 E9 H/ j4 ^: L7 l
  3. {
    / c* }+ D% j- F' B# j
  4.     NMHDR  hdr;
    $ a* K7 q# A6 z( I' M9 \6 Y
  5.     DWORD  dwDrawStage;/ I& N2 }. X, Y  o# K
  6.     HDC    hdc;
    & S7 m/ N' V1 g6 c1 N
  7.     RECT   rc;
      c. Z( G- M4 \6 ^
  8.     DWORD  dwItemSpec;
    + v) Q6 t( F+ E; C, b0 O4 A6 ^
  9.     UINT   uItemState;
    ' J. u. L- p3 {  A2 P
  10.     LPARAM lItemlParam;+ Y* p, t/ N3 F6 h; i9 k% @  H2 U
  11. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码
; {! d+ f# T. d+ L( K
   hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:: ?0 k5 b* r: K) a, A
类型值 含义 ' H9 D2 S& C) g6 X$ ?( y
CDDS_POSTERASE 擦除循环结束
2 c  B- [  S% N# r1 |CDDS_POSTPAINT 绘制循环结束
; S' ~8 |( f7 d! ^/ o5 ZCDDS_PREERASE 准备开始擦除循环
7 E2 C% a) v, X" g6 i1 S  Y+ c# TCDDS_PREPAINT 准备开始绘制循环
2 f! U  w% k2 O; S- V# LCDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 " Z$ l: Z( Z: S/ p" d0 q5 m
CDDS_ITEMPOSTERASE 列表项擦除结束 5 j* K4 d1 }5 S' r  N. s
CDDS_ITEMPOSTPAINT 列表项绘制结束
. q' d, `" n3 u+ c! [1 e6 D7 wCDDS_ITEMPREERASE 准备开始列表项擦除
" O2 @" _$ Y+ y( W+ Q2 i# K% lCDDS_ITEMPREPAINT 准备开始列表项绘制 ; ?, ]& E; [) ~: m
CDDS_SUBITEM 指定列表子项
2 D9 [. Y) e% Q# @# Y表7 dwDrawStage的类型值与含义5 U  i7 l* r% }, r- a; ~
  hdc指定了绘制操作所使用的设备环境。 ( y9 n( L( J) q0 n
  rc指定了将被绘制的矩形区域。
4 z/ _, E2 B% o) {( W+ N: f  dwItemSpec 列表项的索引 / J# X6 n' i( |) H4 C9 _! ^
  uItemState 当前列表项的状态,其取值如表8所示:6 R* p0 m4 ]' s$ A! Y" u9 U0 ?
类型值 含义
, j8 e- ]8 J! a  k) A- X9 j3 qCDIS_CHECKED 标记状态。
# C' z6 d4 u# N4 Z3 k1 P# q7 _  ?CDIS_DEFAULT 默认状态。
5 L& t* V2 R# Y. R; w7 vCDIS_DISABLED 禁止状态。
5 Q) T  t' s0 c: D! k6 ]CDIS_FOCUS 焦点状态。
$ J+ k: o% M! N1 T/ ACDIS_GRAYED 灰化状态。 # r8 N$ d% [: E/ Q# G" a( i( v9 Z* z# Y
CDIS_SELECTED 选中状态。
  Q8 T6 g. f* b% i) h  D4 HCDIS_HOTLIGHT 热点状态。 - b0 l# \5 }4 Q- Y9 x) s5 e  Q! E
CDIS_INDETERMINATE 不定状态。 : \+ `, O2 O4 k5 K' s9 C
CDIS_MARKED 标注状态。
, s6 H: B/ o9 r6 v3 n& F8 r表8 uItemState的类型值与含义
7 }! H  h5 O; }- y  m( U  H, C  lItemlParam 当前列表项的绑定数据 5 D. E# m; Y* v; D6 r4 A
  pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage:
3 I# A$ n0 {8 u  当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:
- ^# K$ I8 \  \6 H! H' a5 y类型值 含义 ; ?; H3 v3 [+ H2 J( b2 A( }8 E
CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。
; P$ O5 Q: [  M* i6 yCDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。
$ O% S, Y1 A8 p$ X, |6 m  sCDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。
8 \" e( L0 R7 N2 k  cCDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。
1 J8 L6 O& N2 f8 H表9 pResult的类型值与含义(一)
" \; T( k& j( `* g5 K. u  当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:
! }; A0 v0 g7 e# S6 n类型值 含义 ; G+ X( P- p* P9 a5 I) y$ H* T
CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。 ) V# o3 F; j6 U  u+ i
CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
3 U1 C4 Q0 G: ]; R5 w( @* fCDRF_SKIPDEFAULT 系统不必再绘制该子项。# i  S4 Z! X3 H0 a7 R1 W' \
表10 pResult的类型值与含义(二)2 `& N+ j* @0 R7 ~
  以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子:
$ {0 |" h& A# G+ |
  图12 利用NM_CUSTOMDRAW消息美化界面 . p4 u/ t) }2 Z9 Q8 z
  对应代码如下: . l: P, H+ ?8 V- `; j; z
  1. 4 I8 T9 j, \  i# U* Y
  2. void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
    2 [0 \& A0 u+ a  y  ?4 {& X0 I
  3. {
    4 E7 ?: R7 C" X5 z
  4.         //类型安全转换
    8 M# u6 v" s+ s" v. y
  5.         NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);" H: a! p+ V% ?' ?7 X
  6.         *pResult = 0;: x0 k8 E5 _! t7 g# P
  7.         
    : X! M% n" S. I( Y" s2 {
  8.         //指定列表项绘制前后发送消息6 x. u6 g0 H0 D6 S9 p
  9.         if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)! q9 x6 V: q3 k: `8 A9 E" R' b
  10.         {
    + z0 K- `' H' X/ Z
  11.                 *pResult = CDRF_NOTIFYITEMDRAW;8 v0 |8 E9 x( D
  12.         }
    2 h# L/ t0 l6 M2 W
  13.         else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)- H4 A! M; P- D
  14.         {
    - S; I4 e6 w1 c, @8 e& W* i3 _
  15.                 //奇数行& R) R1 w  D- Z+ ]5 P; f* i, U
  16.                 if(pLVCD->nmcd.dwItemSpec % 2)
    6 z. D6 q0 O: W( i9 L/ h
  17.                         pLVCD->clrTextBk = RGB(255, 255, 128);
    " ?+ Y  d' R2 _3 V0 S: c+ s6 F
  18.                 //偶数行3 T1 H( H$ b$ k$ G/ w$ c
  19.                 else
    % e6 f8 _9 @7 r5 }- M# J1 b
  20.                         pLVCD->clrTextBk = RGB(128, 255, 255);
    + r* O8 }- S, Q1 U) H8 w8 b
  21.                 //继续. l+ u7 U1 V2 X0 o5 l* k# M1 l
  22.                 *pResult = CDRF_DODEFAULT;  Y. }/ w2 n, C' k2 p# N* e, g) U
  23.         }
    ' l- F7 P- X9 ]* J" }& V
  24. }
复制代码
/ v4 U# I" ^7 J6 h% z* Y
  注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。 " T8 ^9 m3 M( j. Y$ C

0 E/ g- S) E- ]+ \0 k0 {2 n% U- |  3.4 使用MFC类的虚函数机制
' t+ h! `6 ?: ?: s# o, J  修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子: / J1 i+ z3 w& G  e" Z1 R8 \

  1. + ^' W- l  G5 M: Y+ P$ l5 B$ B
  2. void CView::OnPaint()0 J5 p7 [! p% H4 L2 C$ D
  3. {
    , d) X+ ^" J; N0 X% N- _1 h
  4.         // standard paint routine/ L3 |: d, G/ ^4 [* f
  5.         CPaintDC dc(this);" Q' Y$ x7 U4 P, W
  6.         OnPrepareDC(&dc);8 Q$ B; M. m1 I2 V" N+ r
  7.         OnDraw(&dc);
    4 U4 p- f; l0 k% q: c' W
  8. }
复制代码

) h6 O0 _/ Z4 K  这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。 7 D! }. d4 S2 h& Q# e! a; t
  以下列出了与界面美化相关的虚函数,参数说明略去: " c. e! a4 ~8 l
CButton::DrawItem - X. ~& M0 m6 N5 @% Z
CCheckListBox::DrawItem
# N+ _2 n- o7 j* [0 w1 J; ACComboBox::DrawItem
- Q* c1 Z: T8 }! \: _CHeaderCtrl::DrawItem
  u8 x; e+ W7 z$ W7 \  H2 n& mCListBox::DrawItem   R/ b1 O) T; @8 Z; X% N) b& l: K
CMenu::DrawItem 4 N# M% W: L& t  A) t. C
CStatusBar::DrawItem
* V6 @4 C- D9 ?8 ~$ t4 `0 i, JCStatusBarCtrl::DrawItem
; h* U4 }* L* O; ZCTabCtrl::DrawItem
3 L9 e# T' L& `1 @$ X0 p* Zvirtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );
0 {( v2 u6 L  l  Q5 F- e; ^Owner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。* {+ G) t/ }+ X3 ~1 K
  限于篇幅,在此不再附以例程。
$ @/ ]' c1 p/ w! t8 J, t: y+ m9 J( {! S, A2 h
参考文献7 \0 f  G: {8 I% `
本文为白乔原创,曾经在《电脑爱好者》合订本上发表。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-10-2 00:11 , Processed in 0.020108 second(s), 14 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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