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