1. 美化界面之开题篇: a" {, {4 q8 N' \& v: T2 k
相信使用过《金山毒霸》、《瑞星杀毒》软件的读者应该还记得它们的精美界面: 图1 瑞星杀毒软件的精美界面 程序的功能如何如何强大是一回事,它的用户界面则是另一回事。千万不要忽视程序的用户界面,因为它是给用户最初最直接的印象,丑陋的界面、不友好的风格肯定会影响用户对软件程序的使用。8 {5 K2 h" C4 `3 \" \
“受之以鱼,不若授之以渔”,本教程并不会向你推荐《瑞星杀毒软件》精美界面的具体实现,而只是向你推荐一些常用的美化方法。 P) @/ @: g( |9 N s0 ^
; K6 e& G( ^# y$ k
2. 美化界面之基础篇. Z8 ^2 p. ?- g# }8 [
美化界面需要先熟悉Windows下的绘图操作,并明白Windows的幕后绘图操作,才能有的放矢,知道哪些可以使用,知道哪些可以避免……
; R' Q5 z2 `, c, N/ d1 O
/ R! G5 P2 C5 Q. E3 \$ K2 Q 2.1 Windows下的绘图操作& R6 q" U. `8 o3 {% b @! q3 n
熟悉DOS的读者可能就知道:DOS下面的图形操作很方便,进入图形模式,整个屏幕就是你的了,你希望在哪画个点,那个地方就会出现一个点,红的、或者黄的,随你的便。你也可以花点时间画个按钮,画个你自己的菜单,等等……
7 W K+ H* L+ B X' v2 q. W2 C Windows本身就是图形界面,所以Windows下面的绘图操作功能更丰富、简单。要了解Windows下的绘图操作,要实现Windows界面的美化,就必须了解MFC封装的设备环境类和图形对象类。
4 ?9 S- |# d& C+ v% [* m
$ f, b! K4 W1 Y: u1 a( e) ?# m 2.1.1 设备环境类
8 Y1 t: Z2 w: t% {6 q" |+ f2 m Windows下的绘图操作说到底就是DC操作。DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows的设备无关性。: j; [" \) s. z! W$ H( n
MFC的CDC类封装了Windows API 中大部分的画图函数。CDC的常见操作函数包括:+ P/ s& |) X! ~+ i4 ~
Drawing-Attribute Functions:绘图属性操作,如:设置透明模式& Q& F/ k( _! P( ?: }
Mapping Functions:映射操作
/ t+ D8 ~- G' ]" p8 ~! j Coordinate Functions:坐标操作) w6 [4 Z6 @( f1 I/ j
Clipping Functions:剪切操作
1 j1 I1 n# R k& N2 ~5 s* _# t8 j Line-Output Functions:画线操作
# a* J7 U% R3 a, @9 X: B& ? q) ?) E Simple Drawing Functions:简单绘图操作,如:绘制矩形框0 c0 `. r& q; e, a- b1 ^
Ellipse and Polygon Functions:椭圆/多边形操作
% O) }1 Y0 K' J0 q4 m. `% ~ Text Functions:文字输出操作, ~0 u6 c2 }5 k, z$ J
Printer Escape Functions:打印操作
8 g. t2 G5 y% W$ a/ m Scrolling Functions:滚动操作
* z+ m% s3 q# m/ | *Bitmap Functions:位图操作
+ Y1 z5 o! B8 c8 j( b- ~5 C# w *Region Functions:区域操作+ f5 @! V- [6 K$ X! n3 ]( G z
*Font Functions:字体操作
; ]4 j7 x( [/ i *Color and Color Palette Functions:颜色/调色板操作, K3 m) \2 t0 L, s% y
其中,标注*项会用到相应的图形对象类,参见2.1.2内容。, I4 ^& l3 {% G* U3 {' M" g, |$ o8 k
; S8 H! Q: G2 V- W5 B) c5 O. y. {5 L
2.1.2 图形对象类
' k3 X" w, c2 k5 b7 m' ?5 A7 v; t6 d 设备环境不足以包含绘图功能所需的所有绘图特征,除了设备环境外, Windows还有其他一些图形对象用来储存绘图特征。这些附加的功能包括从画线的宽度和颜色到画文本时所用的字体。图形对象类封装了所有六个图形对象。
3 F2 t7 u. A& B 下面的表格列出了MFC的图形对象类:
1 x0 h5 N+ q2 l. d+ j# a+ _ MFC类 图形对象句柄 图形对象目的
5 g0 K2 W K4 \# x# W1 H# j8 J CBitmap HBITMAP 内存中的位图
4 W S* _- R7 f8 Q! ?( K CBrush HBRUSH 画刷特性—填充某个图形时所使用的颜色和模式9 _" \2 H8 a( b
CFont HFONT 字体特性—写文本时所使用的字体- i) p8 F" E' ]" _, x
CPalette HPALETTE 调色板颜色/ l; O& C* Q. L/ A, D' b+ p% T
CPen HPEN 画笔特性—画轮廓时所使用的线的粗细) O: C; X( o+ |( _( p; G" N( ]6 `
CRgn HRGN 区域特性—包括定义它的点. [$ [. h. ~' b6 |
表1 图形对象类和它们封装的句柄2 T, m6 ^3 S' `) ` \# U9 Q
5 i; V5 ]/ S; i6 M! R) ^# A0 {7 F 使用CDC和图形对象类,在Windows里绘图还算是很简单的。观察以下的画面:
2 j& V7 |7 [2 F) \7 i( b" b图2 使用CDC绘制出的按钮 该画面通过以下代码自行绘制的假按钮:6 \' H- R+ _( w) A& G
- , N! R: ^$ n' }) A+ D: G f8 p
- BOOL CUi1View::PreCreateWindow(CREATESTRUCT& cs)
1 u9 d/ E" U; m& ~. r- J - {
+ X0 F" r7 t( ^1 @ D - //设置背景色- k: L: |1 H5 M! H2 a
- //CBrush CUi1View::m_Back
7 [, Z- b- V* s' K+ ?" ]1 J - m_Back.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));
; P! ?+ H9 S6 }" y% x - cs.lpszClass = AfxRegisterWndClass(0, 0, m_Back, NULL); 1 J6 H% ~0 w* Y' ^
- return CView::PreCreateWindow(cs);
0 u: B* E7 O) N$ ^ - }0 C& c1 p& b6 E! r
- int CUi1View::OnCreate(LPCREATESTRUCT lpCreateStruct)6 L5 N/ f& g+ E. |
- {
7 ]. \$ c+ N0 f9 l' w( ] - if (CView::OnCreate(lpCreateStruct) == -1): V6 k7 i$ M P/ q8 z# O
- return -1;7 {* [2 C+ f4 @/ r i6 g
- //创建字体/ f' W- m5 C( y% j c
- //CFont CUi1View::m_Font
+ O' g6 C8 t' C$ k - m_Font.CreatePointFont(120, "Impact");
5 t1 I8 ^: V. {) o* l4 M - return 0;7 r5 Y* J: @, y; S9 t" U
- }
, x5 `6 U* k, C0 F - void CUi1View::OnDraw(CDC* pDC)# v; X2 e0 ~9 W- W
- {
2 a9 ], y) k* D& a; M/ S) U - //绘制按钮框架
) L3 K! V% V v6 l4 D - pDC->DrawFrameControl(CRect(100, 100, 220, 160), DFC_BUTTON, DFCS_BUTTONPUSH); Y; _. V! J# @! E3 K2 x
- //输出文字7 ~! r& @, M" j" T* v. q
- pDC->SetBkMode(TRANSPARENT);6 g+ ?1 [1 @1 y# X9 F/ y* M: }
- pDC->TextOut(120, 120, "Hello, CFan!");. a) f. i7 O; m
- }
复制代码 ' \4 M" X. l) |# C
呵呵,不好意思,这并不是真的Windows按钮,它只是一个假的空框子,当用户在按钮上点击鼠标时,放心,什么事情都不会发生。
) W/ d7 Q7 [2 l* l' }. m7 k3 F+ ?( C4 m O
2.2 Windows的幕后绘图操作 0 ^" _$ g" ^4 K) M
在Window中,如果所有的界面操作都由用户代码来实现,那将是一个很浩大的工程。笔者曾经在DOS设计过窗口图形界面,代码上千行,但实现的界面还是很古板、难看,除了我那个对编程一窍不通的女友,没有一个人欣赏它L;而且,更要命的是,操作系统,包括别的应用程序并不认识你的界面元素,这才是真正悲哀的。认识这些界面的只有你的程序,图2中的按钮永远只是一个无用的框子。
2 s) E* n1 e# Q @' l7 T& ? 有了Windows,一切都好办了,Windows将诸如按钮、菜单、工具栏等等这些通用界面的绘制及动作都交给了系统,程序员就不用花心思再画那些按钮了,可以将更多的精力放在程序的功能实现方面。+ R+ ~& S4 G$ b2 P
所有的标准界面元素都被Windows封装好了。Windows知道怎么画你的菜单以及你的标注着“Hello, Cfan!”的按钮。当CFan某个快乐的小编(譬如:小飞)点击这个按钮的时候,Windows也明白按钮按下去的时候该有的模样,甚至,当这个友好的按钮获取焦点时,Windows也会不失时机地为它准备一个虚框……
% G- U- i- k) d, w* z 有利必有弊。你的不满这时候产生了:你既想使用Windows的True Button,可也嫌它的界面不够好看,譬如,你喜欢用蓝色的粗体表达你对CFan的无限情怀(正如图2那样)——人心不足,有办法吗?有的。! x' U# V& y. r( h9 E$ J$ ?( I
+ T% j1 V4 j {% X; q4 |
3. 美化界面之实现篇
/ e2 f7 q# E2 ~( L0 c' e7 O( O Windows还是给程序员留下了很多后门,通过一些途径还是可以美化界面的。本章节我们系统学习一下Windows界面美化的实现。
1 h; P. Y; F# n. C7 h4 p( b3 X( t/ }- f% G
3.1 美化界面的途径
2 L% s3 X' @( R% ~: o0 \. C 如何以合法的手段来达到美化界面的效果?一般美化界面的方法包括:
4 q0 n9 Y$ D( R! M# {7 J' }3 P1 i 1. 使用MFC类的既有函数,设定界面属性;" | l2 a, l2 T* |3 T4 ^6 g
2. 利用Windows的消息机制,截获有用的Windows的消息。通过MFC的消息映射(Message Mapping)和反射(Message Reflecting)机制,在Windows准备或者正在绘制该元素时,偷偷修改它的状态和行为,譬如:让按钮的边框为红色;
( c- z) ~ p: [ }. d- H 3. 利用MFC类的虚函数机制,重载有用的虚函数。在MFC框架调用该函数的时候,重新定义它的状态和行为;0 o4 }1 N* X7 {' b- W5 a4 [
一般来说,应用程序可以通过以下两种途径来实现以上的方法:' W2 Y8 y2 t5 a3 s, T8 f
1. 在父窗口里,截获自身的或者由子元素(包括控件和菜单等元素)传递的关于界面绘制的消息;0 F8 ? m- R. B9 k, t4 m) ]4 C: j [
2. 子类化子元素,或者为子元素准备一个新的类(一般来说该类必须继承于MFC封装的某个标准类,如:CButton)。在该子元素里,截获自身的或者从父窗口反射过来的关于界面绘制的消息。譬如:用户可以创建一个CXPButton类来实现具有XP风格的按钮,CXPButton继承于CButton。
' T. g: G% N' x7 m+ p3 Q7 x7 s( l 对于应用程序,使用CXPButton类的途径相对于对话框窗口和普通窗口分成两种:
1 b3 }% }% o# X8 m0 k6 K ① 对话框窗口中,直接将原先绑定按钮的CButton类替换成CXPButton类,或者在绑定变量时直接指定Control类型为CXPButton,如图3所示: ! R% y0 k6 d9 ?
图3 为按钮指定CXPButton类型 ②在普通窗口中,直接创建一个CXPButton类对象,然后在OnCreate()中调用CXPButton的Create方法;
" Z2 D7 U2 C, m8 y% Z$ w! [ 以下的章节将综合地使用以上的方法,请读者朋友留心观察。. A4 {) V7 |) q# T& g
I4 q! u+ v' y, Z! A 3.2 使用MFC类的既有函数
, J) M# j- v. P 在界面美化的专题中,MFC也并非一无是处。MFC类对于界面美化也做了部分的努力,以下是一些可以使用的,参数说明略去。- @* D2 w% H& A0 }8 R) n
! e) C3 a5 F" `% y
CWinApp::SetDialogBkColor
; ]8 G* H5 @ ^void SetDialogBkColor( COLORREF clrCtlBk = RGB(192, 192, 192), COLORREF clrCtlText = RGB(0, 0, 0) );
( s7 Y" A s3 }指定对话框的背景色和文本颜色。2 \: j% W! [" ]) S
( H2 M, Z; @+ _/ s. Q" Y
CListCtrl::SetBkColor- H8 F; T5 Y6 u! O, {; z4 X
CReBarCtrl::SetBkColor
9 b& `0 M% h. l% R2 Q8 nCStatusBarCtrl::SetBkColor! q6 `$ \9 K9 i" a& K
CTreeCtrl::SetBkColor
5 V+ k+ t% T; FCOLORREF SetBkColor( COLORREF clr );
* a& N' ?: k, b! _" ]设定背景色。5 w: _' Y. ^7 q( J6 m
( ]: Q2 Z; ?: d: l8 {) \+ JCListCtrl::SetTextColor
# U, q7 i' p$ u/ i. D" R" wCReBarCtrl::SetTextColor$ |5 B9 w+ {& ?0 t
CTreeCtrl::SetTextColor/ V/ x0 a6 j) @- q+ G! z
COLORREF SetTextColor( COLORREF clr );
7 L8 Y. h+ `* o5 j设定文本颜色。
) _' V& [' E) {. d& V; B' l. `, ]5 F0 M3 M% ?$ g% P# l0 _0 _
CListCtrl::SetBkImage
0 B' G' \% Z: Y6 dBOOL SetBkImage( LVBKIMAGE* plvbkImage );0 J6 C$ p0 x6 ?/ p. y1 A- Z
BOOL SetBkImage( HBITMAP hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);1 N5 L& ~1 D. W$ a: v/ B, }
BOOL SetBkImage( LPTSTR pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0 );
6 U: }* V* x* K" ~7 ~: d设定列表控件的背景图片。0 h9 ~, p7 l- P- W% N7 p) u$ S
6 e& f: [9 @. ^4 eCComboBoxEx::SetExtendedStyle
* d( x, n5 i9 x* J. k3 p5 pCListCtrl::SetExtendedStyle0 p; m& |6 o) z6 o2 s
CTabCtrl::SetExtendedStyle
6 n; F& K5 A! J% `7 l& G9 `CToolBarCtrl::SetExtendedStyle : K; ~ h' F5 F% `" u
DWORD SetExtendedStyle( DWORD dwExMask, DWORD dwExStyles ); . h; O9 C4 }: K4 V
设置控件的扩展属性,例如:设置列表控件属性带有表格线。
% I9 j ~9 f* Q8 J% V6 g+ f 图4是个简单应用MFC类的既有函数来改善Windows界面的例子:
8 B9 a, K4 w$ P6 \8 [: i+ L图4 使用MFC类的既有函数美化界面 相关实现代码如下:& Z) i: y8 w2 _5 L
-
8 }* o' ]5 t( G2 S, T2 P - BOOL CUi2App::InitInstance()
; \- ]0 [) B4 d6 m. C7 {" T - {; z, x3 \# c" j. J( F' B
- //…
8 O3 z N$ H9 Y" ~! z% ] - //设置对话框背景色和字体颜色
5 A1 t5 l6 l% z) \ I+ l8 x3 V9 ~7 F - SetDialogBkColor(RGB(128, 192, 255), RGB(0, 0, 255));& ^ z0 m' @; o. U9 h1 b9 t
- //…
* m% W0 s0 T) T* K0 G9 Y - }1 L' q5 j8 r& w. }& W
- BOOL CUi2Dlg::OnInitDialog()" e: V- F* X3 w8 P; V/ ~
- {, B0 L b& `9 h( }4 g
- //…
4 [- n) t: L3 Z% J - //设置列表控件属性带有表格线
' ^9 D! d. c8 k7 e. p1 R - DWORD NewStyle = m_List.GetExtendedStyle();
0 m, l$ ^! P: O5 G8 a - NewStyle |= LVS_EX_GRIDLINES;
! V1 {+ ?+ P' v5 _9 Z# l - m_List.SetExtendedStyle(NewStyle);
& ]3 {& a8 Z) `0 R - //设置列表控件字体颜色为红色/ b- c8 k- K4 y1 ^& ] B2 ]
- m_List.SetTextColor(RGB(255, 0, 0));2 |( y. m" A" L! b; k$ U* g
- //填充数据
+ S7 u6 ]0 i- m3 z$ c - m_List.InsertColumn(0, "QQ", LVCFMT_LEFT, 100);" V9 s8 \2 X$ S- K' k$ ]+ O
- m_List.InsertColumn(1, "昵称", LVCFMT_LEFT, 100);
! q( h3 @+ S; ^( v# o. Q( Q% L - m_List.InsertItem(0, "5854165"); Y) h9 N" J( l& Y( b2 x! S
- m_List.SetItemText(0, 1, "白乔");
6 A9 }) H3 J: T - m_List.InsertItem(1, "6823864");5 s; P) y O5 y# i4 `5 Y) n- i
- m_List.SetItemText(1, 1, "Satan");
3 M; g8 K9 h8 ] - //…
) h1 Q. Y" r0 |% U* C3 h - }
复制代码 / ]. O; w5 x4 c
嗯,这样的界面还算不错吧? 7 m" M1 G9 G# U( ?
* w" `8 f# `7 q2 W0 ], F 3.3 使用Windows的消息机制! V" D K2 ]" S' d
使用MFC类的既有函数来美化界面,其功能是有限的。既然Windows是通过消息机制进行通讯的,那么我们就可以通过截获一些有用的消息来美化我们的界面,以下是一些有用的Windows消息:
4 ]" l1 K$ c% mWM_PAINT - l+ K/ G M+ H& U6 Y4 s8 }0 _
WM_ERASEBKGND 5 W+ e! g- @% w/ B4 w" t
WM_CTLCOLOR*
% u/ U9 q, K* |$ aWM_DRAWITEM* ' B) H8 ~2 n+ Q$ B: l9 K
WM_MEASUREITEM*
& t1 O) z& Q* `/ T ONM_CUSTOMDRAW* $ Z. c0 {: D/ O2 L q; Y( o
注意,标注*的消息是子元素发送给父窗口的通知消息,其它的为窗口或者子元素自身的消息。; h, B c1 N7 d5 ]! r; ?
' _, t+ C, ~6 d* } 3.3.1 WM_PAINT/ s( C) t: D5 ~
WM_PAINT消息相信大家都很熟悉,一个窗口要重绘了,就会有一个WM_PAINT消息发送给窗口。0 ?) o* c9 {/ Z
可以响应窗口的WM_PAINT,以更改它们的模样。WM_PAINT的映射函数原型如下: U+ D l2 c5 z; K3 q6 E: J5 \: A
afx_msg void OnPaint();
# f2 \, I' M8 [5 h! j! z 控件也是窗口,所以控件也有WM_PAINT消息,通过消息映射我们完全可以定义控件的界面。如图5所示: + \1 k) U% l$ G( I' }; v
图5 利用WM_ PAINT消息美化界面 实现代码也很简单: . j4 ?. l; {) b0 E: v/ M6 x
-
$ s8 l: ^# w# m2 V# j - void CLazyStatic::OnPaint()
, {' V, u; O2 J: O8 L' E) G - {8 u9 l/ ~' q; v3 k* a
- CPaintDC dc(this); // device context for painting3 J3 l* f \' J" ]
-
7 H, K# o9 I% k1 _1 h; j& c - //什么都不输出,仅仅画一个矩形框
8 w N$ J0 I& `. a - CRect rc;4 p/ A# {! S/ F6 S; y% ~+ a" z( T' y
- GetClientRect(&rc);3 ]+ A4 \0 S# n, N' w+ B; J: @3 T
- dc.Rectangle(rc);
/ g" ]/ L" T, U9 X - }
复制代码 z% Q& W( o; @% ^' N0 y
哈哈,简单吧?不过WM_PAINT确实绝了点,它要求应用程序完成元素界面的所有绘制过程,想象一下如何画出一个完整的列表控件?太烦了吧。一般来说,很少有人喜欢使用WM_PAINT,还有其它更细致的消息。# r% i) q- N3 T l) o# x
7 z& d- p" X% v+ ^ J' I+ _ 3.3.2 WM_ERASEBKGND $ X% `9 Y8 O0 p) F. I/ y
Windows在向窗口发送WM_PAINT消息之前,总会发送一个WM_ERASEBKGND消息通知该窗口擦除背景,默认情况下,Windows将以窗口的背景色清除该窗口。 4 _" c5 L8 c9 q4 h! w6 k! E! @6 P
可以响应窗口(包括子元素)的WM_ERASEBKGND,以更改它们的背景。WM_ERASEBKGND的映射函数原型如下:& i) ?' w+ R0 F+ t# O7 k
afx_msg BOOL OnEraseBkgnd( CDC* pDC );
4 Z4 e3 }! {# r# Q1 [ 返回值:
& s& V/ P; H5 v- M4 Z* V 指定背景是否已清除,如果为FALSE,系统将自动清除
6 |+ L# ]& n! i( b 参数: pDC指定了绘制操作所使用的设备环境。 2 P8 P7 k9 F6 \1 }$ i. J
图6是个简单的例子,通过OnEraseBkgnd为对话框加载了一副位图背景: + E1 i7 ?4 Z- _, {& v6 o, V
图6 利用WM_ ERASEBKGND消息美化界面 实现代码也很简单:
! e) Z* Z7 N: ^5 c/ }$ Q-
. Y9 u: \) K3 n4 _$ D) {& j - BOOL CUi4Dlg::OnInitDialog()- z; F; h& ?$ u+ L6 n
- { r) c6 |5 P. P j' o: R* J9 B. |
- //…6 z [( t! z" Y E. {
- //加载位图
( \ W! t4 I/ b* y+ F) q" p& _ - //CBitmap m_Back;
6 T, ] U4 h; X# Z. k" u - m_Back.LoadBitmap(IDB_BACK);, _! {) p& {7 F L0 R$ ?/ ?
- //…1 E) N4 O" ]2 Y6 C
- }' ]- r' s) P( ] p
- BOOL CUi4Dlg::OnEraseBkgnd(CDC* pDC) + T& s Y: S: i1 Q- N
- {
2 w( S0 G9 n% ^0 e$ M - CDC dc;2 H: \! J, J! @2 S* s2 [9 _5 }) j$ P
- dc.CreateCompatibleDC(pDC);; ^5 g1 D: e+ a6 ]/ [. A) f4 _
- dc.SelectObject(&m_Back);3 l, g% }9 N7 a
- //获取BITMAP对象 V$ i, J7 ~ v5 |( {" D' q- ?
- BITMAP hb;
6 I3 F( \" m- @1 _7 W) V - m_Back.GetBitmap(&hb);
- t" M5 q( a4 ]" s - //获取窗口大小+ G- M9 c2 D, U
- CRect rt;8 ^9 j. g6 U# Z0 k* H" c9 ^8 E2 d
- GetClientRect(&rt);/ q1 T3 @: s+ D3 X* s. R8 l
- //显示位图
8 \' W, N6 e% _5 H* h - pDC->StretchBlt(0, 0, rt.Width(), rt.Height(),; O3 b" ` K9 b5 }1 ~" c6 ^. F7 l
- &dc, 0, 0, hb.bmWidth, hb.bmHeight, SRCCOPY);/ I0 [6 V/ D6 Y% |! o% \
- return TRUE;
$ z, l9 V7 x. ]5 o' k } - }' n" A: Y l4 @ O$ I. o( A
- HBRUSH CUi4Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) & P/ t7 x% y" X
- {
/ u, g+ U/ b0 f$ H - //设置透明背景模式' o4 {' z8 I. |7 V
- pDC->SetBkMode(TRANSPARENT);
* Q. m4 G$ e3 Z- T - //设置背景刷子为空
: F" I5 T# s* S4 a& m - return (HBRUSH)::GetStockObject(HOLLOW_BRUSH);
- g U; s8 l6 r5 D& ^ - }
复制代码 & q; C+ h, `' S. H' y& l
同时别忘了响应OnCtlColor,否则窗口里面的控件就不透明了。OnCtlColor的内容,详见3.3.3章节。 # e; l* h% c+ _4 f; |
5 T8 F/ x4 e! O2 C5 V! Q 3.3.3 WM_CTLCOLOR
3 C# ?7 {( l' V$ U. ` { 在控件显示之前,每一个控件都会向父对话框发送一个WM_CTLCOLOR消息要求获取绘制所需要的颜色。WM_CTLCOLOR消息缺省处理函数CWnd::OnCtlColor返回一个HBRUSH类型的句柄,这样,就可以设置前景和背景文本颜色,并为控件或者对话框的非文本区域选定一个刷子。 % f" Z3 z- k, J" C2 i0 D
WM_CTLCOLOR的映射函数原型如下: * Y! w- S b, d' s1 M
afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );+ B F0 b& a5 h# z( r1 m; C+ j
返回值: 用以指定背景的刷子 5 p2 O. H5 O9 @
参数: % u+ @6 d% a# |8 W4 [2 D
pDC指定了绘制操作所使用的设备环境。 : G* \% Y: t9 G @5 F* L( a- A
pWnd 控件指针 * x/ n9 `' z' ~
nCtlColor 指定控件类型,其取值如表2所示:
+ V' y; e: ?# I: T 类型值 含义
8 H+ E j4 I7 m0 X# p6 r3 b4 PCTLCOLOR_BTN 按钮控件 6 H( H3 x: I9 k$ u4 r/ Y
CTLCOLOR_DLG 对话框 , D. t, u* m; O1 |# @
CTLCOLOR_EDIT 编辑控件
3 I. O6 k8 D4 x" L7 E, GCTLCOLOR_LISTBOX 列表框 9 {8 x! @; e# {. w; l
CTLCOLOR_MSGBOX 消息框
/ d6 ]% A8 J" q- F' T) o7 T1 ACTLCOLOR_SCROLLBAR 滚动条
! z6 p; N% o, B/ ~CTLCOLOR_STATIC 静态控件
6 c/ t' n, M: h% A. C V: R$ x! }表2 nCtlColor的类型值与含义. A4 u+ R$ P3 W& Z6 e% y0 n5 j
% Q$ b* f) @, g, P" R( [
作为一个简单的例子,观察以下的代码:
9 Y8 Y* |& d `' v- # x' \+ R9 W" x. [
- BOOL CUi5Dlg::OnInitDialog()* G2 @3 o, Q N8 L5 g% l( v2 \. V8 O
- {- B* Z! S) ^+ M+ u
- //…
& K+ y, u) ~( v$ Z" N& @ - //创建字体
+ A# s# I' W/ \ ? - //CFont CUi1View::m_Font1, CUi1View::m_Font2( |2 w8 w( i5 X, l/ G. M7 J1 G1 G
- m_Font1.CreatePointFont(120, "Impact");
- n2 b3 j0 U7 p( q - m_Font3.CreatePointFont(120, "Arial");
7 ~/ t* a% s: F, d5 A7 ~ - return TRUE; b% t0 \! ^! ~5 W
- // return TRUE unless you set the focus to a control 0 N# }4 Q, }5 I6 x
- }
( b x* ^! F. a9 ~# C' d6 g! j - HBRUSH CUi5Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
$ n6 r% j% R+ b! i; E, | - {) K* ?( r& {+ ^* p
- HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
. I4 U) _( v5 O# n4 L0 P+ r - if(nCtlColor == CTLCOLOR_STATIC)# h. {+ N& C3 C" u
- {2 P% r2 _" W' W+ t h% b) z
- //区分静态控件
# J" C7 D5 _' z9 M - switch(pWnd->GetDlgCtrlID())
; u* Q" q6 w7 }3 Y - {- j( l9 s: C1 f2 d4 E" M2 G. s
- case IDC_STATIC1:
! m6 G6 H( F3 w' W - {1 p! [' J8 C5 h
- pDC->SelectObject(&m_Font1);
: D1 l$ U" Y3 d* F' ]8 s& X/ X - pDC->SetTextColor(RGB(0, 0, 255));
_' P# |& p7 s; w. ]5 L* O3 v0 C1 r - break;- I; i- R: q* [" A q2 _
- }3 ~( u4 Z7 L R# [
- case IDC_STATIC2:
x# [& c; }' S: |' L& L8 E2 D) h - {
/ O* x" K+ `% i0 a' }' R# o - pDC->SelectObject(&m_Font2);1 D: F& I) J! b/ `4 z9 W4 ?
- pDC->SetTextColor(RGB(255, 0, 0));
, c7 G0 i, }4 g7 |- @" y - break;# H9 N) L/ A2 ~1 b0 }6 |
- }, x" z' V; q5 O& Z
- }( a( ], _; D/ a8 I+ v" Y* B4 }
- }6 I3 e: s. j" z" M" ^0 Q* \
- return hbr;+ V4 j0 n) n* G8 Z/ y* K
- }
复制代码
# I9 T& N; W9 o* N# ] 生成的界面如下:
: @) a# o$ i |. z/ m- r图7 利用WM_CTLCOLOR消息美化界面
" u. t1 U1 ^8 N; u. S+ ~ 3.3.4 WM_DRAWITEM
2 [' {# Q9 ~! X OnCtlColor只能修改元素的颜色,但不能修改元素的界面框架,WM_DRAWITEM则可以。
" Z7 V. ^7 e3 D8 K0 I 当一个具有Owner draw风格的元素(包括按钮、组合框、列表框和菜单等)需要显示外观时,该元素会发送一条WM_DRAWITEM消息至它的隶属窗口(Owner)。 ' x# Z, \' k/ h. }) r* m
WM_DRAWITEM的映射函数原型如下: 1 T# ^& {: H8 m+ a( _0 ~1 U
afx_msg void OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct );' c* r1 @/ \) b7 _6 Z) i0 g$ G
参数: 8 n5 g, F V' m' Z
nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0
% l8 o1 c/ Y2 r! ] lpDrawItemStruct 指向DRAWITEMSTRUCT结构对象的指针,DRAWITEMSTRUCT的结构定义如下:
4 k. Y5 F9 }+ N- : [. w$ o0 {9 Z$ h) `# W
- typedef struct tagDRAWITEMSTRUCT
4 q$ X, e6 f; ]0 b+ F( o0 q2 L, ` - {* l" x% ^4 U9 t6 }. {
- UINT CtlType;) G) E& ^/ I) s; X9 x
- UINT CtlID;
+ q" g8 |7 B4 R d. [- n - UINT itemID;
' ]. d3 m U, M, G% u% `* v) H - UINT itemAction;
* |4 p, F; b5 ^& ~ - UINT itemState;) V" F5 B* E$ d
- HWND hwndItem;6 v. j2 N0 N( H$ b! n: ?6 o& d, b
- HDC hDC;, Z9 g1 |1 s, a5 ^9 L$ N
- RECT rcItem;
$ q- g# p: i% K+ _$ q - DWORD itemData;
4 ~' P) o# t2 }& O+ R - }DRAWITEMSTRUCT;
复制代码
3 z$ a" l3 {/ R0 W' MCtlType指定了控件的类型,其取值如表3所示: ; u1 b5 T# w, U* k; v6 F
类型值 含义
0 u' X' |! o6 l3 m1 o5 K5 F! }- g( JODT_BUTTON 按钮控件 % m# p. ~7 N# Z+ O# L7 ~
ODT_COMBOBOX 组合框控件 " ^4 g& X0 k6 e1 y/ W
ODT_LISTBOX 列表框控件
% c6 {6 } @4 {( |- w1 P% \ODT_LISTVIEW 列表视图
9 t- Z% ?: f _# V/ YODT_MENU 菜单项
& W$ U+ Z0 M! K9 g7 S2 TODT_STATIC 静态文本控件
! j D- R& i R1 _ODT_TAB Tab控件
1 a) c: n6 r8 _2 k8 L表3 CtlType的类型值与含义
9 _- Z9 f) |) t9 q8 L3 V! N) ^9 C7 B7 x: A
CtlID 指定自绘控件的ID值,该成员不适用于菜单项
7 B D' C- r& S2 j itemID表示菜单项ID,也可以表示列表框或者组合框中某项的索引值。对于一个空的列表框或组合框,该成员的值为?C1。这时应用程序只绘制焦点矩形(该矩形的坐标由rcItem 成员给出)虽然此时控件中没有需要显示的项,但是绘制焦点矩形还是很有必要的,因为这样做能够提示用户该控件是否具有输入焦点。当然也可以设置itemAction 成员为合适值,使得无需绘制焦点。
8 M) p# H2 f( ?( C# C! ?& t itemAction 指定绘制行为,其取值为表4中所示值的一个或者多个的联合:
) R# ^" t1 A, L# l类型值 含义
. V# ^. l! F$ y/ k8 Z, R4 L5 j, \ODA_DRAWENTIRE 当整个控件都需要被绘制时,设置该值。
; O r- C& i' l3 f5 N/ QODA_FOCUS 如果控件需要在获得或失去焦点时被绘制,则设置该值。此时应该检查itemState成员,以确定控件是否具有输入焦点。 1 r1 N5 W; I! M' @- r9 d4 ^$ a1 j- I( T
ODA_SELECT 如果控件需要在选中状态改变时被绘制,则设置该值。此时应该检查itemState 成员,以确定控件是否处于选中状态。 ' z$ Y# I; R. I# f' N
表4 itemAction的类型值与含义
# \. i5 |0 L6 {1 B& h% e P8 w itemState 指定了当前绘制项的状态。例如,如果菜单项应该被灰色显示,则可以指定ODS_GRAYED状态标志。其取值为表5中所示值的一个或者多个的联合:% `$ i& M$ q, c# @. L; J
类型值 含义
0 y; Z$ e8 h1 z" YODS_CHECKED 标记状态,仅适用于菜单项。
, O& B+ M; i, ? \! pODS_DEFAULT 默认状态。
+ A* N7 M. e/ C8 Q8 sODS_DISABLED 禁止状态。
! J, K6 i2 C. U: ^$ ?* R3 LODS_FOCUS 焦点状态。
1 W" E/ \% q R; uODS_GRAYED 灰化状态,仅适用于菜单项。 ! `5 l* H, I* p. M& c
ODS_SELECTED 选中状态。 4 \+ |7 ` {' a N
ODS_HOTLIGHT 仅适用于Windows 98/Me/Windows 2000/XP,热点状态:如果鼠标指针位于控件之上,则设置该值,这时控件会显示高亮颜色。
! {5 X" } T9 E# P* u& t+ Q, cODS_INACTIVE 仅适用于Windows 98/Me/Windows 2000/XP,非激活状态。
+ O4 T, f6 \) FODS_NOACCEL 仅适用于Windows 2000/XP,控件是否有快速键。
I; w/ b: U! xODS_COMBOBOXEDIT 在自绘组合框控件中只绘制选择区域。
' S! P e8 U* l& w x; R" AODS_NOFOCUSRECT 仅适用于Windows 2000/XP,不绘制捕获焦点的效果。
. p* K9 l3 Q. z% E; y* q表5 itemState的类型值与含义
$ ]/ X% Y2 [) P( q, m& L7 v" O: B3 Y2 G hwndItem 指定了组合框、列表框和按钮等自绘控件的窗口句柄;如果自绘的对象为菜单项,则表示包含该菜单项的菜单句柄。
`# b% J; Y6 j" Z0 w' G9 o6 X hDC 指定了绘制操作所使用的设备环境。 $ v- w2 N9 e/ t; ?3 e1 O( K
rcItem 指定了将被绘制的矩形区域。这个矩形区域就是上面hDC的作用范围。系统会自动裁剪组合框、列表框或按钮等控件的自绘制区域以外的部分。也就是说rcItem中的坐标点(0,0)指的就是控件的左上角。但是系统不裁剪菜单项,所以在绘制菜单项的时候,必须先通过一定的换算得到该菜单项的位置,以保证绘制操作在我们希望的区域中进行。
$ F- M; P. ~$ x; F itemData 8 n/ O- F/ N% O* W6 J
对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。 ; A2 s/ k/ t# h
对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 - o# q( h$ j6 y/ i* M! H% v2 O
如果ctlType 的取值是ODT_BUTTON或者ODT_STATIC,itemData的取值为0。
- n% L* K k0 N( U1 ^6 E* _+ K 图5是个相应的例子,它修改了按钮的界面:
, ~+ V3 k/ y7 p& o s图8 利用WM_DRAWITEM消息美化界面 实现代码如下:
( v/ n# m/ y, @6 w& N1 Q+ T( u+ D-
" }! u9 B, i( l - BOOL CUi6Dlg::OnInitDialog()+ }' i. T3 L% [4 N9 m6 a& V4 B
- {
; U3 n% z- w& h' w - //…
3 b; z) ?' m8 T o" ?( Q - //创建字体' |, P1 z: M) _, K" I Y/ u
- //CFont CUi1View::m_Font/ h, n; c+ s5 L' G) q
- m_Font.CreatePointFont(120, "Impact");, Y$ N5 |, S$ q2 U
- //…
) G5 C: M1 Y; _ l/ ~6 A5 J - }
, R0 w6 w1 v. f0 ]& @ - void CUi6Dlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
, t4 K. N2 p0 B5 n7 j - {2 V8 z' Y8 N* t$ H& ?4 m
- if(nIDCtl == IDC_HELLO_CFAN)
$ p/ Y0 o) \5 @. J5 Z/ T2 _: ` - {
5 J) h; h2 g& W; `: } - //绘制按钮框架
9 _( k5 p* A7 s; N* { - UINT uStyle = DFCS_BUTTONPUSH;6 L' V; }- f) U% {. A7 i, O
- //是否按下去了?& w8 {0 t$ H+ x9 [* X
- if (lpDrawItemStruct->itemState & ODS_SELECTED)! Z5 i4 J9 H* `6 h, h
- uStyle |= DFCS_PUSHED;9 Y+ {& N+ c0 \. R5 U/ q! Q& j
- CDC dc;
6 H2 f3 L" N" N* d - dc.Attach(lpDrawItemStruct->hDC);, j& N, C( q E! a
- dc.DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle); X! Q- b9 r) `& l0 U8 V: x6 o O
- //输出文字
) T" I% q! c' E3 n* I2 x - dc.SelectObject(&m_Font);* i8 P/ h9 M0 ^5 B: g- n" l; d
- dc.SetTextColor(RGB(0, 0, 255));
9 I( X0 W) g+ } - dc.SetBkMode(TRANSPARENT);0 V3 c$ u3 t U/ X
- CString sText;7 @, t8 V E' B# J6 ?2 V
- m_HelloCFan.GetWindowText(sText);0 c+ @& |/ _$ s$ @# c* O" A
- dc.TextOut(lpDrawItemStruct->rcItem.left + 20, lpDrawItemStruct->rcItem.top + 20, sText);. ~" c9 P4 b" {% O* Y5 U. v; V
- //是否得到焦点
4 [" f3 q' h" {- Q4 ~ - if(lpDrawItemStruct->itemState & ODS_FOCUS). z+ w5 K* ~+ T; C/ c# @: P
- {( i; m: E6 r2 ~* i! J/ c+ b; |3 L
- //画虚框% s* l" W; t+ v( }1 Z* M) j
- CRect rtFocus = lpDrawItemStruct->rcItem; 5 q( u- D, U0 F1 f0 w' \" d
- rtFocus.DeflateRect(3, 3);: w$ E7 N4 q" ?3 J% ~
- dc.DrawFocusRect(&rtFocus);$ O: a% y+ x# |* K* A5 I0 c
- }) m; i- z( k8 Y! i7 @1 |3 v% l& y
- return;4 r# @/ f: G1 P7 D6 \+ V+ \! n# j
- }
; h9 i( k* ~0 f4 g) i1 A* e5 N - CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
2 z- h6 |8 e7 A5 t% q9 u - }
复制代码
# q* I( e9 ]/ p 别忘了标记Owner draw属性: 2 X& ?8 g& S$ B- D/ X: a6 }
图9 指定按钮的Owner draw属性 值得一提的是,CWnd内部截获了WM_DRAWITEM、WM_MEASUREITEM等消息,并映射成子元素的相应虚函数的调用,如CButton::DrawItem()。所以,以上例子也可以通过派生出一个CButton的派生类,并重载该类的DrawItem()函数来实现。使用虚函数机制实现界面美化参见3.4章节。 8 ^8 T3 q5 I' i% m0 c" m
, u7 t9 y# ]3 L. H0 L 3.3.5 WM_MEASUREITEM * M& n5 g N; L- T
仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。
; A4 } s( _+ m; t+ c WM_DRAWITEM的映射函数原型如下: 2 k L# L$ v- ^1 `5 R8 Z
afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct ); - f" d4 ~. a' m* f( G) O
nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0 l
) {5 j' |7 F9 d0 @# o( Y pMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下: ) K) @% r4 N/ u# [
-
/ Z0 w4 t; L* M6 d - typedef struct tagMEASUREITEMSTRUCT" J. u8 p4 \& x' {7 j8 t; y* }
- {4 N5 s& y) f/ a
- UINT CtlType;8 ^, w. k. |1 f2 O8 w! `2 T" C& P
- UINT CtlID;) X+ [1 z6 H( K9 B. H4 r9 v
- UINT itemID;+ h( a4 ]- V2 x1 o) S
- UINT itemWidth;
6 a+ j5 ?! B2 V C" B9 T! O7 d - UINT itemHeight;
5 P4 w; A; X {1 q$ Z5 _ - DWORD itemData;
2 r q' k; ?! T6 U+ X! u1 [ - } MEASUREITEMSTRUCT;
复制代码
. ~3 C, d2 z% U" x CtlType指定了控件的类型,其取值如表6所示:
/ ?- a- u# Q9 L+ w$ a类型值 含义
* E1 e0 d2 m1 ^ODT_COMBOBOX 组合框控件 b7 ?$ P: }8 Y+ V2 s- I4 ^ @7 v
ODT_LISTBOX 列表框控件 % ~. ?% K6 { W' g9 O$ v, \
ODT_MENU 菜单项
8 K4 A! o* S8 S u- G( o; ?表6 CtlType的类型值与含义
' G5 {0 P1 D/ N/ @/ b CtlID 指定自绘控件的ID值,该成员不适用于菜单项
: k* ?, J3 z5 n# X) X4 D. [( N itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。
% z j8 b) D' J+ n+ I# t itemWidth 指定菜单项的宽度
% s. B* |4 p3 c/ P' a itemHeight指定菜单项或者列表框中某项的的高度,最大值为255 6 F+ f* \; O6 Y' H' D% U
itemData
8 e6 t6 ^+ ]2 i 对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。
- U# v; r( f+ y6 K 对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。 $ \1 K* G) D( I1 o# P6 X
图示出了OnMeasureItem的效果: - P' y' w6 s5 d& E2 L% c. A
图10 利用WM_MEASUREITEM消息美化界面 相应的OnMeasureItem()实现如下: 7 |1 |: n1 X. g4 y& ?$ b
- 8 H8 l) c- }2 M. Z: U
- void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) 5 N/ D* j+ O' T6 A G
- {5 o0 Q/ b7 K1 M/ T6 l
- if(nIDCtl == IDC_COLOR_PICKER)
; o/ h5 ?$ `" z. v - {( Z1 J: i! M3 \6 ]; v! `; n
- //设定高度为30
1 q) g/ ~. x3 C7 M, h - lpMeasureItemStruct->itemHeight = 30;
# \0 ]7 K" K# h7 ]3 [3 ?3 j/ r# k - return;0 p' @. U9 w7 [+ G3 G
- }
4 m& s' B* {( c0 E: k9 C( J4 ? - CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);7 s! E: X% c- N, [
- }
复制代码 / i: g# R# x z2 ]2 ^8 `
同样别忘了指定列表框的Owner draw属性: 2 A! ]: p% \5 P" H( \1 _. j
图11 指定下拉框的Owner draw属性
5 o3 h5 p$ E# h& a0 B2 }" P
' D) Z( [: d) Q& w 3.3.6 NM_CUSTOMDRAW
% j0 q4 r' ]6 p0 O. H+ _9 L 大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。 4 I! `% f0 S0 H. t- d, c
可以反射NM_CUSTOMDRAW消息,如: - r" O8 w( a3 F/ ]' S
ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw) . k) G) e) y* q+ k. o
afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult); 9 t8 W8 V2 a5 B% J& J% g9 {$ z
参数:
- ], m1 } B0 ?4 [ pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:
# X% A4 ?! v0 [: l- 1 d4 \/ M/ Y5 z# p
- typedef struct tagNMHDR7 U) c* \2 T: S. ?; Q) r1 }
- {! D2 O8 I% k) S* l
- HWND hwndFrom;% b4 A% a) Q# _# Y( j
- UINT idFrom;% [# c+ ?7 X$ E% K$ F
- UINT code; 4 J9 @1 i" G& d- ^/ _! w
- } NMHDR;
复制代码 3 D J+ H* }( a# P; \* r: m& s
其中: 5 q1 A8 K A' T* {8 Y2 p2 S( [3 v
hwndFrom 发送方控件的窗口句柄
; k. z! B R6 [9 s- V- } idFrom 发送方控件的ID code 通知代码
! @, V4 }0 L% m7 n2 [% i 对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下: + Z- s3 Y# _2 k1 m
- ) k. O3 c+ q; P0 r3 H+ K
- typedef struct tagNMCUSTOMDRAWINFO& B& p7 r P c" G" I8 b* @
- {
3 G3 ]+ g2 ? f* ?( r - NMHDR hdr;" }1 k7 c" y7 r2 }
- DWORD dwDrawStage;
% G, U3 A( o% X p - HDC hdc;9 X6 x! R. S2 e3 H& V! K0 _/ d- {8 ^
- RECT rc;
) a' N! C) [5 x - DWORD dwItemSpec;) H0 G+ ~. m( U1 c+ J& a1 r5 _
- UINT uItemState;) }3 z1 [9 ?6 r" `
- LPARAM lItemlParam;) Z/ \" w. @, Y) A" m) P1 i( ]
- } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;
复制代码 2 I0 c) [ b8 D* h4 V1 k
hdr NMHDR对象 dwDrawStage 当前绘制状态,其取值如表7所示:6 ~- j& q% J! L$ ~' g
类型值 含义 0 z8 r+ _5 U1 T' H2 E" E& H2 T Q& D
CDDS_POSTERASE 擦除循环结束
9 N) l0 z2 t# t* T; E3 x0 lCDDS_POSTPAINT 绘制循环结束 1 D8 D$ F/ T; e, B2 d# j9 }# e
CDDS_PREERASE 准备开始擦除循环
# A; x: B( i1 F c# o; {CDDS_PREPAINT 准备开始绘制循环 9 o3 {, y; c7 w- v
CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效 % Q- C% Z5 T* {* J( T" K
CDDS_ITEMPOSTERASE 列表项擦除结束
9 G3 I# G4 m: E& ^CDDS_ITEMPOSTPAINT 列表项绘制结束
, Q# w' i4 W3 w) k2 A1 V! fCDDS_ITEMPREERASE 准备开始列表项擦除 & `0 m8 ~8 g; B2 N
CDDS_ITEMPREPAINT 准备开始列表项绘制
/ _5 p- d, `# v; i" O0 o) oCDDS_SUBITEM 指定列表子项
" k! _' u" t" w# t" \1 q表7 dwDrawStage的类型值与含义
, O: L) y b$ n; H4 c hdc指定了绘制操作所使用的设备环境。 # n! Y( D4 N% {4 e9 y% ?
rc指定了将被绘制的矩形区域。 ! Y3 K6 P1 U/ k
dwItemSpec 列表项的索引
! S$ [, S: ^9 T+ T2 C uItemState 当前列表项的状态,其取值如表8所示:
$ a' K+ Z* ? L; N$ Q x& T1 Z( H5 l7 e类型值 含义
" {" P9 x) m1 e* o0 P& ]2 }' ^CDIS_CHECKED 标记状态。
, U. h* N+ z. ~CDIS_DEFAULT 默认状态。 1 D; w x/ H4 o; R* j3 O
CDIS_DISABLED 禁止状态。
# P6 @8 B9 J0 F, HCDIS_FOCUS 焦点状态。
4 U+ i0 s5 L( F. j& Y$ m bCDIS_GRAYED 灰化状态。 & D4 g/ x) v0 {* W3 Y9 @/ \ q
CDIS_SELECTED 选中状态。
, g# Y3 x" k5 S H8 f& x* ^CDIS_HOTLIGHT 热点状态。
4 \3 F& H" B& i XCDIS_INDETERMINATE 不定状态。
- K1 q% ^; N) s8 g$ ]6 Z$ tCDIS_MARKED 标注状态。! H+ ]2 r0 o, X# o* }! ?: N: K
表8 uItemState的类型值与含义; v3 Y$ c. T2 \& \) C
lItemlParam 当前列表项的绑定数据 . m0 |6 N+ C( N f
pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage: : ^7 S) X2 h* h1 A- u$ G
当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:
8 b% o/ c0 Z% e: b* r7 _1 R! L类型值 含义 & h( P; g0 A) G5 a5 Q
CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。 6 U. i& E8 g3 {
CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。 2 F, u, c& `( k, K9 A, j: ~2 S
CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。 & X6 ?. O5 y3 q2 q1 a7 g$ v
CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。
* Q8 ^2 Y. a/ f: x/ s" b表9 pResult的类型值与含义(一)
, O0 d; ~% v( {! o ` 当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:5 ]8 e/ I# b% y; h4 b
类型值 含义
) }! ~7 q2 X I. c; O4 FCDRF_NEWFONT 指定后续操作采用应用中指定的新字体。
& \6 G& k, x- x: \. r+ c: [3 xCDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。
g( p) |2 x0 l! }. SCDRF_SKIPDEFAULT 系统不必再绘制该子项。
' s) V0 g `+ a u4 q表10 pResult的类型值与含义(二)$ s3 e- k w& b0 y4 i) v7 o
以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子: 9 \" g% g. l3 z" [, \
图12 利用NM_CUSTOMDRAW消息美化界面
9 m+ g0 c; \: k4 |3 z% C- [2 j; G 对应代码如下: 5 ]* M3 ]1 w8 }1 K, N2 d. @
- ) Q( \2 _1 R; i7 A+ q
- void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
" I6 K1 @+ H# [' C& [2 j7 _ - {
+ U5 u8 V( e) _: B+ K% z, {; [ - //类型安全转换
) e( u! P) o. g* G; {) g) T6 k - NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);( ? q& p5 x1 H' b: x2 H
- *pResult = 0;5 l3 A6 H5 e* \0 @4 j! a2 u" C( w
- ; ?7 q( K" }- c# G% y
- //指定列表项绘制前后发送消息( F* R. m) `* M* P% }1 C8 _6 F% K
- if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)3 ?' U) V, L+ K2 w! {9 j. e' a
- {9 O4 B- s+ m- C& D+ s+ C+ e
- *pResult = CDRF_NOTIFYITEMDRAW;, ]6 T" Z& t3 ?
- }
$ H' ]; U+ c5 ?$ y - else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)* p+ _- w& q+ W& Q. h; W& a9 D, K. D
- {8 D5 ?9 z7 [: J6 T
- //奇数行# b: j/ c' K7 c7 U$ L7 `) \
- if(pLVCD->nmcd.dwItemSpec % 2); K/ s& H8 ?9 x: P
- pLVCD->clrTextBk = RGB(255, 255, 128);
$ B8 N, T+ L, P$ b9 k' { - //偶数行( Q8 f" z# R1 I; |1 C
- else
3 N0 h* g( b/ M8 Q1 d1 H - pLVCD->clrTextBk = RGB(128, 255, 255);- X; ~; [0 K$ s/ j7 Y- U
- //继续5 y! @! K6 j1 {" A
- *pResult = CDRF_DODEFAULT;2 |5 i; f9 Y" p% T5 p( s
- }
) p' o' D6 u5 y7 @ - }
复制代码 , K4 K' L' }$ ^
注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。 6 B5 U: L' w- L7 a6 |
6 W5 _/ C6 n+ E
3.4 使用MFC类的虚函数机制 , I9 ~+ C, T4 V
修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子:
& _2 X/ ^! J5 B5 {; H7 c/ @# l- / }0 b, X' a$ l1 S( W
- void CView::OnPaint()
. w9 a2 w, m5 W/ d+ @# \0 b% U - {. e! V( H/ F: \" ?* ~& U) c1 q
- // standard paint routine/ O* |- l, a! y Z. I1 C! v% K
- CPaintDC dc(this);
2 V. m( b5 M( V9 D - OnPrepareDC(&dc);6 b- R8 H, B8 S: V; Q
- OnDraw(&dc);3 ^3 Z, P1 Y4 ^" Q; v
- }
复制代码 % l4 R5 I, M+ `* S) Y# d* u
这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。
; e/ e! S* A3 n6 }9 j8 o 以下列出了与界面美化相关的虚函数,参数说明略去:
! U, b0 `6 U" }8 ~3 v$ p; jCButton::DrawItem
6 L& r6 J' d5 ZCCheckListBox::DrawItem
! K, X3 m3 f) k/ J+ FCComboBox::DrawItem
# ~ d8 }/ f, d; r$ F" X TCHeaderCtrl::DrawItem
! i& Y: C2 }2 O0 u' p+ V1 DCListBox::DrawItem d s1 }0 \! G2 w' C5 D# m1 Y
CMenu::DrawItem 4 c. Z- x& j2 W5 Y1 l" @
CStatusBar::DrawItem
: s3 k' t# t( f8 KCStatusBarCtrl::DrawItem
! t; a$ Q$ B# w1 g, `' H* U) CCTabCtrl::DrawItem
7 d4 P" |% G, c. s- K2 e! T9 ?virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );
! l+ V6 k; w; P aOwner draw元素自绘函数 很显然,位图菜单都是通过这个DrawItem画出来的。) k2 P+ Q( x# ~, x6 z0 D! q' W
限于篇幅,在此不再附以例程。 0 D8 m' V7 E8 D9 I8 `* p- }& v
p1 U' G, F5 m Q
参考文献
2 d: Z4 F( w7 N4 Z. c, S/ ]本文为白乔原创,曾经在《电脑爱好者》合订本上发表。 |