ファイルビュワー作成日誌 #005:バイナリビュワー
概要
バイナリビュワーができました。
ドキュメントウィンドウの中身としてビューウィンドウがある分けですが、
ビューウィンドウの派生クラスとしてファイルビューウィンドウがあって、
ファイルビューウィンドウの派生クラスとしてバイナリビューウィンドウがあるという寸法になっております。
対応予定のファイルタイプは以下の5種類です。
- バイナリファイル
- テキストファイル
- イメージファイル
- ムービーファイル
- 3-Dファイル(とりあえず X-File フォーマット)
このうちバイナリファイルとテキストファイルは自分でファイルハンドルを管理する必要があります。
ほかのファイルタイプはオープン時に API にファイル名を渡すだけで良いので自分で管理する必要がありません。
従ってバイナリとテキストのみ共通のファイルビュークラスの派生クラスということになっております。
ビューウィンドウクラス
ビューウィンドウクラスはすべてのビュワーの基底クラスです。
主に画面スクロールを管理します。
class View : public Window { public : static const LPCTSTR pszKeyHorzMin; static const LPCTSTR pszKeyHorzMax; static const LPCTSTR pszKeyHorzPos; static const LPCTSTR pszKeyVertMin; static const LPCTSTR pszKeyVertMax; static const LPCTSTR pszKeyVertPos; public : LONG nFullWidth; LONG nFullHeight; LOGFONT logFont; HFONT hFont; SIZE sizeFont; SIZE sizePage; public : View(); virtual ~ View(); public : virtual VOID SaveScrollPos(); virtual VOID ResumeScrollPos(); virtual VOID UpdateSize(); virtual VOID UpdateFullSize(); virtual VOID UpdateScrollRange( int bar ); virtual VOID ScrollWindow( int bar, int pos ); virtual VOID OnLineHome(); virtual VOID OnLineEnd(); virtual VOID OnLineLeft(); virtual VOID OnLineRight(); virtual VOID OnLineUp(); virtual VOID OnLineDown(); virtual VOID OnPageHome(); virtual VOID OnPageEnd(); virtual VOID OnPageUp(); virtual VOID OnPageDown(); virtual BOOL OnCommand( UINT id ); virtual LRESULT OnWmCreate(); virtual LRESULT OnWmDestroy(); virtual LRESULT OnWmSize(); virtual LRESULT OnWmSetFocus(); virtual LRESULT OnWmKeyDown(); virtual LRESULT OnWmLbuttonDown(); virtual LRESULT OnWmMouseWheel(); virtual LRESULT OnHsbLineLeft(); virtual LRESULT OnHsbLineRight(); virtual LRESULT OnHsbPageLeft(); virtual LRESULT OnHsbPageRight(); virtual LRESULT OnHsbThumbPosition(); virtual LRESULT OnHsbThumbTrack(); virtual LRESULT OnHsbLeft(); virtual LRESULT OnHsbRight(); virtual LRESULT OnVsbLineUp(); virtual LRESULT OnVsbLineDown(); virtual LRESULT OnVsbPageUp(); virtual LRESULT OnVsbPageDown(); virtual LRESULT OnVsbThumbPosition(); virtual LRESULT OnVsbThumbTrack(); virtual LRESULT OnVsbTop(); virtual LRESULT OnVsbBottom(); };
解説
見ての通りほとんど画面スクロール処理です。
nFullWidth と nFullHeight はビュー全体のサイズであり、
これを実際のクライアントウィンドウ領域と比較してスクロールバーを付けたり消したりしています。
スクロール単位として文字サイズを利用するために hFont でフォントを作成しています。
ラインスクロールの単位として sizeFont を、ページスクロールの単位として sizePage を使用します。
あとでキャレットを表示するとき必要になるので logFont を保持しています。
ファイルビュークラス
ファイルビュークラスはバイナリビューとテキストビューの基底クラスです。
ファイルハンドルの管理とファイル表示のための基本機能を司ります。
つまりバイナリビューとテキストビューで共通の仕事があればここに実装します的な感じになります。
class FileView : public View { public : static const INT nBufferSize = 256; static const COLORREF nTextColor = RGB( 0, 0, 0 ); static const COLORREF nIndexColor = RGB( 0, 160, 64 ); static const LPCTSTR pszKeyIndex; static const LPCTSTR pszKeyLine; static const LPCTSTR pszKeyColumn; public : HANDLE hFile; HBRUSH hBgBrush; HWND hIndex; HPEN hIndexPen; INT nIndexWidth; BOOL bIndex; INT nLineCount; INT nMaxColumn; INT nLine; INT nColumn; SIZE sizeOffset; TCHAR szBuffer[ nBufferSize ]; public : FileView(); virtual ~ FileView(); public : virtual VOID UpdateFullSize(); virtual BOOL Open( LPCTSTR path, DWORD mode ); virtual VOID Close(); virtual ULONGLONG GetSize(); virtual LONGLONG SetPos( LONGLONG pos, DWORD dwMoveMethod ); virtual LONGLONG SetAbsPos( LONGLONG pos ); virtual LONGLONG SetRelPos( LONGLONG pos ); virtual DWORD ReadData( LPVOID buffer, DWORD size ); virtual DWORD WriteData( LPCVOID buffer, DWORD size ); virtual BOOL OnViewIndex(); virtual BOOL OnCommand( UINT id ); virtual LRESULT OnWmCreate(); virtual LRESULT OnWmDestroy(); virtual LRESULT OnWmSize(); };
解説
64 ビット整数対応なので表示ファイルサイズに制限なしです、たぶん。
オンメモリではないので大きなファイルも一瞬で開きます。
バイナリビュークラス
バイナリビュークラスは任意のファイルを16進数で表示するクラスです。
表示以外のほとんどの処理はファイルビュークラスがやっちゃってくれちゃってるので、本体は表示処理だけです。
class BinaryView : public FileView { public : inline LPCTSTR GetRegistClassName() { return _T( "ClassBinaryView" ); } inline LPCTSTR GetSectionName() { return _T( "BinaryView" ); } BOOL Open( LPCTSTR path ); LRESULT OnWmPaint(); };
解説
Open() 関数でファイルオープンしたときにファイルサイズから表示行数を算出します。
あとは OnWmPaint() 関数で WM_PAINT メッセージ対応するだけです。
WM_PAINT メッセージ
アドレス表示の有無を切り替えられるようになってるので、
アドレス表示部とデータ表示部はそれぞれ異なるウィンドウです。
LRESULT BinaryView :: OnWmPaint() { PAINTSTRUCT ps; HDC hdc = BeginPaint( hwnd, & ps ); HDC hdc2 = GetDC( hIndex ); INT xpos = GetScrollPos( hwnd, SB_HORZ ); INT ypos = GetScrollPos( hwnd, SB_VERT ); INT ch2 = 0; INT line, left, top, len; RECT rc, rc2; BYTE buffer[ 32 ]; LONGLONG dwSize, dwRead, pos; line = ( sizeOffset.cy + ypos ) / sizeFont.cy; if ( line > 0 ) { line--; } left = sizeOffset.cx - xpos; top = line * sizeFont.cy + sizeOffset.cy - ypos; GetClientRect( hwnd, & rc ); SelectObject( hdc, hFont ); SelectObject( hdc, hIndexPen ); SelectObject( hdc2, hFont ); SelectObject( hdc2, hIndexPen ); SetTextColor( hdc2, nIndexColor ); rc2.left = 0; rc2.top = 0; rc2.right = rc.right; rc2.bottom = top; FillRect( hdc, & rc2, hBgBrush ); rc2.right = nIndexWidth; FillRect( hdc2, & rc2, hBgBrush ); rc2.right = left; rc2.bottom = rc.bottom; FillRect( hdc, & rc2, hBgBrush ); rc2.right = 4; FillRect( hdc2, & rc2, hBgBrush ); rc2.left = rc2.right + 9 * sizeFont.cx; rc2.right = nIndexWidth; FillRect( hdc2, & rc2, hBgBrush ); rc2.left = 9 * sizeFont.cx + 8; rc2.top = top; rc2.right = rc2.left; rc2.bottom = min( rc.bottom, nFullHeight - 4 ); MoveToEx( hdc2, rc2.left, rc2.top, NULL ); LineTo( hdc2, rc2.left, rc2.bottom ); FillMemory( buffer, sizeof buffer, ' ' ); dwSize = GetSize(); pos = line * 16; SetAbsPos( pos ); dwRead = ReadData( buffer, 32 ); for ( ; line < nLineCount; line++, pos += 16 ) { sprintf( szBuffer, _T( "%04X:%04X" ), HIWORD( pos ), LOWORD( pos ) ); len = ( INT ) strlen( szBuffer ); TextOut( hdc2, 4, top, szBuffer, len ); ZeroMemory( szBuffer, sizeof szBuffer ); for ( int i = 0; i < 16; i++ ) { if ( i == 8 ) { strcat( szBuffer, _T( "- " ) ); } if ( pos + i < dwSize ) { sprintf( szBuffer + strlen( szBuffer ), _T( "%02X " ), buffer[ i ] ); } else { strcat( szBuffer, _T( " " ) ); } } strcat( szBuffer, _T( " " ) ); for ( int i = 0; i < 16; i++ ) { if ( ch2 != 0 ) { if ( i == 0 ) { strcat( szBuffer, _T( " " ) ); } ch2 = 0; } else if ( _ismbblead( buffer[ i ] ) ) { ch2 = buffer[ i + 1 ]; sprintf( szBuffer + strlen( szBuffer ), _T( "%c%c" ), buffer[ i ], ch2 ); } else if ( _istprint( buffer[ i ] ) ) { sprintf( szBuffer + strlen( szBuffer ), _T( "%c" ), buffer[ i ] ); } else { strcat( szBuffer, _T( "." ) ); } } len = ( INT ) strlen( szBuffer ); TextOut( hdc, left, top, szBuffer, len ); SetTextColor( hdc, nIndexColor ); TextOut( hdc, left + 3 * 8 * sizeFont.cx, top, _T( "- " ), 2 ); SetTextColor( hdc, nTextColor ); rc2.left = left + ( 3 * 16 + 2 ) * sizeFont.cx; rc2.top = top; rc2.right = rc2.left; rc2.bottom = rc2.top + sizeFont.cy; MoveToEx( hdc, rc2.left, rc2.top, NULL ); LineTo( hdc, rc2.left, rc2.bottom ); rc2.left = left + len * sizeFont.cx; rc2.right = rc.right; FillRect( hdc, & rc2, hBgBrush ); top += sizeFont.cy; if ( top > rc.bottom ) { break; } CopyMemory( buffer, buffer + 16, 16 ); FillMemory( buffer + 16, 16, ' ' ); dwRead = ReadData( buffer + 16, 16 ); } rc2.left = 0; rc2.top = top; rc2.right = rc.right; rc2.bottom = rc.bottom; FillRect( hdc, & rc2, hBgBrush ); rc2.right = nIndexWidth; FillRect( hdc2, & rc2, hBgBrush ); ReleaseDC( hIndex, hdc2 ); EndPaint( hwnd, & ps ); return 0; }
解説
hIndex がアドレス表示用の子ウィンドウです。
これはスタティックコントロールをオーナードローで使用しています。
全体を消去してから文字を描くとちらついてしまうので、背景消去は必要最小部分だけをこまめに消してます。
この消去処理のせいでコードが長くなっちゃってますが、単なる16進数表示なので基本は単純です。