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