Mesoscopic Programming

タコさんプログラミング専門

ファイルビュワー作成日誌 #005:バイナリビュワー

概要

バイナリビュワーができました。

ドキュメントウィンドウの中身としてビューウィンドウがある分けですが、
ビューウィンドウの派生クラスとしてファイルビューウィンドウがあって、
ファイルビューウィンドウの派生クラスとしてバイナリビューウィンドウがあるという寸法になっております。

対応予定のファイルタイプは以下の5種類です。

  1. バイナリファイル
  2. テキストファイル
  3. イメージファイル
  4. ムービーファイル
  5. 3-Dファイル(とりあえず X-File フォーマット)

このうちバイナリファイルとテキストファイルは自分でファイルハンドルを管理する必要があります。
ほかのファイルタイプはオープン時に API にファイル名を渡すだけで良いので自分で管理する必要がありません。
従ってバイナリとテキストのみ共通のファイルビュークラスの派生クラスということになっております。


実行画面と実行ファイル

  1. FileView.zip


ビューウィンドウクラス

ビューウィンドウクラスはすべてのビュワーの基底クラスです。
主に画面スクロールを管理します。

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進数表示なので基本は単純です。