Hex Viewer (Scroll)
В данной статье реализуем скроллинг.
И так первое что мы добавим - это парочку глобальных переменных:
Dim Shared As Ulongint ullVScrollPos,ullMaxCount ' позиция скрола, кол-во прокручиваемых областей
Далее в процедуре создания CreateChildProc нашего самописного контрола мы
добавим такую строчку:
SendMessage(childHwnd,WM_VSCROLL,SB_THUMBTRACK,0) ' установим ползунок прокрутки на нуль
Эту строку, мы потом перенесем в другое место (после загрузки файла).
Данная команда заставит систему принудительно послать сообщение WM_VSCROLL с
событием SB_THUMBTRACK. Мы обработаем сообщение WM_VSCROLL , обсчитаем нужные
нам переменные и окно прорисует все корректно. У меня случалось такое, когда
после загрузки программы и последующего скроллинга за ползунок, контрол
возвращал совсем не ту информацию, которая должна быть. Поэтому, чтобы
предотвратить такие ситуации, я принудительно как бы "инициализирую"
контрол.
Кроме того, пока у нас нет загрузки файла и информации о нем, мы в эту же процедуру внесем и эти строчки:
If ullSizeFile>416 Then ' если развер файла больше кол-ва символов области прокрутки ullMaxCount = (ullSizeFile-416)\16+1 ' высчитываем Else ullMaxCount = 0 ' иначе равно 0 Endif
В этих строчках мы получаем кол-во прокручиваемых областей (точнее
строк), которые как вы поняли зависят от размера файла (в нашем случае пока
буфера). В последствии мы эти строчки перенесем в другую процедуру (туда где
будет записан код загрузки файла и его размера).
Процедуру CopyBytesProc перепишем так:
'заполнение буфера pbBytesBuffer данными Sub CopyBytesProc() Dim As Ulongint ullTempSize If (ullNullOffsetView + 416) < ullSizeFile Then ullTempSize = 416 Else ullTempSize = ullSizeFile - ullNullOffsetView Endif CopyMemory( pbBytesBuffer ,@TempDIM(ullNullOffsetView) , ullTempSize) End Sub
В этом коде проверяется условие чтения в рамках читаемого буфера\файла. В
зависимости от начального смещения ullNullOffsetView , чтение начинается с
нужной точки. Сама же переменная ullNullOffsetView зависит от позиции
скроллинга.
Далее добавляем 2 процедуры, работающие с логикой скроллинга:
' получение информации о скроллинге Sub GetScrollinfoProc(hwnd As hwnd) Dim info As SCROLLINFO info.cbSize = Sizeof(SCROLLINFO) info.fMask = SIF_TRACKPOS GetScrollInfo(hwnd, SB_VERT, @info) ' получаем инфу о скроллинге от системы ullVScrollPos = info.nTrackPos ' получаем позицию скроллинга End Sub ' обработка информации о скроллинге Sub ScrollProc(hwnd As HWND, param As Integer) Dim As Integer vs Select Case param Case SB_TOP ullVScrollPos = 0 ' на начало страницы Case SB_BOTTOM ullVScrollPos = ullMaxCount 'на конец страницы Case SB_LINEUP If(ullVScrollPos > 0) Then ullVScrollPos-=1 'сдвигаемся ближе к началу страницы на 1 Endif Case SB_LINEDOWN If(ullVScrollPos < ullMaxCount) Then ullVScrollPos+=1 'сдвигаемся ближе к концу страницы на 1 Endif Case SB_PAGEUP If(ullVScrollPos > 10) Then ullVScrollPos-=10 'сдвигаемся ближе к началу страницы на 10 Endif Case SB_PAGEDOWN If(ullVScrollPos < (ullMaxCount-10)) Then ullVScrollPos+=10 'сдвигаемся ближе к концу страницы на 10 Endif Case SB_THUMBPOSITION,SB_THUMBTRACK ' если пользовательское перемещение на неопределенное кол-во GetScrollinfoProc(hwnd) ' получаем информацию из системы и высчитываем End Select Dim As SCROLLINFO sInfo sInfo.cbSize = Sizeof(SCROLLINFO) sInfo.fMask = SIF_ALL ' маска для любых действий со скролом sInfo.nMin = 0 sInfo.nMax = ullMaxCount sInfo.nPos = ullVScrollPos sInfo.nPage = 1 SetScrollInfo (hwnd, SB_VERT,@sInfo, TRUE) InvalidateRect(hwnd,0,false) ullNullOffsetView = ullVScrollPos*16 ' получаем начальное смещение в видимой части окна End Sub
Процедура GetScrollinfoProc работает только, когда пользователь скроллит
прямо за ползунок. Система в этом случае присылает сообщения
SB_THUMBPOSITION,SB_THUMBTRACK. Все другие сообщения обрабатываются прямо в
процедуре ScrollProc по установленным правилам. Так же в этой процедуре
вычисляется переменная ullNullOffsetView (начальное смещение в области видимости
окна). И принудительно устанавливается скроллинг на нужную позицию с помощью
SetScrollInfo , предварительно конечно же происходит заполнение структуры
SCROLLINFO. После скроллинга нужно заставить наш контрол HEX перерисовать себя.
Делается это с помощью функции InvalidateRect.
И чтобы все это работало, нужно в процедуре HexWndProc в событии WM_VSCROLL подставить наш обработчик:
ScrollProc(hwnd,Loword(wparam))
Кажется, все выглядит неплохо и должно работать, но есть одна маленькая
деталь. При такой реализации скроллинга, мы упремся в размер загружаемого файла
(приблизительно 32 ГБ). Да это кажется неплохо, особенно если учесть реализации
некоторых программистов, которые вообще ограничивают свои программы размером в
2ГБ. Однако на сегодняшний день, когда размеры архивов могут достигать под сотни
гигабайт, а жесткие диски исчисляются терабайтами, размер в 32 ГБ не такой уж и
большой. Хочется сделать так, чтобы программа гарантировано (с запасом на
будущее) загружала любой возможный файл с жесткого диска, даже если его размер
будет соотносим с размером жесткого диска. Но как это сделать, когда
максимальное число прокрутки , которое можно задать в структуре SCROLLINFO
32-битное число? А это в нашем случае получится (&hFFFFFFFF * &h10)
байт. Да это больше чем в нашей реализации в 2 раза, но все равно мало, да и
канителиться с отрицательными числами (от &h80000000 до &hFFFFFFFF) не
очень-то удобно, хотя и реализуемо. Почему отрицательными? Для того, чтобы
использовать 32-битное число "под потолок" , нам придется в параметр nMin
структуры SCROLLINFO поставить число &h80000000 , а в nMax занести
&h7FFFFFFF . Тогда у нас получится диапазон
&h80000000-...0...-&h7FFFFFFF , где числа от &h80000000 до
&hFFFFFFFF отрицательные. Ведь параметры nMin и nMax имеют тип Integer. И
нам придется считаться с этими неудобствами в своей программе.
Все таки есть более простое и удобное решение. Нам всего лишь надо пропорционально привести [64х-битные значения смещения в окне] к [32х-битным значениям для ползунка]. Или наоборот 32->64 , когда позицию ползунка надо преобразовать к реальному смещению в окне. И поможет нам в этом деле старая добрая пропорция.
Давайте для теории сделаем 4 определения:
nPos - позиция ползунка, которую мы будем заталкивать в структуру SCROLLINFO и скармливать функции SetScrollInfo
nPos64 - абстрактная 64х-битная позиция ползунка, которая используется для получения верхнего смещения в видимой области HEX окна
nMax - максимальная величина кол-ва прокручиваемых областей, которую мы задаем в параметр nMax структуры SCROLLINFO (этот параметр не может быть больше &h7FFFFFFF, определим пока его именно такого размера)
nMax64 - 64х-битное значение кол-ва прокручиваемых областей, по сути это: (размер нашего файла) \ (16 символов в одной строке)
Сама пропорция:
nPos nPos64
nMax nMax64
Отсюда формула для вычисления nPos64 (после получения информации от функции GetScrollInfo) будет такая:
nPos64 = (nPos*nMax64)\nMax
У нас есть все нужные нам аргументы, для вычисления nPos64.
Функция
GetScrollInfo вернет нам nPos.
После загрузки файла , можно легко подсчитать
nMax64.
Ну а nMax мы решили, что будет равна &h7FFFFFFF.
Формула же для вычисления nPos (перед вызовом функции SetScrollInfo) будет такая:
nPos = (nPos64*nMax)\nMax64
После вызова GetScrollInfo , у нас опять же есть все аргументы.
С точки зрения математических действий, у нас вроде полный порядок. Однако есть одна заковырка. Когда значения nPos и nMax64 будут большими, при умножении их произведение будет вылезать за рамки 64х-битного числа, что вызовет переполнение. Как результат, нашу прокрутку вместо того, чтобы опускать вниз, начнет возращать вверх. А что нам мешает поменять порядок действий? Ведь x*y\z = x\z*y. Только единственно, нам нельзя будет использовать оператор целочисленного деления (\), поскольку при x меньше чем y , всегда будет возвращаться ноль. Просто заменим его на другой оператор деления (/).
Но и это еще не все! Если взглянуть еще раз на пропорцию, то становится ясно,
что потенциальный размер загружаемого файла можно еще увеличить. И сделать это
легко , если уменьшить значение nMax. Ведь мы его определили по максимальному
возможному числу, которое можно отправить в структуру SCROLLINFO. Что нам мешает
сделать это число меньше, например равное &h7fff (32767) [данное число
было максимальным в ранних версиях windows для scrollbar]?
Да , точность
попадания ползунком на нужную позицию адреса упадет, но в реальности, на больших
файлах это не имеет вообще никакого значения, поскольку и при значении
&h7FFFFFFF попасть ползунком невозможно. С большими файлами единственный
метод попасть на нужную позицию это точный переход (в программах "Перейти по
адресу:"). Здесь правильно найти золотую середину. Например при файлах с
размерами до 100ГБ использовать константу &h7FFFFFFF , а при больших
размерах использовать &h7FFF. И чтобы не ущемлять точность на небольших
файлах, можно ввести условия работы прокрутки. В одном случае программа должна
работать так , как реализовано в коде выше, а если кол-во прокручиваемых
областей больше константы &h7FFFFFFF , то проводить прорциональные
вычисления.
Ладно хватит теории :) .
Вот код, который получился на данный момент:
#INCLUDE "windows.bi" #INCLUDE "win/commctrl.bi" #INCLUDE "win/commdlg.bi" InitCommonControls() Declare Function HexWndProc(hwnd As HWND, msg As Uinteger,_ wparam As WPARAM, lparam As LPARAM) As Integer ' Переменные Dim Shared hinst As HINSTANCE : hinst = GetModuleHandle(0) Dim msg As MSG Dim As WNDCLASSEX wc Dim As ZString*20 sNameClass = "HexViewer" Dim Shared As ZString*20 szHexClass: szHexClass = "szHexClass" Dim Shared As HFONT hFontCourierNew Dim Shared As HWND mainHwnd Dim Shared As HWND childHwnd Dim Shared ofn As OPENFILENAME Dim Shared filename As Zstring * 512 Dim Shared As Byte TempDIM(10000) Dim Shared As Ulongint ullSizeFile ' размер файла ullSizeFile = 10000 Dim Shared As Ulongint ullNullOffsetView 'смещение в самом начале видимой части окна Dim Shared As Ubyte Ptr pbBytesBuffer ' буфер , ограниченный в размере (416 байт), 'используется для копирования в него части информации, которая отображается в видимой части окна pbBytesBuffer = Callocate(416) ' выделим память для буфера Dim Shared As Ulongint ullVScrollPos,ullMaxCount ' позиция скрола, кол-во прокручиваемых областей ' загрузка шрифта Function LoadFontProc(Byval Name_ As String, _ Byval Size As Integer, _ Byval corner As Integer,_ Byval BOLD As Integer,_ Byval Italic As Integer,_ Byval Underline As Integer,_ Byval StrikeOut As Integer,_ Byval CharSet As Integer) As HFONT Dim As Integer size_ If BOLD=1 Then BOLD=700 ' жирный шрифт Else BOLD=400 ' обычный Endif Dim As HDC hdc = CreateDC("DISPLAY",0,0,0) ' создаем контекст size_ = -MulDiv(Size, GetDeviceCaps(hdc, LOGPIXELSY), 72) ' высчитываем размер шрифта DeleteDC(hdc) ' удаляем контекст Return CreateFont(size_,0,corner*10,0,Bold,Italic,Underline,StrikeOut, _ 'создаем шрифт CharSet, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH Or FF_DONTCARE,Name_) End Function ' диалог вызова файла Function FileOpen(Byval Title As String,Byval curentdir As String, Byval Pattern As String, Byval templateName As String = "") As String Dim fbguiTemp As Integer filename = templateName With ofn .hwndOwner = 0 .lStructSize = Sizeof(OPENFILENAME) .lpstrFilter = Cast(LPCTSTR,Strptr(Pattern) ) .lpstrFile = Strptr(filename) .nFileOffset = 0 .nMaxFile = Sizeof(filename) .lpstrFileTitle = Cast(LPTSTR,Strptr(Title)) .nMaxFileTitle = Sizeof(Title) .lpstrInitialDir = Cast(LPCTSTR,Strptr(curentdir)) .lpstrTitle = Cast(LPCTSTR,Strptr(Title)) .Flags = OFN_EXPLORER Or OFN_FILEMUSTEXIST End With If( GetOpenFileName( @ofn ) = 0 ) Then Return "" Else Return filename End If End Function ' создание HEX окна Sub CreateChildProc(hwnd As hwnd) Dim As WNDCLASSEX wc With wc .cbSize=SizeOf(WNDCLASSEX) .lpfnWndProc=@HexWndProc .hInstance=Hinst .hCursor=LoadCursor(0,IDC_IBEAM) .hbrBackground=Cast(HBRUSH,COLOR_SCROLLBAR) .lpszClassName=StrPtr(szHexClass) End With If RegisterClassEx(@wc)=0 Then Print "Register error, press any key" Sleep End Endif childHwnd = CreateWindowEx( WS_EX_CLIENTEDGE,szHexClass,"",_ WS_VSCROLL Or WS_CHILD Or WS_VISIBLE,0,25,794,552,hwnd,Cast(HMENU,1),Hinst,0) If ullSizeFile>416 Then ' если развер файла больше кол-ва символов области прокрутки ullMaxCount = (ullSizeFile-416)\16+1 ' высчитываем Else ullMaxCount = 0 ' иначе равно 0 Endif SendMessage(childHwnd,WM_VSCROLL,SB_THUMBTRACK,0) ' установим прокрутку на нуль End Sub ' оконная процедура главного окна Function wndproc(hwnd As HWND, msg As Uinteger,_ wparam As WPARAM, lparam As LPARAM) As Integer Select Case msg Case WM_CREATE CreateChildProc(hwnd) ' Создаем свое дочернее окно для вывода HEX значений CreateWindowEx(0,"Button","Открыть файл",_ WS_VISIBLE Or WS_CHILD,10,1,100,20,hwnd,Cast(HMENU,2),Hinst,0) ' Создаем обычную кнопку Case WM_COMMAND If lparam <> 0 Then ' если сообщения от контролов Select Case Loword(wParam) Case 2 ? FileOpen("Открыть","C:\","All files (*.*)"+Chr(0)+"*.*"+Chr(0) ,"") End Select Endif Case WM_DESTROY DeleteObject(hFontCourierNew) PostQuitMessage(0)' выходим End Select Return DefWindowProc(hwnd,msg,wparam,lparam) End Function 'заполнение буфера pbBytesBuffer данными Sub CopyBytesProc() Dim As Ulongint ullTempSize If (ullNullOffsetView + 416) < ullSizeFile Then ullTempSize = 416 Else ullTempSize = ullSizeFile - ullNullOffsetView Endif CopyMemory( pbBytesBuffer ,@TempDIM(ullNullOffsetView) , ullTempSize) End Sub ' рисование на окне childHwnd Sub PaintHexProc() Dim As HDC hdc,hdc2 ' контексты устройства Dim As PAINTSTRUCT ps ' структура, содержащая инфу для рисования Dim As HBITMAP hbitmap ' битмап для рисования Dim As HBRUSH brush ' хендл кисти Dim As HPEN pen ' хендл пера Dim As Integer offset ' смещение Dim As Integer y = 21 ' высота букв #MACRO mRectangle(col,x,w) ' макрос рисования прямоугольников brush=CreateSolidBrush(col) ' создаем кисть SelectObject(hdc,brush) ' кисть в контекст pen= CreatePen(PS_SOLID ,1,Cast(COLORREF,col)) ' создаем перо SelectObject(hDC,Cast(HGDIOBJ,pen)) ' перо в контекст Rectangle(hDC,x,0,w,552) ' рисуем прямоугольник DeleteObject(brush) ' удаляем кисть DeleteObject(pen) ' удаляем перо #EndMacro #MACRO mDrawText() SelectObject(hdc, hFontCourierNEW) ' свой шрифт в контекст TextOut(hdc,10,y*i+5,Hex(i*16+ullNullOffsetView,11), 11) ' рисуем строку - адрес TextOut(hdc,142,y*i+3,sHex, 47)' рисуем строку - HEX TextOut(hdc,611,y*i+3,sString, 16)' рисуем строку - ASCII #EndMacro hdc2 = BeginPaint(childHwnd, @ps) ' начинаем рисование, получаем контекст hdc = CreateCompatibleDC(hdc2) 'создаем совместимый контекст hbitmap = CreateCompatibleBitmap(hdc2,794,552) ' создаем совместимый битмап SelectObject(hdc,hbitmap) ' битмап в контекст mRectangle(&hFFE9E9,0,109) ' рисуем прямоугольник для адресов mRectangle(&hf0f0f0,110,595) 'рисуем прямоугольник для Hex символов mRectangle(&hE9FFE9,596,794) ' рисуем прямоугольник для ASCII символов CopyBytesProc() ' копируем данные в буфер, из которого будем читать информацию и рисовать SetBkMode(hdc,1) ' режим прозрачности для текста For i As Integer = 0 To 25 ' цикл по числу видимых строк Dim As ZString*50 sHex ' хранит HEX символы Dim As ZString*16 sString ' хранит ASCII символы For x As Integer = 0 To 15 ' цикл по числу столбцов If ullNullOffsetView+offset = ullSizeFile Then ' если конец буфера If x>0 Then ' если прошла хоть одна итерация mDrawText() ' вызываем макрос Endif Exit For, For ' выход из обоих циклов Endif sHex &= (Hex(pbBytesBuffer[offset],2)& " ") ' собираем строку HEX Dim As Ubyte btempSimbol = pbBytesBuffer[offset]' получаем ASCCI код Select Case btempSimbol ' отсеиваем непечатываемые символы Case &h0 To &h1f,&hAD,&h98,&h7f btempSimbol = &h2E ' заменяем их точками End Select sString[x] = btempSimbol ' собираем строку ASCII offset +=1 ' увеличиваем смещение Next mDrawText()' вызываем макрос Next BitBlt(hdc2,0,0,794,552,hdc,0,0,SRCCOPY) ' копируем содержимое контекста в другой контекст DeleteObject(hbitmap) ' удаляем битмап DeleteDC(hDC) ' удаляем совместимый контекст EndPaint(childHwnd, @ps) ' заканчиваем рисование End Sub ' получение информации о скроллинге Sub GetScrollinfoProc(hwnd As hwnd) Dim info As SCROLLINFO info.cbSize = Sizeof(SCROLLINFO) info.fMask = SIF_TRACKPOS GetScrollInfo(hwnd, SB_VERT, @info) ' получаем инфу о скроллинге от системы If ullMaxCount<&h7fffffffll Then ' если максимальная прокрутка меньше 7fffffff (размер файла ~32 ГБ) ullVScrollPos = info.nTrackPos ' тогда просто присваиваем системную инфу Else ' высчитываем процентное соотношение позиции скроллинга для файлов с размером больше 32ГБ ullVScrollPos = info.nTrackPos ullVScrollPos = (ullVScrollPos/&h7fffffffull)*ullMaxCount Endif End Sub ' обработка информации о скроллинге Sub ScrollProc(hwnd As HWND, param As Integer) Dim As Integer vs Select Case param Case SB_TOP ullVScrollPos = 0 ' на начало страницы Case SB_BOTTOM ullVScrollPos = ullMaxCount 'на конец страницы Case SB_LINEUP If(ullVScrollPos > 0) Then ullVScrollPos-=1 'сдвигаемся ближе к началу страницы на 1 Endif Case SB_LINEDOWN If(ullVScrollPos < ullMaxCount) Then ullVScrollPos+=1 'сдвигаемся ближе к концу страницы на 1 Endif Case SB_PAGEUP If(ullVScrollPos > 10) Then ullVScrollPos-=10 'сдвигаемся ближе к началу страницы на 10 Endif Case SB_PAGEDOWN If(ullVScrollPos < (ullMaxCount-10)) Then ullVScrollPos+=10 'сдвигаемся ближе к концу страницы на 10 Endif Case SB_THUMBPOSITION,SB_THUMBTRACK ' если пользовательское перемещение на неопределенное кол-во GetScrollinfoProc(hwnd) ' получаем информацию из системы и высчитываем End Select Dim As SCROLLINFO sInfo sInfo.cbSize = Sizeof(SCROLLINFO) sInfo.fMask = SIF_ALL ' маска для любых действий со скролом sInfo.nMin = 0 sInfo.nMax = Iif (ullMaxCount>&h7fffffffull,&h7fffffff,ullMaxCount) ' ставим максимальное значение скроллинга vs = Iif(ullMaxCount>&h7fffffffull,(ullVScrollPos/ullMaxCount)*&h7fffffffull,ullVScrollPos) ' высчитываем реальное смещение в скроле sInfo.nPos = vs sInfo.nPage = 1 SetScrollInfo (hwnd, SB_VERT,@sInfo, TRUE) InvalidateRect(hwnd,0,false) ullNullOffsetView = ullVScrollPos*16 ' получаем начальное смещение в видимой части окна End Sub ' оконная процедура своего окна, на котором будем рисовать HEX данные Function HexWndProc(hwnd As HWND, msg As Uinteger,_ wparam As WPARAM, lparam As LPARAM) As Integer Select Case msg Case WM_PAINT ' если требуется рисование PaintHexProc() Return 0 Case WM_VSCROLL ' если произошло событие скроллинга ScrollProc(hwnd,Loword(wparam)) ' обрабатываем End Select Return DefWindowProc(hwnd,msg,wparam,lparam) End Function ' настройки главного окна With wc .cbSize=SizeOf(WNDCLASSEX) .style=CS_HREDRAW Or CS_VREDRAW .lpfnWndProc=@wndproc .hInstance=Hinst .hIcon=LoadIcon(0,IDI_QUESTION) .hCursor=LoadCursor(0,IDC_ARROW) .hbrBackground=Cast(HBRUSH,COLOR_WINDOW) .lpszClassName=StrPtr(sNameClass) .hIconSm=.hIcon End With ' регистрация окна If RegisterClassEx(@wc)=0 Then Print "Register error, press any key" Sleep End Endif ' Загрузка шрифтов hFontCourierNew = LoadFontProc("Courier new",11,0,0,0,0,0,DEFAULT_CHARSET) ' создаем главное окно mainHwnd = CreateWindowEx(0,sNameClass,"HexViewer",_ WS_VISIBLE Or WS_OVERLAPPED Or WS_CAPTION Or WS_SYSMENU Or WS_MINIMIZEBOX,100,100,800,610,0,0,Hinst,0) For i As Integer = 0 To 10000 TempDIM(i) = Rnd*256 Next ' главный цикл While GetMessage(@msg,0,0,0) TranslateMessage(@msg) DispatchMessage(@msg) Wend
В следующей статье, думаю уже разобрать загрузку файлов.
содержание | назад | вперед