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