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