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

Мне кажется, что у читателя может возникнуть вопрос: что делать в тех случаях, когда нам не нужно масшабировать 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

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

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

Во-первых, если сравнить вызовы функций 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()). Это резко замедляет работу программы и занимает слишком много ресурсов. Но в данном случае целью было не написание программы, работающей оптимальным образом, а простая демонстрация того, что должна сделать программа для того, чтобы вывести на экран изображение.

Используются технологии uCoz