http://www.aogosoft.com/downpage.asp?mode=viewtext&id=163
2011-06-08 15:06
621 查看
来源于:
http://www.aogosoft.com/downpage.asp?mode=viewtext&id=163
滚动条是图形使用者接口中最好的功能之一,它很容易使用,而且提供了很好的视觉回馈效果。您可以使用滚动条显示任何东西--无论是文字、图形、表格、数据库记录、图像或是网页,只要它所需的空间超出了窗口的显示区域所能提供的空间,就可以使用滚动条。
滚动条既有垂直方向的(供上下移动),也有水平方向的(供左右移动)。使用者可以使用鼠标在滚动条两端的箭头上或者在箭头之间的区域中点一下,这时,「卷动方块」在卷动列内的移动位置与所显示的信息在整个文件中的近似相关位置成比例。使用者也可以用鼠标拖动卷动方块到特定的位置。图4-5显示了垂直滚动条的建议用法。
图4-5 垂直滚动条
有时,程序写作者对卷动概念很难理解,因为他们的观点与使用者的观点不同:使用者向下卷动是想看到文件较下面的部分;但是,程序实际上是将文件相对于显示窗口向上移动。Windows文件和表头文件标识符是依据使用者的观点:向上卷动意味着朝文件的开头移动;向下卷动意味着朝文件尾部移动。
很容易在应用程序中包含水平或者垂直的滚动条,程序写作者只需要在CreateWindow的第三个参数中包括窗口样式(WS)标识符WS_VSCROLL(垂直卷动)和/或WS_HSCROLL(水平卷动)即可。这些卷动列通常放在窗口的右部和底部,伸展为显示区域的整个长度或宽度。显示区域不包含卷动列所占据的空间。对于特定的显示驱动程序和显示分辨率,垂直卷动列的宽度和水平卷动列的高度是恒定的。如果需要这些值,可以使用GetSystemMetrics呼叫来取得(如前面的程序那样)。
Windows负责处理对滚动条的所有鼠标操作,但是,窗口滚动条没有自动的键盘接口。如果想用光标键来完成卷动功能,则必须提供这方面的程序代码(我们将在下一期另一个版本的SYSMETS程序中做到这一点)。
滚动条的范围和位置
每个滚动条均有一个相关的「范围」(这是一对整数,分别代表最小值和最大值)和「位置」(它是卷动方块在此范围内的位置)。当卷动方块在卷动列的顶部(或左部)时,卷动方块的位置是范围的最小值;在卷动列的底部(或右部)时,卷动方块的位置是范围的最大值。
在内定情况下,滚动条的范围是从0(顶部或左部)至100(底部或右部),但将范围改变为更方便于程序的数值也是很容易的:
SetScrollRange (hwnd, iBar, iMin, iMax, bRedraw) ;
参数iBar为SB_VERT或者SB_HORZ,iMin和iMax分别是范围的最小值和最大值。如果想要Windows根据新范围重画滚动条,则设置bRedraw为TRUE(如果在呼叫SetScrollRange后,呼叫了影响滚动条位置的其它函数,则应该将bRedraw设定为FALSE以避免过多的重画)。
卷动方块的位置总是离散的整数值。例如,范围为0至4的滚动条具有5个卷动方块位置,如图4-6所示。
图4-6 具有5个卷动方块位置的卷动列
您可以使用SetScrollPos在滚动条范围内设置新的卷动方块位置:
SetScrollPos (hwnd, iBar, iPos, bRedraw) ;
参数iPos是新位置,它必须在iMin至iMax的范围内。Windows提供了类似的函数(GetScrollRange和GetScrollPos)来取得滚动条的目前范围和位置。
在程序内使用滚动条时,程序写作者与Windows共同负责维护滚动条以及更新卷动方块的位置。下面是Windows对滚动条的处理:
以下是程序写作者应该完成的工作:
像生活中的大多数事情一样,在我们看一些程序代码时这些会显得更加有意义。
滚动条消息
在用鼠标单击滚动条或者拖动卷动方块时,Windows给窗口消息处理程序发送WM_VSCROLL(供上下移动)和WM_HSCROLL(供左右移动)消息。在滚动条上的每个鼠标动作都至少产生两个消息,一条在按下鼠标按钮时产生,一条在释放按钮时产生。
和所有的消息一样,WM_VSCROLL和WM_HSCROLL也带有wParam和lParam消息参数。对于来自作为窗口的一部分而建立的滚动条消息,您可以忽略lParam;它只用于作为子窗口而建立的滚动条(通常在对话框内)。
wParam消息参数被分为一个低字组和一个高字组。wParam的低字组是一个数值,它指出了鼠标对滚动条进行的操作。这个数值被看作一个「通知码」。通知码的值由以SB(代表「scroll bar(滚动条)」)开头的标识符定义。以下是在Windows.inc中定义的通知码:
包含LEFT和RIGHT的标识符用于水平滚动条,包含UP、DOWN、TOP和BOTTOM的标识符用于垂直滚动条。鼠标在滚动条的不同区域单击所产生的通知码如图4-7所示。
图4-7 用于滚动条消息的wParam值的标识符
如果在滚动条的各个部位按住鼠标键,程序就能收到多个滚动条消息。当释放鼠标键后,程序会收到一个带有SB_ENDSCROLL通知码的消息。一般可以忽略这个消息,Windows不会去改变卷动方块的位置,而您可以在程序中呼叫SetScrollPos来改变卷动方块的位置。
当把鼠标的光标放在卷动方块上并按住鼠标键时,您就可以移动卷动方块。这样就产生了带有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消息更好一些(但更困难),对于某些型态的数据,您的程序可能很难跟上产生的消息。
Windows.inc表头文件还包括SB_TOP、SB_BOTTOM、SB_LEFT和SB_RIGHT通知码,指出滚动条已经被移到了它的最小或最大位置。然而,对于作为应用程序窗口一部分而建立的滚动条来说,永远不会接收到这些通知码。
在滚动条范围使用32位的值也是有效的,尽管这不常见。然而,wParam的高字组只有16位的大小,它不能适当地指出SB_THUMBTRACK和SB_THUMBPOSITION操作的位置。在这种情况下,需要使用GetScrollInfo函数(在下面描述)来得到信息。
在SYSMETS中加入卷动功能
前面的说明已经很详尽了,现在,要将那些东西动手做做看了。让我们开始时简单些,从垂直卷动着手,因为我们实在太需要垂直卷动了,而暂时还可以不用水平卷动。SYSMET2如程序4-3所示。这个程序可能是滚动条的最简单的应用。
程序4-3 SYSMETS2.ASM
(程序大部分都同SYSMETS1.ASM,下面的程序我们特别省略了.data?段之前的,调试的时候请拷贝过来就可以了)
新的CreateWindow呼叫在第三个参数中包含了WS_VSCROLL窗口样式,从而在窗口中加入了垂直滚动条,其窗口样式为:
WS_OVERLAPPEDWINDOW or WS_VSCROLL
WndProc窗口消息处理程序在处理WM_CREATE消息时增加了两条叙述,以设置垂直滚动条的范围和初始位置:
invoke SetScrollRange,hwnd, SB_VERT, 0, NUMLINES - 1, FALSE
invoke SetScrollPos,hwnd, SB_VERT, iVscrollPos, TRUE
sysmetrics结构具有NUMLINES行文字,所以滚动条范围被设定为0至NUMLINES-1。滚动条的每个位置对应于在显示区域顶部显示的一个文字行。如果卷动方块的位置为0,则第一行会被放置在显示区域的顶部。如果位置大于0,其它行就会出现在显示区域的顶部。当位置为NUMLINES-1时,则最后一行文字出现在显示区域的顶部。
为了有助于处理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使整个窗口无效。
InvalidateRect呼叫产生一个WM_PAINT消息。SYSMETS1在处理WM_PAINT消息时,每一行的y坐标计算公式为:
cyChar * i
在SYSMETS2中,计算公式为:
cyChar * (i - iVscrollPos)
循环仍然显示NUMLINES行文字,但是对于非零值的iVscrollPos是负数。程序实际上在显示区域以外显示这些文字行。当然,Windows不会显示这些行,因此屏幕显得干净和漂亮。
前面说过,我们一开始不想弄得太复杂,这样的程序代码很浪费,效率很低。下面我们对此加以修改,但是先要考虑在WM_VSCROLL消息之后更新显示区域的方法。
绘图程序的组织
在处理完滚动条消息后,SYSMETS2不更新显示区域,相反,它呼叫InvalidateRect使显示区域失效。这导致Windows将一个WM_PAINT消息放入消息队列中。
最好能使Windows程序在响应WM_PAINT消息时完成所有的显示区域绘制功能。因为程序必须在一接收到WM_PAINT消息时就更新整个显示区域,如果在程序的其它部分也绘制的话,将很可能使程序代码重复。
首先,您可能对这种拐弯抹角的方式感到厌烦。在Windows的早期,因为这种方式与文字模式的程序设计差别太大,程序写作者感到这种概念很难理解。并且,程序要不断地通过马上绘制画面来响应键盘和鼠标。这样做既方便又有效,但是在很多情况下,这完全不必要。当您掌握了在响应WM_PAINT消息时积累绘制显示区域所需要的全部信息的原则之后,会对这种结果感到满意的。
如同SYSMETS2示范的,程序仍然需要在处理非WM_PAINT消息时更新特定的显示区域,使用InvalidateRect就很方便,您可以用它使显示区域内的特定矩形或者整个显示区域失效。
只将窗口显示区域标记为无效以产生WM_PAINT消息,对于某些应用程序来说也许不是完全令人满意的选择。在呼叫InvalidateRect之后,Windows将WM_PAINT消息放入消息队列中,最后由窗口消息处理程序处理它。然而,Windows将WM_PAINT消息当成低优先级消息,如果系统有许多其它的动作正在发生,那么也许会让您等待一会儿工夫。这时,当对话框消失时,将会出现一些空白的「洞」,程序仍然等待更新它的窗口。
如果您希望立即更新无效区域,可以在呼叫InvalidateRect之后呼叫UpdateWindow:
UpdateWindow (hwnd) ;
如果显示区域的任一部分无效,则UpdateWindow将导致Windows用WM_PAINT消息呼叫窗口消息处理程序(如果整个显示区域有效,则不呼叫窗口消息处理程序)。这一WM_PAINT消息不进入消息队列,直接由Windows呼叫窗口消息处理程序。窗口消息处理程序完成更新后立即退出,Windows将控制传回给程序中UpdateWindow呼叫之后的叙述。
您可能注意到,UpdateWindow与WinMain中用来产生第一个WM_PAINT消息的函数相同。最初建立窗口时,整个显示区域内容变为无效,UpdateWindow指示窗口消息处理程序绘制显示区域。
总结:随着经验的积累,你会慢慢知道Windows将要收到什么消息, 或 者逆需要做什么才能产生你要的效果。
建立更好的滚动
SYSMETS2动作良好,但它只是模仿其它程序中的滚动条,并且效率很低。很快我将示范一个新的版本,改进它的不足。也许最有趣的是这个新版本不使用目前所讨论的四个滚动条函数。相反,它将使用Win32 API中才有的新函数。
滚动条信息函数
一些资料中指出SetScrollRange、SetScrollPos、GetScrollRange和GetScrollPos函数是「过时的」,但这并不完全正确。这些函数在Windows 1.0中就出现了,在Win32 API中升级以处理32位参数。它们仍然具有良好的功能。而且,它们不与Windows程序设计中新函数相冲突,这就是我在此书中仍使用它们的原因。
Win32 API介绍的两个滚动条函数称作SetScrollInfo和GetScrollInfo。这些函数可以完成以前函数的全部功能,并增加了两个新特性。
第一个功能涉及卷动方块的大小。您可能注意到,卷动方块大小在SYSMETS2程序中是固定的。然而,在您可能使用到的一些Windows应用程序中,卷动方块大小与在窗口中显示的文件大小成比例。显示的大小称作「页面大小」。算法为:
可以使用SetScrollInfo来设置页面大小(从而设置了卷动方块的大小),如将要看到的SYSMETS3程序所示。
GetScrollInfo函数增加了第二个重要的功能,或者说它改进了目前API的不足。假设您要使用65,536或更大单位的范围,这在16位Windows中是不可能的。当然在Win32中,函数被定义为可接受32位参数,因此是没有问题的。(记住如果使用这样大的范围,卷动方块的实际物理位置数仍然由卷动列的图素大小限制)。然而,当使用SB_THUMBTRACK或SB_THUMBPOSITION通知码得到WM_VSCROLL或WM_HSCROLL消息时,只提供了16位数据来指出卷动方块的目前位置。通过GetScrollInfo函数可以取得真实的32位值。
SetScrollInfo和GetScrollInfo函数的语法是
像在其它滚动条函数中那样,iBar参数是SB_VERT或SB_HORZ,它还可以是用于滚动条控制的SB_CTL。SetScrollInfo的最后一个参数可以是TRUE或FALSE,指出了是否要Windows重新绘制计算了新信息后的滚动条。
两个函数的第三个参数是SCROLLINFO结构,定义为:
在程序中,可以定义如下的SCROLLINFO结构型态:
SCROLLINFO si ;
在呼叫SetScrollInfo或GetScrollInfo之前,必须将cbSize字段设定为结构的大小:
si.cbSize = sizeof (si) ;
或
si.cbSize = sizeof (SCROLLINFO) ;
逐渐熟悉Windows后,您就会发现另外几个结构像这个结构一样,第一个字段指出了结构大小。这个字段使将来的Windows版本可以扩充结构并添加新的功能,并且仍然与以前编译的版本兼容。
把fMask字段设定为一个以上以SIF前缀开头的旗标,并且可以使用位操作OR组合这些旗标。
SetScrollInfo函数使用SIF_RANGE旗标时,必须把nMin和nMax字段设定为所需的滚动条范围。GetScrollInfo函数使用SIF_RANGE旗标时,应把nMin和nMax字段设定为从函数传回的目前范围。
SIF_POS旗标也一样。当通过SetScrollInfo使用它时,必须把结构的nPos字段设定为所需的位置。可以通过GetScrollInfo使用SIF_POS旗标来取得目前位置。
使用SIF_PAGE旗标能够取得页面大小。用SetScrollInfo函数把nPage设定为所需的页面大小。GetScrollInfo使用SIF_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_TRACKPOS的组合。在WM_SIZE消息处理期间设置滚动条参数时,这是很方便的(在SetScrollInfo函数中指定SIF_TRACKPOS后,它会被忽略)。这在处理滚动条消息时也是很方便的。
卷动范围
在SYSMETS2中,卷动范围设置最小为0,最大为NUMLINES-1。当滚动条位置是0时,第一行信息显示在显示区域的顶部;当滚动条的位置是NUMLINES-1时,最后一行显示在显示区域的顶部,并且看不见其它行。
可以说SYSMETS2卷动范围太大。事实上只需把信息最后一行显示在显示区域的底部而不是顶部即可。我们可以对SYSMETS2作出一些修改以达到此点。当处理WM_CREATE消息时不设置滚动条范围,而是等到接收到WM_SIZE消息后再做此工作:
假定NUMLINES等于75,并假定特定窗口大小是:50(cyChar除以cyClient)。换句话说,我们有75行信息但只有50行可以显示在显示区域中。使用上面的两行程序代码,把范围设置最小为0,最大为25。当滚动条位置等于0时,程序显示0到49行。当滚动条位置等于1时,程序显示1到50行;并且当滚动条位置等于25(最大值)时,程序显示25到74行。很明显需要对程序的其它部分做出修改,但这是可行的。
新滚动条函数的一个好的功能是当使用与滚动条范围一样大的页面时,它已经为您做掉了一大堆杂事。可以像下面的程序代码一样使用SCROLLINFO结构和SetScrollInfo:
这样做之后,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只是让那个滚动条不能被使用,而不隐藏它。
新SYSMETS
SYSMETS3-此章中最后的SYSMETS程序版本-显示在程序4-4中。此版本使用SetScrollInfo和GetScrollInfo函数,添加左右卷动的水平滚动条,并能更有效地重画显示区域。
程序4-4 SYSMETS3
这个版本的程序仰赖Windows保存滚动条信息并做边界检查。在WM_VSCROLL和WM_HSCROLL处理的开始,它取得所有的滚动条信息,根据通知码调整位置,然后呼叫SetScrollInfo设置其位置。程序然后呼叫GetScrollInfo。如果该位置超出了SetScrollInfo呼叫的范围,则由Windows来纠正该位置并且在GetScrollInfo呼叫中传回正确的值。
SYSMETS3使用ScrollWindow函数在窗口的显示区域中卷动信息而不是重画它。虽然该函数很复杂(在新版本的Windows中已被更复杂的ScrollWindowEx所替代),SYSMETS3仍以相当简单的方式使用它。函数的第二个参数给出了水平卷动显示区域的数值,第三个参数是垂直卷动显示区域的数值,单位都是图素。
ScrollWindow的最后两个参数设定为NULL,这指出了要卷动整个显示区域。Windows自动把显示区域中未被卷动操作覆盖的矩形设为无效。这会产生WM_PAINT消息。再也不需要InvalidateRect了。注意ScrollWindow不是GDI函数,因为它不需设备内容句柄。它是少数几个非GDI的Windows函数之一,它可以改变窗口的显示区域外观。很特殊但不方便,它是随滚动条函数一起记载在文件中。
WM_HSCROLL处理拦截SB_THUMBPOSITION通知码并忽略SB_THUMBTRACK。因而,如果使用者在水平滚动条上拖动卷动方块,在使用者释放鼠标按钮之前,程序不会水平卷动窗口的内容。
WM_VSCROLL的方法与之不同:程序拦截SB_THUMBTRACK消息并忽略SB_THUMBPOSITION。因而,程序随使用者在垂直滚动条上拖动卷动方块而垂直地滚动内容。这种想法很好,但应注意:一旦使用者发现程序会立即响应拖动的卷动方块,他们就会不断地来回拖动卷动方块。幸运的是现在的PC快得可以胜任这种严酷的测试。但是在较慢的机器上,可以考虑为GetSystemMetrics使用SB_SLOWMACHINE参数来替代这种处理。
加快WM_PAINT处理的一个方法由SYSMETS3展示:WM_PAINT处理程序确定无效区域中的文字行并仅仅重画这些行。当然,程序代码复杂一些,但速度很快。
不用鼠标怎么办
在Windows的早期,有大量的使用者不喜欢使用鼠标,而且,Windows自身也不要求必须有鼠标。虽然,没有鼠标的PC现在走上了单色显示器和点阵打印机的没落之路,但我仍然建议您编写可以使用键盘来产生与鼠标操作相同效果的程序,尤其对于像滚动条这样的基本操作对象更是如此。因为我们的键盘有一组光标移动键,所以应该实作同样的操作。
在后面几期,您将学习使用键盘和在SYSMETS3中增加键盘接口的方法。您可能会注意到,SYSMETS3似乎在通知码等于SB_TOP和SB_BOTTOM时处理了WM_VSCROLL消息。前面已经提到过,窗口消息处理程序不从滚动条接收这些消息,所以,目前这是多余的程序代码。当我们在后面再次回到这个程序时,您将会明白这样做的原因。
http://www.aogosoft.com/downpage.asp?mode=viewtext&id=163
滚动条是图形使用者接口中最好的功能之一,它很容易使用,而且提供了很好的视觉回馈效果。您可以使用滚动条显示任何东西--无论是文字、图形、表格、数据库记录、图像或是网页,只要它所需的空间超出了窗口的显示区域所能提供的空间,就可以使用滚动条。
滚动条既有垂直方向的(供上下移动),也有水平方向的(供左右移动)。使用者可以使用鼠标在滚动条两端的箭头上或者在箭头之间的区域中点一下,这时,「卷动方块」在卷动列内的移动位置与所显示的信息在整个文件中的近似相关位置成比例。使用者也可以用鼠标拖动卷动方块到特定的位置。图4-5显示了垂直滚动条的建议用法。
图4-5 垂直滚动条
有时,程序写作者对卷动概念很难理解,因为他们的观点与使用者的观点不同:使用者向下卷动是想看到文件较下面的部分;但是,程序实际上是将文件相对于显示窗口向上移动。Windows文件和表头文件标识符是依据使用者的观点:向上卷动意味着朝文件的开头移动;向下卷动意味着朝文件尾部移动。
很容易在应用程序中包含水平或者垂直的滚动条,程序写作者只需要在CreateWindow的第三个参数中包括窗口样式(WS)标识符WS_VSCROLL(垂直卷动)和/或WS_HSCROLL(水平卷动)即可。这些卷动列通常放在窗口的右部和底部,伸展为显示区域的整个长度或宽度。显示区域不包含卷动列所占据的空间。对于特定的显示驱动程序和显示分辨率,垂直卷动列的宽度和水平卷动列的高度是恒定的。如果需要这些值,可以使用GetSystemMetrics呼叫来取得(如前面的程序那样)。
Windows负责处理对滚动条的所有鼠标操作,但是,窗口滚动条没有自动的键盘接口。如果想用光标键来完成卷动功能,则必须提供这方面的程序代码(我们将在下一期另一个版本的SYSMETS程序中做到这一点)。
滚动条的范围和位置
每个滚动条均有一个相关的「范围」(这是一对整数,分别代表最小值和最大值)和「位置」(它是卷动方块在此范围内的位置)。当卷动方块在卷动列的顶部(或左部)时,卷动方块的位置是范围的最小值;在卷动列的底部(或右部)时,卷动方块的位置是范围的最大值。
在内定情况下,滚动条的范围是从0(顶部或左部)至100(底部或右部),但将范围改变为更方便于程序的数值也是很容易的:
SetScrollRange (hwnd, iBar, iMin, iMax, bRedraw) ;
参数iBar为SB_VERT或者SB_HORZ,iMin和iMax分别是范围的最小值和最大值。如果想要Windows根据新范围重画滚动条,则设置bRedraw为TRUE(如果在呼叫SetScrollRange后,呼叫了影响滚动条位置的其它函数,则应该将bRedraw设定为FALSE以避免过多的重画)。
卷动方块的位置总是离散的整数值。例如,范围为0至4的滚动条具有5个卷动方块位置,如图4-6所示。
图4-6 具有5个卷动方块位置的卷动列
您可以使用SetScrollPos在滚动条范围内设置新的卷动方块位置:
SetScrollPos (hwnd, iBar, iPos, bRedraw) ;
参数iPos是新位置,它必须在iMin至iMax的范围内。Windows提供了类似的函数(GetScrollRange和GetScrollPos)来取得滚动条的目前范围和位置。
在程序内使用滚动条时,程序写作者与Windows共同负责维护滚动条以及更新卷动方块的位置。下面是Windows对滚动条的处理:
处理所有滚动条鼠标事件 当使用者在滚动条内单击鼠标时,提供一种「反相显示」的闪烁 当使用者在滚动条内拖动卷动方块时,移动卷动方块 为包含滚动条窗口的窗口消息处理程序发送滚动条消息 |
初始化滚动条的范围和位置 处理窗口消息处理程序的滚动条消息 更新滚动条内卷动方块的位置 更改显示区域的内容以响应对滚动条的更改 |
滚动条消息
在用鼠标单击滚动条或者拖动卷动方块时,Windows给窗口消息处理程序发送WM_VSCROLL(供上下移动)和WM_HSCROLL(供左右移动)消息。在滚动条上的每个鼠标动作都至少产生两个消息,一条在按下鼠标按钮时产生,一条在释放按钮时产生。
和所有的消息一样,WM_VSCROLL和WM_HSCROLL也带有wParam和lParam消息参数。对于来自作为窗口的一部分而建立的滚动条消息,您可以忽略lParam;它只用于作为子窗口而建立的滚动条(通常在对话框内)。
wParam消息参数被分为一个低字组和一个高字组。wParam的低字组是一个数值,它指出了鼠标对滚动条进行的操作。这个数值被看作一个「通知码」。通知码的值由以SB(代表「scroll bar(滚动条)」)开头的标识符定义。以下是在Windows.inc中定义的通知码:
SB_HORZ equ 0 SB_VERT equ 1 SB_CTL equ 2 SB_BOTH equ 3 SB_LINEUP equ 0 SB_LINELEFT equ 0 SB_LINEDOWN equ 1 SB_LINERIGHT equ 1 SB_PAGEUP equ 2 SB_PAGELEFT equ 2 SB_PAGEDOWN equ 3 SB_PAGERIGHT equ 3 SB_THUMBPOSITION equ 4 SB_THUMBTRACK equ 5 SB_TOP equ 6 SB_LEFT equ 6 SB_BOTTOM equ 7 SB_RIGHT equ 7 SB_ENDSCROLL equ 8 |
图4-7 用于滚动条消息的wParam值的标识符
如果在滚动条的各个部位按住鼠标键,程序就能收到多个滚动条消息。当释放鼠标键后,程序会收到一个带有SB_ENDSCROLL通知码的消息。一般可以忽略这个消息,Windows不会去改变卷动方块的位置,而您可以在程序中呼叫SetScrollPos来改变卷动方块的位置。
当把鼠标的光标放在卷动方块上并按住鼠标键时,您就可以移动卷动方块。这样就产生了带有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消息更好一些(但更困难),对于某些型态的数据,您的程序可能很难跟上产生的消息。
Windows.inc表头文件还包括SB_TOP、SB_BOTTOM、SB_LEFT和SB_RIGHT通知码,指出滚动条已经被移到了它的最小或最大位置。然而,对于作为应用程序窗口一部分而建立的滚动条来说,永远不会接收到这些通知码。
在滚动条范围使用32位的值也是有效的,尽管这不常见。然而,wParam的高字组只有16位的大小,它不能适当地指出SB_THUMBTRACK和SB_THUMBPOSITION操作的位置。在这种情况下,需要使用GetScrollInfo函数(在下面描述)来得到信息。
在SYSMETS中加入卷动功能
前面的说明已经很详尽了,现在,要将那些东西动手做做看了。让我们开始时简单些,从垂直卷动着手,因为我们实在太需要垂直卷动了,而暂时还可以不用水平卷动。SYSMET2如程序4-3所示。这个程序可能是滚动条的最简单的应用。
程序4-3 SYSMETS2.ASM
(程序大部分都同SYSMETS1.ASM,下面的程序我们特别省略了.data?段之前的,调试的时候请拷贝过来就可以了)
.DATA? hInstance dd ? cxChar dd ? cxCaps dd ? cyChar dd ? cyClient dd ? iVscrollPos dd ? .CODE START: ;从这里开始执行 invoke GetModuleHandle,NULL mov hInstance,eax invoke WinMain,hInstance,NULL,NULL,SW_SHOWDEFAULT invoke ExitProcess,0 WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,iCmdShow:DWORD LOCAL wndclass :WNDCLASSEX LOCAL msg :MSG local hWnd :HWND mov wndclass.cbSize,sizeof WNDCLASSEX mov wndclass.style,CS_HREDRAW or CS_VREDRAW mov wndclass.lpfnWndProc,offset WndProc mov wndclass.cbClsExtra,0 mov wndclass.cbWndExtra,0 push hInst pop wndclass.hInstance invoke LoadIcon,NULL,IDI_APPLICATION mov wndclass.hIcon,eax invoke LoadCursor,NULL,IDC_ARROW mov wndclass.hCursor,eax invoke GetStockObject,WHITE_BRUSH mov wndclass.hbrBackground,EAX mov wndclass.lpszMenuName,NULL mov wndclass.lpszClassName,offset szAppName mov wndclass.hIconSm,0 invoke RegisterClassEx, ADDR wndclass .if (EAX==0) invoke MessageBox,NULL, CTXT("This program requires Windows NT!"), addr szAppName,MB_ICONERROR ret .endif invoke CreateWindowEx, NULL, ADDR szAppName, ;window class name CTXT("Get System Metrics No. 2"), ;window caption WS_OVERLAPPEDWINDOW, ;window style CW_USEDEFAULT, ;initial x position CW_USEDEFAULT, ;initial y position CW_USEDEFAULT, ;initial x size CW_USEDEFAULT, ;initial y size NULL, ;parent window handle NULL, ;window menu handle hInstance, ;program instance handle NULL ;creation parameters mov hWnd,eax invoke ShowWindow,hWnd,iCmdShow invoke UpdateWindow,hWnd StartLoop: invoke GetMessage,ADDR msg,NULL,0,0 cmp eax, 0 je ExitLoop invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg jmp StartLoop ExitLoop: mov eax,msg.wParam ret WinMain endp WndProc proc hwnd:DWORD,message:DWORD,wParam :DWORD,lParam :DWORD LOCAL hdc :HDC LOCAL i :DWORD LOCAL ps :PAINTSTRUCT LOCAL szBuffer[10] :BYTE LOCAL tm :TEXTMETRIC LOCAL y_Pos,x_Caps :DWORD .if message==WM_CREATE invoke GetDC,hwnd mov hdc,eax invoke GetTextMetrics,hdc,addr tm mov eax,tm.tmAveCharWidth mov cxChar,eax ;cxChar = tm.tmAveCharWidth mov eax,2 ;cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 test DWORD ptr tm.tmPitchAndFamily,1 jz @f inc eax @@: push eax mov eax,cxChar pop ebx mul ebx shr eax,1 mov cxCaps,eax mov eax,tm.tmHeight ;cyChar = tm.tmHeight + tm.tmExternalLeading add eax,DWORD ptr tm.tmExternalLeading mov cyChar,eax invoke ReleaseDC,hwnd, hdc invoke SetScrollRange,hwnd, SB_VERT, 0, NUMLINES - 1, FALSE invoke SetScrollPos,hwnd, SB_VERT, iVscrollPos, TRUE ret .elseif message == WM_SIZE mov eax,lParam shr eax,16 mov cyClient,eax;cyClient = HIWORD(lParam) ret .elseif message == WM_VSCROLL mov eax,wParam and eax,0FFFFh ;LOWORD (wParam) .if eax==SB_LINEUP dec iVscrollPos ;iVscrollPos -= 1 .elseif eax==SB_LINEDOWN inc iVscrollPos ;iVscrollPos += 1 .elseif eax==SB_PAGEUP xor edx,edx ;iVscrollPos -= cyClient / cyChar mov eax,cyClient div cyChar sub iVscrollPos,eax .elseif ax==SB_PAGEDOWN xor edx,edx ;iVscrollPos += cyClient / cyChar mov eax,cyClient div cyChar add iVscrollPos,eax .elseif ax==SB_THUMBPOSITION mov eax,wParam ;iVscrollPos = HIWORD (wParam) shr eax,16 mov iVscrollPos,eax .endif mov eax,iVscrollPos ;min (iVscrollPos, NUMLINES - 1) cmp eax,NUMLINES-1 jNg @f ;当iVscrollPos>NUMLINES-1的时候跳转 ;考虑一下用gb比较是否可以呢?:) mov eax,(NUMLINES-1) @@: cmp eax,0 ;iVscrollPos = max (0, min (iVscrollPos, NUMLINES - 1)) jg @f xor eax,eax @@: mov iVscrollPos,eax ;我都是使用类似于下面的语句调试整个程序中 ;有兴趣的读者可以调试一下看看 ;invoke wsprintf,addr szBuffer,CTEXT("%d"),eax ;invoke MessageBox,hwnd,addr szBuffer,NULL,NULL ;if (iVscrollPos != GetScrollPos (hwnd, SB_VERT)) invoke GetScrollPos,hwnd,SB_VERT .if iVscrollPos != eax invoke SetScrollPos,hwnd, SB_VERT, iVscrollPos, TRUE invoke InvalidateRect,hwnd, NULL, TRUE .endif ret .elseif message == WM_PAINT invoke BeginPaint,hwnd,addr ps mov hdc,eax mov DWORD ptr i,0 lea esi,sysmetrics add esi,4 ;指向后面的字符串的地址 @@: mov ebx,i sub ebx,iVscrollPos mov eax,cyChar ;y = cyChar * (i - iVscrollPos) mul ebx mov y_Pos,eax mov edi,[esi] ;esi指向字符串的地址 ;edi指向字符串 invoke lstrlen,edi ;取字符串长度 mov ebx,eax ;TextOut (hdc, 0,cyChar * i,sysmetrics[i].szLabel,lstrlen (sysmetrics[i].szLabel)) invoke TextOut,hdc,0,y_Pos,edi,ebx add esi,4 mov ebx,22 ;x_Caps=cxCaps*22 mov eax,cxCaps mul ebx mov x_Caps,eax mov edi,[esi] ;指向一个字符串地址 invoke lstrlen,edi mov ebx,eax ;TextOut (hdc, 22 * cxCaps, cyChar * i,sysmetrics[i].szDesc,lstrlen (sysmetrics[i].szDesc)) invoke TextOut,hdc,x_Caps,y_Pos,edi,ebx invoke SetTextAlign,hdc,TA_RIGHT or TA_TOP sub esi,8 ;x_Caps=22 * cxCaps + 40 * cxChar mov eax,cxChar mov ebx,40 mul ebx add x_Caps,eax mov edi,[esi] ;edi=sysmetrics[i].iIndex invoke GetSystemMetrics,edi invoke wsprintf,addr szBuffer,CTXT("%5d"),eax mov ebx,eax invoke TextOut,hdc,x_Caps,y_Pos,addr szBuffer,ebx invoke SetTextAlign,hdc,TA_LEFT or TA_TOP inc i add esi,16 cmp DWORD ptr i,NUMLINES jNz @b invoke EndPaint,hwnd,addr ps ret .elseif message == WM_DESTROY invoke PostQuitMessage,NULL .endif invoke DefWindowProc,hwnd, message, wParam, lParam ret WndProc endp END START |
WS_OVERLAPPEDWINDOW or WS_VSCROLL
WndProc窗口消息处理程序在处理WM_CREATE消息时增加了两条叙述,以设置垂直滚动条的范围和初始位置:
invoke SetScrollRange,hwnd, SB_VERT, 0, NUMLINES - 1, FALSE
invoke SetScrollPos,hwnd, SB_VERT, iVscrollPos, TRUE
sysmetrics结构具有NUMLINES行文字,所以滚动条范围被设定为0至NUMLINES-1。滚动条的每个位置对应于在显示区域顶部显示的一个文字行。如果卷动方块的位置为0,则第一行会被放置在显示区域的顶部。如果位置大于0,其它行就会出现在显示区域的顶部。当位置为NUMLINES-1时,则最后一行文字出现在显示区域的顶部。
为了有助于处理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使整个窗口无效。
InvalidateRect呼叫产生一个WM_PAINT消息。SYSMETS1在处理WM_PAINT消息时,每一行的y坐标计算公式为:
cyChar * i
在SYSMETS2中,计算公式为:
cyChar * (i - iVscrollPos)
循环仍然显示NUMLINES行文字,但是对于非零值的iVscrollPos是负数。程序实际上在显示区域以外显示这些文字行。当然,Windows不会显示这些行,因此屏幕显得干净和漂亮。
前面说过,我们一开始不想弄得太复杂,这样的程序代码很浪费,效率很低。下面我们对此加以修改,但是先要考虑在WM_VSCROLL消息之后更新显示区域的方法。
绘图程序的组织
在处理完滚动条消息后,SYSMETS2不更新显示区域,相反,它呼叫InvalidateRect使显示区域失效。这导致Windows将一个WM_PAINT消息放入消息队列中。
最好能使Windows程序在响应WM_PAINT消息时完成所有的显示区域绘制功能。因为程序必须在一接收到WM_PAINT消息时就更新整个显示区域,如果在程序的其它部分也绘制的话,将很可能使程序代码重复。
首先,您可能对这种拐弯抹角的方式感到厌烦。在Windows的早期,因为这种方式与文字模式的程序设计差别太大,程序写作者感到这种概念很难理解。并且,程序要不断地通过马上绘制画面来响应键盘和鼠标。这样做既方便又有效,但是在很多情况下,这完全不必要。当您掌握了在响应WM_PAINT消息时积累绘制显示区域所需要的全部信息的原则之后,会对这种结果感到满意的。
如同SYSMETS2示范的,程序仍然需要在处理非WM_PAINT消息时更新特定的显示区域,使用InvalidateRect就很方便,您可以用它使显示区域内的特定矩形或者整个显示区域失效。
只将窗口显示区域标记为无效以产生WM_PAINT消息,对于某些应用程序来说也许不是完全令人满意的选择。在呼叫InvalidateRect之后,Windows将WM_PAINT消息放入消息队列中,最后由窗口消息处理程序处理它。然而,Windows将WM_PAINT消息当成低优先级消息,如果系统有许多其它的动作正在发生,那么也许会让您等待一会儿工夫。这时,当对话框消失时,将会出现一些空白的「洞」,程序仍然等待更新它的窗口。
如果您希望立即更新无效区域,可以在呼叫InvalidateRect之后呼叫UpdateWindow:
UpdateWindow (hwnd) ;
如果显示区域的任一部分无效,则UpdateWindow将导致Windows用WM_PAINT消息呼叫窗口消息处理程序(如果整个显示区域有效,则不呼叫窗口消息处理程序)。这一WM_PAINT消息不进入消息队列,直接由Windows呼叫窗口消息处理程序。窗口消息处理程序完成更新后立即退出,Windows将控制传回给程序中UpdateWindow呼叫之后的叙述。
您可能注意到,UpdateWindow与WinMain中用来产生第一个WM_PAINT消息的函数相同。最初建立窗口时,整个显示区域内容变为无效,UpdateWindow指示窗口消息处理程序绘制显示区域。
总结:随着经验的积累,你会慢慢知道Windows将要收到什么消息, 或 者逆需要做什么才能产生你要的效果。
建立更好的滚动
SYSMETS2动作良好,但它只是模仿其它程序中的滚动条,并且效率很低。很快我将示范一个新的版本,改进它的不足。也许最有趣的是这个新版本不使用目前所讨论的四个滚动条函数。相反,它将使用Win32 API中才有的新函数。
滚动条信息函数
一些资料中指出SetScrollRange、SetScrollPos、GetScrollRange和GetScrollPos函数是「过时的」,但这并不完全正确。这些函数在Windows 1.0中就出现了,在Win32 API中升级以处理32位参数。它们仍然具有良好的功能。而且,它们不与Windows程序设计中新函数相冲突,这就是我在此书中仍使用它们的原因。
Win32 API介绍的两个滚动条函数称作SetScrollInfo和GetScrollInfo。这些函数可以完成以前函数的全部功能,并增加了两个新特性。
第一个功能涉及卷动方块的大小。您可能注意到,卷动方块大小在SYSMETS2程序中是固定的。然而,在您可能使用到的一些Windows应用程序中,卷动方块大小与在窗口中显示的文件大小成比例。显示的大小称作「页面大小」。算法为:
可以使用SetScrollInfo来设置页面大小(从而设置了卷动方块的大小),如将要看到的SYSMETS3程序所示。
GetScrollInfo函数增加了第二个重要的功能,或者说它改进了目前API的不足。假设您要使用65,536或更大单位的范围,这在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) ; |
两个函数的第三个参数是SCROLLINFO结构,定义为:
SCROLLINFO STRUCT cbSize DWORD ? ; set to sizeof (SCROLLINFO) fMask DWORD ? ; values to set or get nMin DWORD ? ; minimum range value nMax DWORD ? ; maximum range value nPage DWORD ? ; page size nPos DWORD ? ; current position nTrackPos DWORD ? ; current tracking position SCROLLINFO ENDS |
SCROLLINFO si ;
在呼叫SetScrollInfo或GetScrollInfo之前,必须将cbSize字段设定为结构的大小:
si.cbSize = sizeof (si) ;
或
si.cbSize = sizeof (SCROLLINFO) ;
逐渐熟悉Windows后,您就会发现另外几个结构像这个结构一样,第一个字段指出了结构大小。这个字段使将来的Windows版本可以扩充结构并添加新的功能,并且仍然与以前编译的版本兼容。
把fMask字段设定为一个以上以SIF前缀开头的旗标,并且可以使用位操作OR组合这些旗标。
SetScrollInfo函数使用SIF_RANGE旗标时,必须把nMin和nMax字段设定为所需的滚动条范围。GetScrollInfo函数使用SIF_RANGE旗标时,应把nMin和nMax字段设定为从函数传回的目前范围。
SIF_POS旗标也一样。当通过SetScrollInfo使用它时,必须把结构的nPos字段设定为所需的位置。可以通过GetScrollInfo使用SIF_POS旗标来取得目前位置。
使用SIF_PAGE旗标能够取得页面大小。用SetScrollInfo函数把nPage设定为所需的页面大小。GetScrollInfo使用SIF_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_TRACKPOS的组合。在WM_SIZE消息处理期间设置滚动条参数时,这是很方便的(在SetScrollInfo函数中指定SIF_TRACKPOS后,它会被忽略)。这在处理滚动条消息时也是很方便的。
卷动范围
在SYSMETS2中,卷动范围设置最小为0,最大为NUMLINES-1。当滚动条位置是0时,第一行信息显示在显示区域的顶部;当滚动条的位置是NUMLINES-1时,最后一行显示在显示区域的顶部,并且看不见其它行。
可以说SYSMETS2卷动范围太大。事实上只需把信息最后一行显示在显示区域的底部而不是顶部即可。我们可以对SYSMETS2作出一些修改以达到此点。当处理WM_CREATE消息时不设置滚动条范围,而是等到接收到WM_SIZE消息后再做此工作:
iVscrollMax = max (0, NUMLINES - cyClient / cyChar) ; SetScrollRange (hwnd, SB_VERT, 0, iVscrollMax, TRUE) ; |
新滚动条函数的一个好的功能是当使用与滚动条范围一样大的页面时,它已经为您做掉了一大堆杂事。可以像下面的程序代码一样使用SCROLLINFO结构和SetScrollInfo:
si.cbSize = sizeof (SCROLLINFO) ; si.cbMask = SIF_RANGE | SIF_PAGE ; si.nMin = 0 ; si.nMax = NUMLINES - 1 ; si.nPage = cyClient / cyChar ; SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ; |
当页面大小与滚动条范围一样大时,会发生什么情况呢?在这个例子中,就是nPage等于75或更大的情况。Windows通常隐藏滚动条,因为它并不需要。如果不想隐藏滚动条,可在呼叫SetScrollInfo时使用SIF_DISABLENOSCROLL,Windows只是让那个滚动条不能被使用,而不隐藏它。
新SYSMETS
SYSMETS3-此章中最后的SYSMETS程序版本-显示在程序4-4中。此版本使用SetScrollInfo和GetScrollInfo函数,添加左右卷动的水平滚动条,并能更有效地重画显示区域。
程序4-4 SYSMETS3
;MASMPlus 代码模板 - 普通的 Windows 程序代码 .386 .Model Flat, StdCall Option Casemap :None Include windows.inc Include user32.inc Include kernel32.inc Include gdi32.inc Include winmm.inc includelib gdi32.lib IncludeLib user32.lib IncludeLib kernel32.lib IncludeLib winmm.lib include macro.asm WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD NUMLINES equ (sysmetricsEnd - sysmetrics) / 4 /3 .DATA szAppName db "SysMets3",0 (篇幅限制,中部省略。同上面的程序定义相同) .DATA? hInstance dd ? cxChar dd ? cxCaps dd ? cyChar dd ? cxClient dd ? cyClient dd ? iMaxWidth dd ? .CODE START: ;从这里开始执行 invoke GetModuleHandle,NULL mov hInstance,eax invoke WinMain,hInstance,NULL,NULL,SW_SHOWDEFAULT invoke ExitProcess,0 WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,iCmdShow:DWORD LOCAL wndclass :WNDCLASSEX LOCAL msg :MSG local hWnd :HWND mov wndclass.cbSize,sizeof WNDCLASSEX mov wndclass.style,CS_HREDRAW or CS_VREDRAW mov wndclass.lpfnWndProc,offset WndProc mov wndclass.cbClsExtra,0 mov wndclass.cbWndExtra,0 push hInst pop wndclass.hInstance invoke LoadIcon,NULL,IDI_APPLICATION mov wndclass.hIcon,eax invoke LoadCursor,NULL,IDC_ARROW mov wndclass.hCursor,eax invoke GetStockObject,WHITE_BRUSH mov wndclass.hbrBackground,EAX mov wndclass.lpszMenuName,NULL mov wndclass.lpszClassName,offset szAppName mov wndclass.hIconSm,0 invoke RegisterClassEx, ADDR wndclass .if (EAX==0) invoke MessageBox,NULL, CTXT("This program requires Windows NT!"), addr szAppName,MB_ICONERROR ret .endif invoke CreateWindowEx, NULL, ADDR szAppName, ;window class name CTXT("Get System Metrics No. 3"), ;window caption WS_OVERLAPPEDWINDOW, ;window style CW_USEDEFAULT, ;initial x position CW_USEDEFAULT, ;initial y position CW_USEDEFAULT, ;initial x size CW_USEDEFAULT, ;initial y size NULL, ;parent window handle NULL, ;window menu handle hInstance, ;program instance handle NULL ;creation parameters mov hWnd,eax invoke ShowWindow,hWnd,iCmdShow invoke UpdateWindow,hWnd StartLoop: invoke GetMessage,ADDR msg,NULL,0,0 cmp eax, 0 je ExitLoop invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg jmp StartLoop ExitLoop: mov eax,msg.wParam ret WinMain endp WndProc proc hwnd:DWORD,message:DWORD,wParam :DWORD,lParam :DWORD LOCAL hdc :HDC LOCAL i,x,y,iVertPos,iHorzPos, iPaintBeg, iPaintEnd :DWORD LOCAL ps :PAINTSTRUCT LOCAL ssi :SCROLLINFO LOCAL szBuffer[10] :BYTE LOCAL tm :TEXTMETRIC LOCAL y_Pos,x_Caps :DWORD .if message==WM_CREATE invoke GetDC,hwnd mov hdc,eax invoke GetTextMetrics,hdc,addr tm mov eax,tm.tmAveCharWidth mov cxChar,eax ;cxChar = tm.tmAveCharWidth mov eax,2 ;cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 test DWORD ptr tm.tmPitchAndFamily,1 jz @f inc eax @@: push eax mov eax,cxChar pop ebx mul ebx shr eax,1 mov cxCaps,eax mov eax,tm.tmHeight ;cyChar = tm.tmHeight + tm.tmExternalLeading add eax,DWORD ptr tm.tmExternalLeading mov cyChar,eax invoke ReleaseDC,hwnd, hdc ;Save the width of the three columns mov eax,cxChar ;iMaxWidth = 40 * cxChar + 22 * cxCaps mov ecx,40 mul ecx mov ebx,eax mov eax,cxCaps mov ecx,22 mul ecx add eax,ebx mov iMaxWidth,eax ret .elseif message == WM_SIZE mov eax,lParam ;cxClient = LOWORD (lParam) and eax,0FFFFh mov cxClient,eax mov eax,lParam shr eax,16 mov cyClient,eax ;cyClient = HIWORD (lParam) mov eax,sizeof ssi mov ssi.cbSize,eax mov DWORD ptr [ssi.fMask],SIF_RANGE or SIF_PAGE mov DWORD ptr [ssi.nMin],0 mov DWORD ptr [ssi.nMax],NUMLINES - 1 xor edx,edx ;si.nPage = cyClient / cyChar mov eax,cyClient mov ecx,cyChar div ecx mov DWORD ptr [ssi.nPage],eax ;Set horizontal scroll bar range and page size invoke SetScrollInfo,hwnd,SB_VERT,addr ssi,TRUE mov eax,sizeof ssi mov ssi.cbSize,eax mov DWORD ptr [ssi.fMask],SIF_RANGE or SIF_PAGE mov DWORD ptr [ssi.nMin],0 xor edx,edx ; si.nMax = 2 + iMaxWidth / cxChar mov eax,iMaxWidth mov ecx,cxChar div ecx add eax,2 mov DWORD ptr [ssi.nMax],eax xor edx,edx mov eax,cxClient mov ecx,cxChar div ecx mov DWORD ptr [ssi.nPage],eax ;Set horizontal scroll bar range and page size invoke SetScrollInfo,hwnd,SB_HORZ,addr ssi,TRUE ret .elseif message == WM_VSCROLL ;Get all the vertical scroll bar information mov eax,sizeof ssi mov ssi.cbSize,eax mov DWORD ptr [ssi.fMask],SIF_ALL invoke GetScrollInfo,hwnd,SB_VERT,addr ssi ;Save the position for comparison later on mov eax,ssi.nPos mov DWORD ptr iVertPos,eax mov eax,wParam and eax,0FFFFh ;LOWORD (wParam) .if eax ==SB_TOP mov eax,ssi.nMin mov ssi.nPos,eax .elseif eax==SB_BOTTOM mov eax,ssi.nMax mov ssi.nPos,eax .elseif eax==SB_LINEUP mov eax,ssi.nPos dec eax mov ssi.nPos,eax .elseif eax==SB_LINEDOWN mov eax,ssi.nPos inc eax mov ssi.nPos,eax .elseif eax==SB_PAGEUP mov eax,ssi.nPos sub eax,ssi.nPage mov ssi.nPos,eax .elseif eax==SB_PAGEDOWN mov eax,ssi.nPos add eax,ssi.nPage mov ssi.nPos,eax .elseif eax==SB_THUMBTRACK mov eax,ssi.nTrackPos mov ssi.nPos,eax .endif ;Set the position and then retrieve it. Due to adjustments ;by Windows it may not be the same as the value set. mov DWORD ptr [ssi.fMask],SIF_POS invoke SetScrollInfo,hwnd,SB_VERT,Addr ssi,TRUE invoke GetScrollInfo,hwnd,SB_VERT,Addr ssi ;If the position has changed, scroll the window and update mov eax,ssi.nPos .if eax!=iVertPos mov eax,iVertPos sub eax,ssi.nPos mov ecx,cyChar mul ecx invoke ScrollWindow,hwnd,0,eax,NULL,NULL invoke UpdateWindow,hwnd .endif ret .elseif message == WM_HSCROLL mov eax,sizeof ssi mov ssi.cbSize,eax mov DWORD ptr [ssi.fMask],SIF_ALL invoke GetScrollInfo,hwnd,SB_HORZ,addr ssi mov eax,ssi.nPos mov DWORD ptr iVertPos,eax mov eax,wParam and eax,0FFFFh ;LOWORD (wParam) .if eax ==SB_LINELEFT mov eax,ssi.nPos dec eax mov ssi.nPos,eax .elseif eax==SB_LINERIGHT mov eax,ssi.nPos inc eax mov ssi.nPos,eax .elseif eax==SB_PAGELEFT mov eax,ssi.nPos sub eax,DWORD ptr ssi.nPage mov ssi.nPos,eax .elseif eax==SB_PAGERIGHT mov eax,ssi.nPos add eax,DWORD ptr ssi.nPage mov ssi.nPos,eax .elseif ax==SB_THUMBTRACK mov eax,ssi.nTrackPos mov ssi.nPos,eax .endif mov DWORD ptr [ssi.fMask],SIF_POS invoke SetScrollInfo,hwnd,SB_HORZ,Addr ssi,TRUE invoke GetScrollInfo,hwnd,SB_HORZ,Addr ssi mov eax,iHorzPos sub eax,DWORD ptr [ssi.nPos] mov ecx,cxChar mul ecx mov ebx,eax mov eax,ssi.nPos .if eax!=iHorzPos invoke ScrollWindow,hwnd,0,ebx,NULL,NULL invoke UpdateWindow,hwnd .endif ret .elseif message == WM_PAINT invoke BeginPaint,hwnd,addr ps mov hdc,eax ;Get vertical scroll bar position mov eax,sizeof ssi mov ssi.cbSize,eax mov DWORD ptr [ssi.fMask],SIF_POS invoke GetScrollInfo,hwnd,SB_VERT,addr ssi mov eax,ssi.nPos mov iVertPos,eax ;Get horizontal scroll bar position invoke GetScrollInfo,hwnd,SB_HORZ,addr ssi mov eax,ssi.nPos mov iHorzPos ,eax ;Find painting limits xor edx,edx ;iPaintEnd=min(NUMLINES - 1,iVertPos + ps.rcPaint.bottom / cyChar) mov eax,ps.rcPaint.bottom mov ecx,cyChar div ecx add eax,iVertPos mov ecx,(NUMLINES-1) .if eax>ecx mov eax,NUMLINES-1 .endif mov iPaintEnd,eax xor edx,edx ;iPaintBeg = max (0, iVertPos + ps.rcPaint.top / cyChar) mov eax,ps.rcPaint.top mov ecx,cyChar div ecx add eax,iVertPos cmp eax,0 ;iVscrollPos = max (0, min (iVscrollPos, NUMLINES - 1)) jg @f xor eax,eax @@: mov iPaintBeg,eax mov i,eax ;i=iPaintBeg mov eax,iPaintBeg ;ESI point to sysmetrics[i] shl eax,2 lea esi,sysmetrics add esi,4 add esi,eax add esi,eax add esi,eax @@: mov eax,1 ; x = cxChar * (1 - iHorzPos) sub eax,iHorzPos mov ecx,cxChar mul ecx mov x,eax mov eax,i ; y = cyChar * (i - iVertPos) sub eax,iVertPos mov ecx,cyChar mul ecx mov y,eax mov edi,[esi] ;esi指向字符串的地址 ;edi指向字符串 invoke lstrlen,edi ;取字符串长度 mov ebx,eax ;TextOut (hdc, 0, cyChar * i,sysmetrics[i].szLabel,lstrlen (sysmetrics[i].szLabel)) invoke TextOut,hdc,x,y,edi,ebx add esi,4 mov edi,[esi] ;指向一个字符串地址 mov eax,cxCaps mov ecx,22 mul ecx mov ecx,DWORD ptr x add ecx,eax push ecx invoke lstrlen,edi mov ebx,eax pop ecx invoke TextOut,hdc,ecx,y,edi,ebx invoke SetTextAlign,hdc,TA_RIGHT or TA_TOP sub esi,8 mov edi,[esi] ;edi=sysmetrics[i].iIndex mov eax,cxCaps ;x_Caps=22 * cxCaps + 40 * cxChar mov ecx,22 mul ecx mov ebx,eax mov eax,cxChar mov ecx,40 mul ecx add eax,ebx add eax,DWORD ptr x mov x_Caps,eax invoke GetSystemMetrics,edi invoke wsprintf,addr szBuffer,CTXT("%5d"),eax ;wsprintf格式化后的长度会 ;作为返回值放在EAX中 mov ebx,eax invoke TextOut,hdc,x_Caps,y,addr szBuffer,ebx invoke SetTextAlign,hdc,TA_LEFT or TA_TOP inc i add esi,16 mov eax,iPaintEnd cmp DWORD ptr i,eax jbe @b invoke EndPaint,hwnd,addr ps ret .elseif message == WM_DESTROY invoke PostQuitMessage,NULL .endif invoke DefWindowProc,hwnd, message, wParam, lParam ret WndProc endp END START |
SYSMETS3使用ScrollWindow函数在窗口的显示区域中卷动信息而不是重画它。虽然该函数很复杂(在新版本的Windows中已被更复杂的ScrollWindowEx所替代),SYSMETS3仍以相当简单的方式使用它。函数的第二个参数给出了水平卷动显示区域的数值,第三个参数是垂直卷动显示区域的数值,单位都是图素。
ScrollWindow的最后两个参数设定为NULL,这指出了要卷动整个显示区域。Windows自动把显示区域中未被卷动操作覆盖的矩形设为无效。这会产生WM_PAINT消息。再也不需要InvalidateRect了。注意ScrollWindow不是GDI函数,因为它不需设备内容句柄。它是少数几个非GDI的Windows函数之一,它可以改变窗口的显示区域外观。很特殊但不方便,它是随滚动条函数一起记载在文件中。
WM_HSCROLL处理拦截SB_THUMBPOSITION通知码并忽略SB_THUMBTRACK。因而,如果使用者在水平滚动条上拖动卷动方块,在使用者释放鼠标按钮之前,程序不会水平卷动窗口的内容。
WM_VSCROLL的方法与之不同:程序拦截SB_THUMBTRACK消息并忽略SB_THUMBPOSITION。因而,程序随使用者在垂直滚动条上拖动卷动方块而垂直地滚动内容。这种想法很好,但应注意:一旦使用者发现程序会立即响应拖动的卷动方块,他们就会不断地来回拖动卷动方块。幸运的是现在的PC快得可以胜任这种严酷的测试。但是在较慢的机器上,可以考虑为GetSystemMetrics使用SB_SLOWMACHINE参数来替代这种处理。
加快WM_PAINT处理的一个方法由SYSMETS3展示:WM_PAINT处理程序确定无效区域中的文字行并仅仅重画这些行。当然,程序代码复杂一些,但速度很快。
不用鼠标怎么办
在Windows的早期,有大量的使用者不喜欢使用鼠标,而且,Windows自身也不要求必须有鼠标。虽然,没有鼠标的PC现在走上了单色显示器和点阵打印机的没落之路,但我仍然建议您编写可以使用键盘来产生与鼠标操作相同效果的程序,尤其对于像滚动条这样的基本操作对象更是如此。因为我们的键盘有一组光标移动键,所以应该实作同样的操作。
在后面几期,您将学习使用键盘和在SYSMETS3中增加键盘接口的方法。您可能会注意到,SYSMETS3似乎在通知码等于SB_TOP和SB_BOTTOM时处理了WM_VSCROLL消息。前面已经提到过,窗口消息处理程序不从滚动条接收这些消息,所以,目前这是多余的程序代码。当我们在后面再次回到这个程序时,您将会明白这样做的原因。
相关文章推荐
- XSBase255 -linux 启动过程描述 引用自http://www.linuxeden.com/forum/blog/index.php?op=ViewArticle&articleId=230&blogId=102509
- 在C#中使用MSHTML的高级支持接口(引自http://www.vckbase.com/document/viewdoc.asp?id=1018)
- 解决ASP(图像)上传漏洞的方法http://www.cnbruce.com/blog/showlog.asp?cat_id=26&log_id=942
- ASP.NET Internals: Viewstate and Page Life Cycle(http://www.codeproject.com/KB/aspnet/aspnetviewstatepagecycle.aspx)
- MFC 弹出窗口MessageBox的使用[转自http://www.bigasp.com.cn/blog/?action=show&id=51]
- VS .NET 2005中水晶报表的使用 http://www.tianyablog.com/blogger/post_show.asp?BlogID=116800&PostID=9731765
- 什么是SoC(源引自 http://www.elenchina.com/bbs.do?method=view&id=1440&code=plate02)
- http://www.fa.omron.com.cn/support/faqview.jsp?pid=0&id=3351&cid=null
- step one step(http://www.erpxp.com/bbs/dispbbs.asp?boardid=6&id=1033)
- http://www.kekecn.com/blog/default.asp?CateID=3&page=6
- 因ARP欺骗网站源文件被注入了代码http://www.cnbruce.com/blog/showlog.asp?cat_id=32&log_id=1191
- ASP.NET 缓存(七)--根据参数缓存页的版本http://www.opent.cn/?action=show&id=194
- winsock学习笔记(一)(http://www.vccode.net/article_view.asp?id=737)
- window.event对象详解 转自【http://www.jluvip.com/blog/article.asp?id=137】
- http://cdn.ac.nbutoj.com/Problem/view.xhtml?id=1184&&Elaine's Queue
- http://www.vckbase.com/document/viewdoc/?id=1473
- HTML在线编辑器的原理(转载整理)http://www.cnbruce.com/blog/showlog.asp?log_id=1168
- winsock学习笔记(二)(http://www.vccode.net/article_view.asp?id=738)
- 明基成人礼:巅峰战将营 http://www.cnvn.com.cn/Article/ShowArticle.asp?ArticleID=3172
- 正则表达式在UBB论坛中的应用(http://www.blueidea.com/bbs/newsdetail.asp?id=303249)