Chinaunix首页 | 论坛 | 博客
  • 博客访问: 198483
  • 博文数量: 264
  • 博客积分: 6010
  • 博客等级: 准将
  • 技术积分: 2740
  • 用 户 组: 普通用户
  • 注册时间: 2009-06-03 13:25
文章分类

全部博文(264)

文章存档

2011年(1)

2009年(263)

我的朋友

分类: C/C++

2009-06-04 13:30:23

在MFC中,用列表框(CListBox)来显示多个字符串是一种很方便的方法。但缺省的列表框水平滚动条不够智能——这里智能的含义是:在应该出现的时候出现,不应该出现的时候消失,而且应能自动调节自己的大小。本文通过实例说明了存在的问题和解决办法。  . M: U( z) k6 I
  S5 [7 q5 g  f3 v$ t0 G9 F1 @, N% R$ H
一、问题演示  ) T9 `! V- t% p& x8 }
. @& S9 ?. m6 Y
首先用Visual Studio应用向导创建工程CustomCListBox。这是一个基于对话框的应用,向导提供的所有可选参数均采用其缺省值。  
+ x8 o# j5 L/ ?1 r2 o* I
2 Z, x/ I9 P7 Z( H在资源编辑器中将对主话框字体设为宋体12,插入一个CListBox控制,设其ID为IDC_LLISTTEST,大小为125 X 84。 请确认列表框的垂直滚动条、水平滚动条有效,取消其排序风格。  * C5 O+ N. Q" d$ _' T# A' C) w

' M( m  {6 P6 _" w启动Class Wizard,选择Member Variables选项卡,为列表框加入对应的成员变量m_lListTest,在Category中选择Control。  
+ k* u' o8 M$ `6 t: w+ H; e
8 @% [$ w# B3 c5 b+ N$ {接下来在 Workspace窗格中选择ClassView,扩展CCustomCListBoxDlg类并双击OnInitDialog(),在编辑窗格中找到注 释行“TODO: Add extra initialization here”,在该行下面加入以下内容:  
4 F; j7 f' |7 F2 }+ p5 L' O, F5 ~9 P
m_lListTest.AddString(_T("One")); 8 G4 S* C: n" h% m" A4 f* r
m_lListTest.AddString(_T("Two"));
# o' c$ u  E2 Y1 v- C8 um_lListTest.AddString(_T("Three"));
2 ~  G3 |3 K* b: ~m_lListTest.AddString(_T("Four"));
6 B! e. ]2 K( i3 {( Hm_lListTest.AddString(_T("Five")); 8 W  b" o5 }4 e
m_lListTest.AddString(_T("Six"));
3 U/ C( k( D' S: k5 qm_lListTest.AddString(_T("北国风光,千里冰封,万里雪飘。"));
5 W# [- U0 V. v" u+ O8 F% nm_lListTest.AddString(_T("Eight"));
4 W$ ?. T: P0 A9 z' v7 nm_lListTest.AddString(_T("Nine"));   Y# _* p6 }  S# A' W3 L; R$ c6 E
m_lListTest.AddString(_T("Ten"));
8 \3 i* w) {2 x: H$ Y. k) r+ x: x" p& a8 E/ e8 J
编译并运行这个工程,可以发现列表框能够正确显示全部内容。  
8 v% l# ?2 M' M1 Q  k3 W' e/ _' q' @+ L
如果在上述m_lListText.AddString(_T"Ten"))后面加入一行:  8 }, F! v; [; h( I
2 h. M0 a" r+ U; y5 i
m_lListTest.AddString(_T("Eleven")); ' p! b' D, R6 N

! w9 ?: B$ q+ D& c. p重新编译并运行该工程,可以发现出现了一个垂直滚动条。垂直滚动条的出现使得列表框水平方向有效显示宽度变小,第七行的内容被切割而不能完整显示。但此时水平滚动条并没有自动出现,第七行被切割部分就无法看到了。  
, d/ o) u% D# z# J/ k
- u$ j  _- E3 h1 Y5 s* d) K! r如果我们删除最后加入的语句,把第七行汉字加长到超出列表框显示宽度为止,也可以发现水平滚动条不会自动出现。被切割部分仍旧无法看到。  ) y1 [! S( ~9 V6 N9 s( E. l8 C. t

" ^: y6 v& b- J; g5 @5 Z$ l由此可知,CListBox的水平滚动条并不象垂直滚动条那样“聪明”:垂直滚动条总是能够在需要它的时候自动出现,并能够自动调节自身大小,而水平滚动条不能。  & n: p5 ^& x& ]: u8 M
4 [/ S% G1 i) [) Q/ s
二、解决问题  / \2 P7 L5 _1 A

/ R; y; b  N+ e: W5 ~6 o为提高代码的可重用性,可以创建CListBox的派生类,在派生类中实现“智能”水平滚动条。需要考虑的主要问题包括:跟踪最大字符串宽度(应能适应不同场合下的字体变化),必要时计算垂直滚动条宽度,自动显示和调节水平滚动条的大小。  
! \6 ^  ^* s) H- G6 \' i" g: B
! e! z; ]" Y8 f; z选菜单 Insert/New Class,设新创建类的名字为CDJListBox,其基类为CListBox,其它选项采用缺省值。单击OK,Visual Studio自动生成DJListBox.cpp和DJListBox.h两个文件。  / i4 x$ @/ j: I* _6 I+ X

. b9 \" n. q3 f3 D接下来将主对话框的列表框改为CDJListBox类型,即在CLassView扩展CCustomListBoxDlg类并双击m_lListTest成员,在编辑窗格,修改  
$ a; N/ I' ?! F) \
+ |. B5 _1 D; J9 gCListBox m_lListTest;
0 b5 Y7 A6 }9 P0 D# [8 a' u! z" Q* ~( f
为:  
7 q# J. s- K0 P3 m6 J0 x5 o: n7 j/ q* H# w  m! @% K
CDJListBox m_lListTest; % M4 y" G; i- y" H! K5 ~$ |

3 s0 S2 p2 X3 E; I0 v4 e5 V然后,在类声明代码之前,插入  4 k5 h1 r$ H! Y6 O9 U7 U

5 j6 w3 @) x5 i& V% ]#include "DJListBox.h"
) a4 W& p. j! B
9 g: i5 y% B' G, |0 u( m9 J此时如果重新编译并运行,是无法看到任何实质性的改变的,因为我们并没有修改CDJListBox。所有对于CDJListBox的调用都直接传递给基类CListBox了。  
' T4 }  i: ^1 a! G- G7 l' D4 S& i% E# `* O; v
跟踪字符串最大宽度可以通过覆盖CListBox::AddString()实现。打开DJListBox.h,紧接类的析构函数加入如下声明:  ' ^1 o) K+ j7 i9 @- C0 M
% C, Z+ `; C; v+ ^
int AddString( LPCTSTR lpszItem );
" ~) ]) m  k% S2 }" p9 g% A7 R% m1 y9 n- R( F) S6 D1 H
并在实现文件DJListBox.cpp加入该函数框架:  
: s  w- Q5 s6 D5 s7 q' [" {7 M- ^  m" ]( T; E3 b# f
int CDJListBox::AddString(LPCTSTR lpszItem) 5 m/ E  A3 q# Q7 i, F
{
% w3 d* q2 R! L; @. a//此处加入字符串宽度跟踪、水平滚动条显示等代码 5 w9 J/ Q! ~/ ?4 ]  v. H  U* E
} % p' F( f5 O2 m* |
; n6 Z$ S, j. D: U
字符串宽度跟踪可以用整形成员变量m_nMaxWidth实现。在DjListBox.h的protected声明区内,加入以下一行:  7 j$ @# @- Y2 b: L1 ~
! S! ]4 n* B9 k# r
int m_nMaxWidth; 0 Z2 C; u$ o7 u1 L9 w' ^6 G

4 \; x! q) s. {- a$ P* Y在DJListBox.cpp文件,找到CDJListBox的建构函数,为这个最大宽度作初始化:  
7 V( i) R4 B+ \4 s; l7 K5 b' R2 ?" F* s
m_nMaxWidth = 0; # b1 B3 c- i, O4 l2 v

. v/ P* C  k6 [) F: v现在可以改动新加入的AddString()了。先应该调用基类AddString(),并用nRet记录其返回值:  
9 d' M3 n2 }3 W5 }7 B# ~3 z8 U# u& d; H2 R
int nRet = CListBox::AddString(lpszItem);
6 F; |/ Y) ^# e9 l" f. |5 g: c, ~
7 I3 w8 S: i* [4 i: S( @' U8 t) E接下来调用GetScrollInfo()以获得垂直滚动条的相关信息。这些信息是通过一个SCROLLINFO结构传递的,下面是对该结构初始化并调用GetScrollInfo()的代码:  6 A* ?, [8 Q' t* q+ W: S' C, m9 [( |
1 o! D6 I. r1 W! M. [, T
SCROLLINFO scrollInfo; 2 ~. l+ K2 T* G' D% k  L
memset(&scrollInfo, 0, sizeof(SCROLLINFO)); $ s! s5 u, N) {5 w; [- z; {
scrollInfo.cbSize = sizeof(SCROLLINFO); % M0 s% O% K% t. w) k
scrollInfo.fMask = SIF_ALL; 2 [$ u: A- e/ R  k' k' [* M8 F
GetScrollInfo(SB_VERT, &scrollInfo, SIF_ALL);
; b4 y. G, Q3 N0 H( G4 H: o0 X& d$ m, G& R9 W1 [8 X( T& p2 g, b# z1 W
在调试器内观察SCROLLINFO,可以发现要获得nMax和nPage的正确数值,列表框至少应含有一个字符串。SCROLLINFO的成员 nPage保存了列表框“每页”能够显示的项目数,nMax是列表框内项目总数。当nMax大于或等于nPage,就出现了垂直滚动条。我们需要知道垂直 滚动条的宽度以正确计算列表框的有效显示宽度。这里使用一个初始值为0的整数nScrollWidth表示,并在垂直滚动条显示时将它赋值:  9 W& |1 W2 R. G& a6 y9 I, u
% ]; d$ S' ?+ C( Y0 {
int nScrollWidth = 0;
* ~/ j8 n, C6 e7 ]$ y( jif(GetCount() > 1 && ((int)scrollInfo.nMax  7 ?; R4 A, m: V4 {( J. ?+ K' ^
> = (int)scrollInfo.nPage))
! i; Y% w' }7 C- [% \{ 1 v" W' u, t' S* E- k1 `3 y! Y% F
nScrollWidth = GetSystemMetrics(SM_CXVSCROLL);
. K1 ?* W( l. S9 p! B' v* N} 4 s7 k7 b( ~/ U: ]% V7 r, J& X5 k4 d
" g, ~: I3 N& _: Q& c
接下来声明一个SIZE变量sSize,并实例化对话框的CClientDC:  ; b) ~3 d; O! z' ^

) g, i! Q1 o, n6 o/ nSIZE sSize;
0 l% H2 U* }9 v& B: ?% C4 aCClientDC myDC(this);
& ?4 Z% J, B! x+ X, R, ^3 x8 G, R9 ?0 G) D! w3 D
对话框所采用的字体,有可能是缺省字体,也有可能是有目的的选择。在对话框编辑器中右击对话框,并选择Properties可以查看当前值。虽然MyDC 是从列表框取得的,但列表框字体信息并未包含在MyDC中。也就是说,对话框创建时所用字体并没有“选入”CClientDC。要从 GetTextExtentPoint32()获得真正的字符串大小,应该先调用GetFont()获得列表框的字体信息,然后将此字体选入MyDC,代 码为:  - S3 A1 V1 I/ {7 J, K: {

& X' Y& _1 t" HCFont* pListBoxFont = GetFont(); 2 p/ e1 F# s6 k$ o
if(pListBoxFont != NULL) + `0 H/ O* d: |. {; H3 H
{
+ k: D% r$ H' b: hCFont* pOldFont =  
/ E9 o& _, X. ~2 V9 `! h, TmyDC.SelectObject(pListBoxFont); * l% e* W8 R1 g; S  l
% e; ^) \% v0 @2 ]
现在可以调用GetTextExtendPoint32()函数来获得字符串的宽度了。字符串的宽度由sSize结构的cx成员返回,将该值和已有最大宽度相比较:  
, w- j% Y" k# w  r# Q$ p
# _8 o7 N9 L. N" Y/ ~8 T0 aGetTextExtentPoint32(myDC.m_hDC,  
6 T3 B5 [# l$ x) olpszItem, strlen(lpszItem), &sSize);
" K5 v- W6 F" ^% J2 }m_nMaxWidth = max(m_nMaxWidth, (int)sSize.cx); $ d# k% y6 F0 S3 E) k6 S, T

$ c' n, X1 n& H: d剩下的重要工作之一,就是设置水平滚动条的大小了。这可以通过调用SetHorizontalExtent()完成。如果传递给它的整形参数比列表框本身宽度小,则水平滚动条被隐藏。  
1 A/ z) r  q; h& Q6 W) \& T- ~' d  g2 A
这里有一个容易被忽略的地方。如果仔细观察CListBox,可以发现文本左边有一栏小小的空白,它的大小为3 。这部分宽度应该加到文本宽度上。如果希望在文本右边也同样空出一栏,则可以在文本宽度上再加3。  
  a4 }$ a0 E; c; b4 f5 n$ k+ p; L2 Y# N. H- j% {
SetHorizontalExtent(m_nMaxWidth + 3); 2 L2 P% k( C- \( F' ]+ W% T
! x  k- T$ D" P, K6 H$ G
在结束之前,我们需要为MyDC选入原有字体。原有字体保存在pOldFont中:  ! I8 [/ q7 F7 {3 Q8 `5 I6 m* B
8 c4 K% t) N- O$ d
myDC.SelectObject(pOldFont); } . j' x4 `- [: {0 [% X9 H
7 B! n4 [2 V3 H- k9 E8 h7 ?4 e  h
return nRet;
$ s; F3 l, a% J' U; b* N, D
1 F7 j2 \0 b* `7 L2 X编译并执行新的代码,可以看到水平滚动条终于能够自动显示了。  
: z0 r$ e- X& D% o; ]2 y1 ^# J  x. c$ j
三、其它问题    {# A( o7 c' [0 e- `

* H3 }& K7 Y/ O! e在实际应用中,凡是改变列表框内容的函数都可能影响水平滚动条的显示要求,因而也必须加以定制。但其基本过程——计算文本宽度并按指定大小显示滚动条等,和上述讨论过程是相似的。  2 M' I) f' P2 s0 I9 u# U: a( Q

/ a9 O, {8 p% r' e1 KCListBox类能够改变列表 内容的方法除AddString()外,还有DeleteString(),InsertString(),ResetContent()。其中 InsertString()用于在指定位置插入字符串,在本文讨论的主题内它和AddString()是一样的。  3 Y* l  ?2 V. ]+ N( c9 S1 j: w
/ y/ g1 f4 s( n+ z
DeleteString()删除一个字符串,在派生类中其参考代码如下:  
8 f) U8 W# y0
" K* t) R$ x" R" v% yint CDJListBox:eleteString(UINT nIndex)
( Q* \8 q3 b* L, R# Z, P{
- e& x! R" W' |4 q! n: \8 SRECT lRect; + N# o) y& f, y' A: c2 @: I( [
GetWindowRect(&lRect); 2 p- K* e0 Q( M8 f/ E
$ o! i+ z+ p; c% i6 w3 k) f: H
int nRet = CListBox:eleteString(nIndex);
+ @4 h! F& B/ R
1 t' v0 r8 q( }# E1 _- r1 n, Lint nBoxWidth = lRect.right - lRect.left;
4 f) }9 \5 q! b% a) v- o0 im_nMaxWidth = nBoxWidth; 6 P  E% ]) t" U) Y/ c$ P8 ]
. w& d2 D" v% p' T6 X3 _
SIZE sSize;
! ?! \3 X( P7 M) Z: q7 iCClientDC myDC(this); , o: P6 S* y5 ?6 V8 }8 F# _4 Y* z3 ^
! d3 G6 J9 \2 C( B6 Q4 h3 f" ^
int i; 1 I7 M/ y0 u/ h* z
char szEntry[257];
- U' [: r2 E6 O" T! a- K
) `  h$ v( ?1 H& ~( v( X5 n. pfor (i = 0; i nBoxWidth) // 显示水平滚动条
5 i4 {2 {# s4 f. ?) w. Y  g{ # S, g" k! f9 {7 U
ShowScrollBar(SB_HORZ, TRUE);
) t( a. p3 q! O+ y! s4 {SetHorizontalExtent(m_nMaxWidth); 2 v3 Y& G2 c" x0 P0 o1 R; q  |
}  
9 H" j! G3 L2 _' M0 v( Aelse  $ L! }4 {  v" o3 F
{   F$ d# T3 G. |  k  E
ShowScrollBar(SB_HORZ, FALSE); & |7 u7 n. `* d" i  E  Y4 r
}
9 r$ g* X  \  D4 }! r/ H# R) K+ \+ [return nRet; / O. j8 N; I* q2 e4 d) |/ ~: d
} $ q' \# G, ~+ |, |* v

# V( U; s6 t- x( BResetContent()用于清除列表框的全部内容。在派生类中其参考代码如下:  0 K7 i' {! ]: U- A5 t3 ^& j
1 o9 ^" x* [9 s) z
void CDJListBox::ResetContent()
" ]" q2 d4 ^) q6 J# F' s{
& s# S. \6 p8 i& ~) SCListBox::ResetContent();
3 o( P& l" _' q1 F5 D0 J' Y# B/ q  E7 _5 U. v
m_nMaxWidth = 0; 1 W5 O9 e; ?9 y9 |
SetHorizontalExtent(0); ! J$ ~6 f1 E0 @$ m) I
}
阅读(531) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~