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