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