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