Полосы прокрутки

Мне кажется, что у читателя может возникнуть вопрос: что делать в тех случаях, когда нам не нужно масшабировать bitmap, но нужно иметь возможность просматривать все части изображения? Ответ заключен в названии данного раздела - использование полос прокрутки.

Давайте, уважаемый читатель, чуть-чуть изменим предыдущую программу для того, чтобы продемонстрировать использование полос прокрутки, и посмотрим, как она будет работать. Текст программы с внесенными изменениями:

.386
.model flat, stdcall
include win32.inc

extrn            BeginPaint:PROC
extrn            BitBlt:PROC
extrn            CreateCompatibleDC:PROC
extrn            CreateWindowExA:PROC
extrn            DefWindowProcA:PROC
extrn            DeleteDC:PROC
extrn            DeleteObject:PROC
extrn            DispatchMessageA:PROC
extrn            EndPaint:PROC
extrn            ExitProcess:PROC
extrn            GetClientRect:PROC
extrn            GetMessageA:PROC
extrn            GetModuleHandleA:PROC
extrn            GetObjectA:PROC
extrn            GetStockObject:PROC
extrn            InvalidateRect:PROC
extrn            LoadCursorA:PROC
extrn            LoadIconA:PROC
extrn            LoadImageA:PROC
extrn            PostQuitMessage:PROC
extrn            RegisterClassA:PROC
extrn            SelectObject:PROC
extrn            SetScrollPos:PROC
extrn            SetScrollRange:PROC
extrn            ShowWindow:PROC
extrn            StretchBlt:PROC
extrn            TranslateMessage:PROC
extrn            UpdateWindow:PROC

BITMAP struct
   bmType       dd ?
   bmWidth      dd ?
   bmHeight     dd ?
   bmWidthBytes dd ?
   bmPlanes     dw ?
   bmBitsPixel  dw ?
   bmBits       dd ?
BITMAP ends

LR_LOADFROMFILE  = 010h
SRCCOPY          = 00CC0020h
IMAGE_BITMAP     = 0

TRUE             = 1
FALSE            = 0


; Scroll Bar Constants
SB_HORZ          = 0
SB_VERT          = 1
SB_CTL           = 2
SB_BOTH          = 3

; Scroll Bar Commands
SB_LINEUP        = 0
SB_LINELEFT      = 0
SB_LINEDOWN      = 1
SB_LINERIGHT     = 1
SB_PAGEUP        = 2
SB_PAGELEFT      = 2
SB_PAGEDOWN      = 3
SB_PAGERIGHT     = 3
SB_THUMBPOSITION = 4
SB_THUMBTRACK    = 5
SB_TOP           = 6
SB_LEFT          = 6
SB_BOTTOM        = 7
SB_RIGHT         = 7
SB_ENDSCROLL     = 8

.data

newhwnd          dd 0
msg              MSGSTRUCT   
wc               WNDCLASS    

hDC              dd ?
hCompatibleDC    dd ?
PaintStruct      PAINTSTRUCT 
hBitmap          dd ?
hOldBitmap       dd ?
Rect             RECT 
Bitmap           BITMAP 
nHorizDifference dd 0
nVertDifference  dd 0
nHorizPosition   dd 0
nVertPosition    dd 0

hInstance        dd 0

szTitleName      db 'DCDemo', 0
szClassName      db 'ASMCLASS32',0
szImg            db 'IMG.BMP',0

.code
;-----------------------------------------------------------------------------
start:
        call    GetModuleHandleA, 0
        mov     [hInstance], eax

; initialize the WndClass structure
        mov     [wc.clsStyle], CS_HREDRAW + CS_VREDRAW
        mov     [wc.clsLpfnWndProc], offset DCDemoWndProc
        mov     [wc.clsCbClsExtra], 0
        mov     [wc.clsCbWndExtra], 0

        mov     eax, [hInstance]
        mov     [wc.clsHInstance], eax

        call    LoadIconA, 0, IDI_APPLICATION
        mov     [wc.clsHIcon], eax

        call    LoadCursorA, 0 ,IDC_ARROW
        mov     [wc.clsHCursor], eax

        call    GetStockObject, WHITE_BRUSH
        mov     [wc.clsHbrBackground], eax

        mov     [wc.clsLpszMenuName], 0
        mov     [wc.clsLpszClassName], offset szClassName

        call    RegisterClassA, offset wc

        call    CreateWindowExA, 0,offset szClassName,offset szTitleName, \
                WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, \
                CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,0,0, \
                [hInstance], 0

        mov     [newhwnd], eax

        call    ShowWindow, [newhwnd], SW_SHOWNORMAL
        call    UpdateWindow, [newhwnd]

msg_loop:
        call    GetMessageA, offset msg, 0, 0, 0
        .if     eax != 0
                call    TranslateMessage, offset msg
                call    DispatchMessageA, offset msg
                jmp     msg_loop
        .endif
        call    ExitProcess, [msg.msWPARAM]


;-----------------------------------------------------------------------------
DCDemoWndProc    proc uses ebx edi esi, hwnd:DWORD, wmsg:DWORD,\
                 wparam:DWORD, lparam:DWORD

        cmp     [wmsg], WM_CREATE
        je      wmcreate
        cmp     [wmsg], WM_PAINT
        je      wmpaint
        cmp     [wmsg], WM_VSCROLL
        je      wmvscroll
        cmp     [wmsg], WM_HSCROLL
        je      wmhscroll
        cmp     [wmsg], WM_DESTROY
        je      wmdestroy

        call    DefWindowProcA, [hwnd],[wmsg],[wparam],[lparam]
        jmp     finish

wmcreate:
        call    LoadImageA, 0,offset szImg,IMAGE_BITMAP, \
                0,0,LR_LOADFROMFILE
        mov     [hBitmap], eax
        mov     eax, 0
        jmp     finish

wmpaint:
        call    BeginPaint, [hwnd], offset PaintStruct
        mov     [hDC], eax

        call    GetObjectA, [hBitmap], size BITMAP, offset Bitmap

        call    CreateCompatibleDC, [hDC]
        mov     [hCompatibleDC], eax

        call    SelectObject, [hCompatibleDC], [hBitmap]
        mov     [hOldBitmap], eax

        call    GetClientRect, [hwnd], offset Rect

        call    BitBlt, [hDC],0,0,[Rect.rcRight],[Rect.rcBottom], \
                [hCompatibleDC],[nHorizPosition],[nVertPosition], \
                SRCCOPY

        mov     eax, [Bitmap.bmWidth]
        sub     eax, [Rect.rcRight]
        mov     [nHorizDifference], eax

        .if     eax > 0
                call    SetScrollRange, [hwnd],SB_HORZ,0, \
                        [nHorizDifference],TRUE
        .else
                call    SetScrollRange, [hwnd],SB_HORZ,0,0,TRUE
        .endif

        mov     eax, [Bitmap.bmHeight]
        sub     eax, [Rect.rcBottom]
        mov     [nVertDifference], eax

        .if     eax > 0
                call    SetScrollRange, [hwnd],SB_VERT,0, \
                [nVertDifference],TRUE
        .else
                call    SetScrollRange, [hwnd],SB_VERT,0,0,TRUE
        .endif

        call    SelectObject, [hCompatibleDC], [hOldBitmap]
        call    DeleteDC, [hCompatibleDC]
        call    EndPaint, [hwnd], offset PaintStruct
        mov     eax, 0
        jmp     finish

wmvscroll:
        movzx   eax, [word ptr wparam]
        .if     eax == SB_LINEDOWN
                mov      eax, [nVertPosition]
                .if eax < [nVertDifference]
                    inc  [nVertPosition]
                .endif
        .elseif eax == SB_LINEUP
                .if [nVertPosition] > 0
                    dec  [nVertPosition]
                .endif
        .elseif eax == SB_THUMBTRACK
                 movzx   eax, [word ptr wparam+2]
                 mov     [nVertPosition], eax
        .endif
        call    SetScrollPos, [hwnd],SB_VERT,[nVertPosition],TRUE
        call    InvalidateRect, [hwnd],0,TRUE
        mov     eax, 0
        jmp     finish

wmhscroll:
        movzx   eax, [word ptr wparam]
        .if     eax == SB_LINEDOWN
                mov      eax, [nHorizPosition]
                .if eax < [nHorizDifference]
                    inc  [nHorizPosition]
                .endif
        .elseif eax == SB_LINEUP
                .if [nHorizPosition] > 0
                    dec  [nHorizPosition]
                .endif
        .elseif eax == SB_THUMBTRACK
                 movzx   eax, [word ptr wparam+2]
                 mov     [nHorizPosition], eax
        .endif
        call    SetScrollPos, [hwnd],SB_HORZ,[nHorizPosition],TRUE
        call    InvalidateRect, [hwnd],0,TRUE
        mov     eax, 0
        jmp     finish

wmdestroy:
        call    DeleteObject, [hBitmap]
        call    PostQuitMessage, 0
        mov     eax, 0
finish:
        ret
DCDemoWndProc          endp
ends
end start
Вид окна, создаваемой программой:
Окно DCDemo

Если в окне не отображаются горизонтальная и вертикальная полосы прокрутки, необходимо уменьшить размеры окна по горизонтали и вертикали.

Появились полосы прокрутки? Давайте разберемся, благодаря чему это произошло.

Во-первых, если сравнить вызовы функций CreateWindowEx() в этой и предыдущей программах, то можно увидеть, что у окна появились два новых стиля - WS_HSCROLL и WS_VSCROLL. Эти стили определяют наличие у окна горизотнальной и вертикальной полос прокрутки соответственно. Первый шаг сделан. Этот шаг можно было бы сделать и по-другому, определив полосы прокрутки как дочерние окна, но о дочерних окнах мы будем говорить позже. Разница между полосами прокрутки, являющимися частью окна, и полосами прокрутки - дочерними окнами состоит в том, что дочерние окна имеют встроенный клавиатурный интерфейс, позволяющий воздействовать на полосу прокрутки с помощью клавиатуры. Встроенным полосам прокрутки, к сожалению, досталось только управление с помощью курсора мыши.

Теперь необходимо определить диапазон прокрутки, который определяет число шагов между крайними позициями бегунка (слайдера). По умолчанию для полос прокрутки, являющихся частью окна, этот диапазон определен от 0 до 100. Для того чтобы изменить диапазон прокрутки, необходимо вызвать функцию SetScrollRange(), которая в файле winuser.h определена следующим образом:

  WINUSERAPI BOOL WINAPI SetScrollRange(HWND hWnd, int nBar,
                                        int nMinPos, int nMaxPos,
                                        BOOL bRedraw);
Первый аргумент функции - хэндл окна, которому принадлежат полосы прокрутки. Второй аргумент определяет, для какой полосы прокрутки (вертикальной или горизонтальной) устанавливается диапозон. В данном случае этот аргумент может принимать значение SB_VERT или SB_HORZ, что определяет работу с вертикальной или горизонтальной полосой прокрутки. Третий и четвертый аргументы непосредственно указывают нижнюю и верхнюю границу диапозона прокрутки. Пятый аргумент представляет собой флаг, определяющий, нужно ли перерисовывать полосу прокрутки после определения диапозона. TRUE - полоса прокрутки перерисовывается, FALSE - перерисовка не нужна. Заметьте, что если диапазон прокрутки определен от 0 до 0, то полоса прокрутки становится невидимой. Это свойство используется и в приведенной выше программе. В том случае, когда размеры окна превышают размеры отображаемого bitmap'а, у полос прокрутки устанавливается диапазон от 0 до 0, следовательно, полоса прокрутки скрывается.

В данной случае с помощью функции SetScrollRange() диапазон прокрутки определен как разность между размером bitmap'а и размера окна по вертикали и по горизонтали, т.е. шаг полосы прокрутки соответствует одному пикселю.

Воздействовать на полосы прокрутки можно по-разному: во-первыхб можно щелкнуть клавишей мыши на стрелах, расположенных по краям полосы; во-вторых, можно щелкнуть на полосе выше или ниже слайдера. Наконец, можно перетащить слайдер на другое место. Все эти воздействия приводят к тому, что оконная функция окна, которому принадлежат полосы прокрутки, получает сообщение WM_VSCROLL (если действия производились вертикальной полосой) или WM_HSCROLL (реакция на воздействие на горизонтальную полосу).

Характер воздействия оконная функция может определить по параметрам сообщения. Младшее слово wParam, которое и определяет характер воздействия на полосу прокрутки, может принимать значения, прведенные в таблице:

Идентификаторы характеров воздействия на полосы прокрутки

Параметр Значение Описание
SB_LINEUP 0 Используется только с WM_VSCROLL, щелчок мышью на стрелке вверх, приводит к прокрутке на одну "строку" вверх
SB_LINELEFT 0 Используется только с WM_HSCROLL, щелчок мышью на стрелке влево, приводит к прокрутке на одну "колонку" влево
SB_LINEDOWN 1 Используется только с WM_VSCROLL, щелчок мышью на стрелке вниз, приводит к прокрутке на одну "строку" вниз
SB_LINERIGHT 1 Используется только с WM_HSCROLL, щелчок мышью на стрелке вправо, приводит к прокрутке на одну "колонку" вправо
SB_PAGEUP 2 Используется только с WM_VSCROLL, щелчок мышью на полосе прокрутки выше слайдера, приводит к прокрутке на одну "страницу" вверх
SB_PAGELEFT 2 Используется только с WM_HSCROLL, щелчок мышью на полосе прокрутки левее слайдера, приводит к прокрутке на одну "страницу" влево
SB_PAGEDOWN 3 Используется только с WM_VSCROLL, щелчок мышью на полосе прокрутки ниже слайдера, приводит к прокрутке на одну "страницу" вниз
SB_PAGERIGHT 3 Используется только с WM_HSCROLL, щелчок мышью на полосе прокрутки правее слайдера, приводит к прокрутке на одну "страницу" вправо
SB_THUMBPOSITION 4 Перетаскивание слайдера закончено, пользователь отжал клавишу мыши
SB_THUMBTRACK 5 Слайдер перетаскивается с помощью мыши, приводит к перемещению содержимого экрана
SB_TOP 6 Используется только с вертикальными полосами прокрутки, реализованными как дочерние окна, пользователь нажал клавищу "Home"
SB_LEFT 6 Используется только с горизонтальными полосами прокрутки, реализованными как дочерние окна, пользователь нажал клавишу "Home"
SB_BOTTOM 7 Используется только с вертикальными полосами прокрутки, реализованными как дочерние окна, пользователь нажал клавищу "End"
SB_RIGHT 7 Используется только с горизонтальными полосами прокрутки, реализованными как дочерние окна, пользователь нажал клавишу "End"
SB_ENDSCROLL 8 Пользователь отпустил клавишу мыши после удержания ее нажатой на стрелке или на полосе прокрутке

В таблице показано, что прокрутка при нажатии клавиши мыши в некоторых случаях производится на одну строку и одну страницу. В данном случае необходимо осознать, что понятие "строка" и "страница" ничего общего с текстовой и страницей не имеют. Этими понятиями я заменил условные единицы, на которые прокручивается изображение в окне. К примеру, в приведенной программе строка соответствует один пиксель, а понятие страницы вовсе не определено (что есть страница для картинки?).

Старшее слово wparam используется только в тех случаях, когда младшее слово wparam равно SB_THUMBPOSITION или SB_THUMBTRACK. В этих случаях оно хранит позицию слайдера. В остальных случаях это значение не используется.

В тех случаях, когда полосы прокрутки реализованы как дочерние окна, lparam содержит хэндл окна полосы прокрутки. Если полоса реализована как часть окна, этот параметр не используется.

После того, как мы зафиксировали факт произведенного с полосой прокрутки действия и характер действия, программа должна правильно отреагировать на него и при необходимости изменить позицию слайдера в соответствии с произведенным воздействием. Делается это с помощью обращения к функции SetScrollPos(), которая следующим образом описана в файле winuser.h:

   WINUSERAPI int WINAPI SetScrollPos(HWND hWnd, int nBar, int nPos, BOOL bRedraw);
Первый аргумент - это хэндл окна, содержащего полосу прокрутки (в этом случае, если полоса прокрутки реализована как часть окна), второй аргумент может принимать значение SB_VERT или SB_HORZ (об этих значениях говорилось выше), третий аргумент определяет, в какую позицию должен быть установлен слайдер. И наконец, четвертый аргумент определяет, нужно ли перерисовывать полосу прокрутки после установки слайдера. Если последний аргумент равен TRUE, то полоса прокрутки будет перерисована.

Для того чтобы в соответствии с новой позицией слайдера изменилось изображение в рабочей области, окну необходимо послать сообщение WM_PAINT, которое заставит окно перерисоваться. В программе, приведенной выше, сообщение WM_PAINT окну посылается с помощью вызова функции InvalidateRect(). Из этого следует, что код обработки сообщения WM_PAINT в оконной функции должен разрабатываться с учетом того, что содержимое окна может прокручиваться (скроллироваться).

И в заключение мне бы хотелось слегка посыпать голову пеплом. Любой хоть немного понимающий в программировании человек ужаснется, когда увидит, что я загружаю изображение из файла при каждой перерисовке окна (в программе, использующей функцию StretchBlt()). Это резко замедляет работу программы и занимает слишком много ресурсов. Но в данном случае целью было не написание программы, работающей оптимальным образом, а простая демонстрация того, что должна сделать программа для того, чтобы вывести на экран изображение.