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