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