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