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