http://www.aogosoft.com/downpage.asp?mode=viewtext&id=163

来源:互联网 发布:linux服务器架设 编辑:程序博客网 时间:2024/06/06 11:02

来源于: 

http://www.aogosoft.com/downpage.asp?mode=viewtext&id=163

 

 

滚动条是图形使用者接口中最好的功能之一,它很容易使用,而且提供了很好的视觉回馈效果。您可以使用滚动条显示任何东西--无论是文字、图形、表格、数据库记录、图像或是网页,只要它所需的空间超出了窗口的显示区域所能提供的空间,就可以使用滚动条。

 

滚动条既有垂直方向的(供上下移动),也有水平方向的(供左右移动)。使用者可以使用鼠标在滚动条两端的箭头上或者在箭头之间的区域中点一下,这时,「卷动方块」在卷动列内的移动位置与所显示的信息在整个文件中的近似相关位置成比例。使用者也可以用鼠标拖动卷动方块到特定的位置。图4-5显示了垂直滚动条的建议用法。

    image067.png (49016 字节) 

    图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所示。

    image069.png (36817 字节) 

    图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

    包含LEFT和RIGHT的标识符用于水平滚动条,包含UP、DOWN、TOP和BOTTOM的标识符用于垂直滚动条。鼠标在滚动条的不同区域单击所产生的通知码如图4-7所示。

    image071.png (50844 字节) 

   图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

    新的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指示窗口消息处理程序绘制显示区域。

    image073.jpg (993 字节)
    总结:随着经验的积累,你会慢慢知道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) ;

    像在其它滚动条函数中那样,iBar参数是SB_VERT或SB_HORZ,它还可以是用于滚动条控制的SB_CTL。SetScrollInfo的最后一个参数可以是TRUE或FALSE,指出了是否要Windows重新绘制计算了新信息后的滚动条。

    两个函数的第三个参数是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结构型态:
    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) ;

    假定NUMLINES等于75,并假定特定窗口大小是:50(cyChar除以cyClient)。换句话说,我们有75行信息但只有50行可以显示在显示区域中。使用上面的两行程序代码,把范围设置最小为0,最大为25。当滚动条位置等于0时,程序显示0到49行。当滚动条位置等于1时,程序显示1到50行;并且当滚动条位置等于25(最大值)时,程序显示25到74行。很明显需要对程序的其它部分做出修改,但这是可行的。
    新滚动条函数的一个好的功能是当使用与滚动条范围一样大的页面时,它已经为您做掉了一大堆杂事。可以像下面的程序代码一样使用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) ;

    这样做之后,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

;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

    这个版本的程序仰赖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消息。前面已经提到过,窗口消息处理程序不从滚动条接收这些消息,所以,目前这是多余的程序代码。当我们在后面再次回到这个程序时,您将会明白这样做的原因。

原创粉丝点击