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