滚动条
滚动条是图形用户界面中最好的功能之一,它很容易使用,而且提供了很好的视觉反馈。
滚动条既有垂直方向的(供上下移动),也有水平方向的(供左右移动)。很容易在应用程序中包含水平或者垂直的滚动条,程序员只需要在CreateWindow的第三个参数中包括窗口风格(WS)标识符WS_VSCROLL(垂直滚动)和(或)WS_HSCROLL(水平滚动)即可。这些滚动条通常放在窗口的右部和底部,伸展为客户区的整个长度或宽度。客户区不包含滚动条所占据的空间。对于特定的显示驱动程序和显示分辨率,垂直滚动条的宽度和水平滚动条的高度是恒定的。如果需要这些值,可以使用GetSystemMetrics调用来获取。
Windows负责处理对滚动条的所有鼠标操作,但是,窗口滚动条没有自动的键盘界面。如果想用光标键来完成滚动功能,则必须提供这方面的代码。
滚动条的范围和位置
每个滚动条均有一个相关的“范围”(这是一对整数,分别代表最小值和最大值)和”位置“(它是滚动框在此范围内的位置)。当滚动框在滚动条的顶部(或左部)时,滚动框的位置是范围最小值;在滚动条的底部(或右部)时,滚动框的位置是范围的最大值。
在默认情况下,滚动条的范围是从0(顶部或左部)~100(底部或右部),但将范围改变为更方便于程序的数值也是很容易的:
SetScrollRange(hwnd,iBar,iMin,iMax,bRedraw);
参数iBar为SB_VERT或者SB_HORZ,iMin和iMax分别是范围的最小值和最大值。如果想要Windows根据新范围重画滚动条,则设置bRedraw为TRUE。(如果在调用SetScrollRange后,调用了影响滚动条位置的其他函数,则应该将bRedraw设置为FALSE,以避免过多的重画。)
滚动框的位置总是离散的整数值。例如,范围为0~4的滚动条具有5个滚动框位置。
我们可以使用SetScrollPos在滚动条范围内设置新的滚动框位置:SetScrollPos(hwnd,iBar,iPos,bRedraw);
参数iPos是新位置,它必须在iMin至iMax的范围内。Windows提供了类似的函数(GetScrollRange和GetScrollPos)来获取滚动条的当前范围和位置。
在程序内使用滚动条时,程序员与Windows共同负责维护滚动条以及更新滚动框的位置。下面是Windows对滚动条的处理:
1.处理所有滚动条鼠标事件;
2.当用户在滚动条内单击鼠标时,提供一种”反相视频“闪烁;
3.当用户在滚动条内拖动滚动框时,移动滚动框;
4.为包含滚动条窗口的窗口过程发送滚动条消息。
以下是程序员应该完成的工作:
1.初始化滚动条的范围和位置;
2.处理窗口过程的滚动条消息;
3.更新滚动条内滚动框的位置;
4.更改客户区的内容以响应对滚动条的更改。
滚动条消息
在用鼠标单击滚动条或者拖动滚动框时,Windows给窗口过程发送WM_VSCROLL(供上下移动)和WM_HSCROLL(供左右移动)消息。在滚动条上的每个鼠标动作都至少产生两个消息,一个在按下鼠标键时产生,一个在释放键时产生。
与所有的消息一样,WM_VSCROLL和WM_HSCROLL也带有wParam和lParam消息参数。对于来自作为窗口的一部分而创建的滚动条消息,我们可以忽略lParam;它只用于作为子窗口而创建的滚动条(通常在对话框内)。
wParam消息参数被分为一个低位字和一个高位字。其的低位字是一个数值,它指出了鼠标对滚动条进行的操作。这个数值被看作一个”通知码“。通知码的值由以SB(代表”scroll bar(滚动条)“)开关的标识符定义。
以下是在WINUSER.H中定义的通知码:
/*
* Scroll Bar Commands
*/
#define SB_LINEUP 0
#define SB_LINELEFT 0
#define SB_LINEDOWN 1
#define SB_LINERIGHT 1
#define SB_PAGEUP 2
#define SB_PAGELEFT 2
#define SB_PAGEDOWN 3
#define SB_PAGERIGHT 3
#define SB_THUMBPOSITION 4
#define SB_THUMBTRACK 5
#define SB_TOP 6
#define SB_LEFT 6
#define SB_BOTTOM 7
#define SB_RIGHT 7
#define SB_ENDSCROLL 8
#endif /* !NOSCROLL */
包含LEFT和RIGHT的标识符用于水平滚动条,包含UP、DOWN、TOP和BOTTOM的标识符用于垂直滚动条。鼠标在滚动条的不同区域单击所产生的通知码如下图所示:
如果在滚动条的各个部位按住鼠标键,程序就能收到多个滚动条消息。当释放鼠标键后,程序会收到一个带有SB_ENDSCROLL通知码的消息。一般可以忽略这个消息,Windows不会去改变滚动框的位置,而我们可以在程序中调用SetSrollPos来改变滚动框的位置。
当把鼠标的光标放在滚动框上并按住鼠标键时,就可以移动滚动框。这样就产生了带有SB_THUMBTRACK和SB_THUMBPOSITION通知码的滚动条消息。在wParam的低位字是SB_THUMBTRACK时,wParam的高位字是用户在拖动滚动框时的当前位置。该位置位于滚动条范围的最小值和最大值之间。在wParam的低位字是SB_THUMBPOSITION时,wParam的高位字是用户释放鼠标键后滚动框的最终位置。对于其他的滚动条操作,wParam的高位字应该被忽略。
为了给用户提供反馈,Windows在我们用鼠标拖动滚动框移动它时,同时我们的程序会收到SB_THUMBTRACK消息。然而,如果不通过调用SetScrollPos来处理SB_THUMBTRACK或SB_THUMBPOSITION消息,在用户释放鼠标键后,滚动框会迅速跳回原来的位置。
程序能够处理SB_THUMBTRACK或SB_THUMBPOSITION消息,但一般不同时处理两者。如果处理SB_THUMBTRACK消息,在用户拖动滚动框时我们需要移动客户区的内容。而如果处理SB_THUMBPOSITION 消息,则只需在用户停止拖动滚动框时移动客户区的内容。处理SB_THUMBTRACK消息更好一些(但更困难),对于某些类型的数据,我们的程序可能很难跟上产生的消息。
WINUSER.H头文件不包括SB_TOP、SB_BOTTOM、SB_LEFT和SB_RIGHT通知码,指出滚动条已经被移到了它的最小或最大位置。然而,对于作为应用程序窗口一部分而创建的滚动条来说,永远不会接收到这些通知码。
为滚动条范围使用32位的值也是有效的,尽管这不常见。然而,wParam的高位字只是16位的,它不能适当地指出SB_THUMBTRACK和SB_THUMBPOSITION操作的位置。在这种情况下,需要使用GetScrollInfo函数(下面描述)来得到信息。
在程序中添加滚动功能
前面的说明已经很详尽了,现在要将其应用于实践中。让我们开始时简单些,从垂直滚动入手,因为我们实在太需要垂直滚动了,而暂时还可以不用水平滚动。
在CreateWindow调用的第三个参数中包含 WS_VSCROLL 窗口风格,从而在窗口中加入了垂直滚动条
WndProc窗口过程在处理WM_CREATE消息时增加两条语句,以设置垂直滚动条的范围和初始位置:
SetScrollRange(hwnd,SB_VERT,0,100,FALSE);
SetScrollPos(hwnd,SB_VERT,iVscrollPos,TRUE);
为了有助于处理WM_VSCROLL消息,在窗口过程中定义了一个静态变量iVscrollPos,这一变量是滚动条滚动框的当前位置。对于SB_LINEUP和SB_LINEDOWN,只需要将滚动框调整一个单位的位置。对于SB_PAGEUP和SB_PAGEDOWN,我们想整屏地移动屏幕的内容,或者移动cyClient/cyChar个单位的位置。对于SB_THUMBPOSITION,新的滚动框位置是wParam的高位字。SB_ENDSCROLL和SB_THUMBTRACK消息被忽略。
在程序基于收到的WM_VSCROLL类型的消息计算出新的iVscrollPos值后,用min和max宏来调整iVscrollPos,以确保它在最大值与最小值之间。程序然后将iVscrollPos与调用GetScrollPos获取的先前位置相比较,如果滚动位置发生了变化,则使用SetScrollPos来进行更新,并且调用InvalidateRect (hwnd, NULL, TRUE) ;使整个窗口无效。
InvalidateRect (hwnd, NULL, TRUE) ;调用产生一个WM_PAINT消息。其他不说了。。。
但,由于Windows将WM_PAINT消息当成低优先级消息,如果系统有许多其他的动作正在发生,那么也许会让我们等待一会儿工夫。这时,当对话框消失时,将会出现一些空白的“洞”,程序仍然等待刷新它的窗口。
如果希望立即刷新无效区域,可以在调用InvalidateRect之后调用UpdateWindow(hwnd);,如果客户区的任一部分无效,则UpdateWindow将导致Windows用WM_PAINT消息调用窗口过程(如果整个客户区有效,则不调用窗口过程)。窗口过程完成刷新后立即退出,Windows将控制返回给程序中UpdateWindow调用之后的语句。(与WinMain中的UpdateWindow同)
建立更好的滚动
下面的更好的滚动将不使用前面所讨论的4个滚动条函数。相反将使用Win32API中都有的新函数。
滚动条信息函数
滚动条文档指出SetScrollRange、SetScrollPos、GetScrollRange和GetScrollPos函数是“过时的”,但这并不完全正确。这些函数在Windows1.0中就出现了,在Win32API中升级以处理32位参数。它们仍然具有良好的功能。而且,它们不与Windows编程中新函数相冲突,这就是上面仍使用它们的原因。
Win32API介绍的两个滚动条函数称作SetScrollInfo和GetScrollInfo。这些函数完成以前函数的全部功能,并增加了两个新特性。
第一个功能涉及滚动框的大小。也许您可能注意到,滚动框大小在上面的程序中的固定的。然而,在我们使用到的一些应用程序中,滚动框大小与在窗口中显示的文档大小成比例。显示的大小称作“页面大小”。
算法为:滚动框大小/滚动长度≈页面大小/范围≈显示的文档数量/文档的总大小
可以使用SetScrollInfo来设置页面大小,从而设置了滚动框的大小。
GetScrollInfo函数增加了第二个重要的功能,或者说它改进了目前API的不足。假设您要使用65536或更大单位的范围,这在16位的Windows中是不可能的。当然在Win32中,函数被定义为可接受32位参数,因此是没有问题。然而当使用SB_THUMBTRACK或SB_THUMBPOSITION通知码得到WM_VSCROLL或WM_HSCROLL消息时,只提供了16位数据来滚动框的当前位置。通过GetScrollInfo函数可以获取真实的32位值。
SetScrollInfo和GetScrollInfo函数的语法是:
- SetScrollInfo(hwnd,iBar,&si,bRedraw);
- GetScrollInfo(hwnd,iBar,&si);
像在其他滚动条函数中那样,iBar参数是SB_VERT或SB_HORZ,它还可以是用于滚动条控制的SB_CTL。SetScrollInfo的最后一个参数可以是TRUE或FALSE,指出了是否要Windows重新绘制计算了新信息后的滚动条。
两个函数的第三个参数是SCROLLINFO结构,定义为:
typedef struct tagSCROLLINFO
{
UINT cbSize;
UINT fMask;
int nMin;
int nMax;
UINT nPage;
int nPos;
int nTrackPos;
} SCROLLINFO, FAR *LPSCROLLINFO;
typedef SCROLLINFO CONST FAR *LPCSCROLLINFO;
在程序中,可以定义如下的SCROLLINFO结构类型:SCROLLINFO si;
在调用SetScrollInfo或GetScrollInfo之前,必须将cbSize字段设置为结构的大小:
si.cbSize=sizeof(si); 或 si.sbSize=sizeof(SCROLLINFO);
逐渐熟悉Windows后,您就会发现另外几个结构像这个结构一样,第一个字段指出了结构大小。这个字段使将来的Windows版本可以扩充结构并添加新的功能,并且仍然与以前编译的版本兼容。
把fMask字段设置为以SIF前缀开头的一个或多个标志,并且可以使用C的位操作OR函数(|)组合这些标志。如下几个:
#define SIF_RANGE 0x0001
#define SIF_PAGE 0x0002
#define SIF_POS 0x0004
#define SIF_DISABLENOSCROLL 0x0008
#define SIF_TRACKPOS 0x0010
#define SIF_ALL (SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS)
当通过SetScrollInfo函数使用SIF_RANGE标志时,必须把nMin和nMax字段设置为所需的滚动条范围。当通过GetScrollInfo函数使用SIF_RANGE标志时,应把nMin和nMax字段设置为从函数返回的当前范围。
SIF_POS标志也一样。当通过SetScrollInfo使用它时,必须把结构的nPos字段设置为所需的位置。可以通过GetScrollInfo使用SIF_POS标志来获取当前位置。
使用SIF_PAGE标志能够获取页面大小。用SetScrollInfo函数把nPage设置为所需的页面大小。GetScrollInfo使用SIT_PAGE标志可以获取当前页面的大小。如果不想得到比例化的滚动条,就还要使用该标志。
当处理带有SB_THUMBTRACK或SB_THUMBPOSITION通知码的WM_VSCROLL或WM_HSCROLL消息时,通过GetScrollInfo只使用SIF_TRACKPOS标志。从函数的返回中,SCROLLINFO结构的nTrackPos字段将指出当前的32位滚动框位置。
在SetScrollInfo函数中仅使用SIF_DISABLENOSCROLL标志。如果指定了此标志,并且新的滚动条参数使滚动条不可见,则禁用该滚动条。
SIF_ALL标志是SIF_RANGE、SIF_POS、SIF_PAGE和SIF_TRANCKPOS的组合。当在WM_SIZE消息期间设置滚动条参数时,这是很方便的。(当在SetScrollInfo函数中指定SIF_TRACKPOS时,则SIF_TRACKPOS标志被忽略。)
滚动范围
对于用以前的函数的滚动范围可以进行适当的优化,比如,当有75行内容(0~74),但窗口大小是50,则只有50行显示在客户区中,所以应把范围改为0~25。当滚动条位置等于0时,程序显示0~49行,当等于25时,显示25~74行。
但新滚动条函数的一个好的功能是当使用与滚动条范围一样大的页面时,它已经为您做出了大量的逻辑功能。可以像下面的一样使用SCROLLINFO结构和SetScrollInfo:
- si.cbSize = sizeof (si) ;
- si.fMask = SIF_RANGE | SIF_PAGE ;
- si.nMin = 0 ;
- si.nMax = NUMLINES - 1 ;
- si.nPage = cyClient / cyChar ;
- SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
这样之后,Windows会把最大的滚动条位置限制为si.nMax-si.nPage+1,而不是si.nMax。像前面那样做出假设NUMLINES等于75(所以si.nMax等于74),si.nPage等于50.这意味着最大的滚动条位置限制为74-50+1,即25,这正是我们想要的。
当页面大小与滚动条范围一样大时,会发生什么情况呢?在这个例子中,就是nPage等于75或更大的情况。Windows通常隐藏滚动条,因为它并不需要。如果不想隐藏滚动条,可在调用SetScrollInfo时使用SIF_DISABLENOSCROlL,Windows只是禁用滚动条而不隐藏它。