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