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