Mesoscopic Programming

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

ファイルビュワー作成日誌 #001 : ウィンドウズアプリケーション

はじめに

以前ウィンドウズプログラミング講座などという仰々しいタイトルを付けたもんだから、
敷居が高くなって何も書けない状態が続いてしまったので、ブログタイトルを作成日誌に変更しました。
これで公開ソースに多少バグがあっても大目に見てもらえるつーか何の問題もないよね。

一応バイナリ、テキスト、画像、動画、Xファイルの5種類のファイル形式に対応したビュワーは大方出来てるので、
ソース整理を兼ねて頭から連載する予定です。


実行画面

実行ファイル
  1. FileView.zip


WinMain() 関数

まずは WinMain() 関数です。これが無いと何も始まらないもんで。

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
    HANDLE hmutex = CreateMutex( 0, TRUE, MUTEX_NAME );
    int    result = -1;

    if ( GetLastError() != ERROR_ALREADY_EXISTS )
    {
        if ( App :: Begin( lpCmdLine, nCmdShow ) )
        {
            result = App :: Run();

            App :: End();
        }
    }

    CloseHandle( hmutex );

    return result;
}
解説

CreateMutex() は多重起動防止策なんですよね。
不用意に多重起動を許可すると INI ファイルとかの環境設定内容の扱いが複雑になってバグの原因になるんですよね。
まずはシングルドキュメント対応で開発しますが、将来的には多重起動ではなくマルチドキュメント対応にするべきですよね。
ここでやってる内容はと言えば、初期化と実行ループと終了処理ですよね。
それぞれ App::Begin()、App::Run()、App::End() がそれに対応してるんですよね。
あとミューテックス解放したりなんかして終了ですよね。
App はアプリケーションクラスなんですよね。
C++ だと仮想関数が使えるので非常に便利なんですよね。
だってウィンドウってたくさん使うじゃない?
基底クラスを使えばいちいち同じような処理書かなくて済むんだよね。


アプリケーションクラスの定義

今回はこのウィンドウ1つだけです。
実際にウィンドウクラスを登録したり作成したりするのは基底クラスの方でやってるので、
ここではウィンドウ作成用のパラメータを定義してるだけなんですよね。

class App : public Window
{
public :

    static BOOL Begin( LPSTR lpCmdLine, int nCmdShow );
    static int  Run();
    static VOID End();

public :

    inline LPCTSTR GetRegistClassName()  { return _T( "ClassApp" ); }
    inline HBRUSH  GetRegistBackGround() { return ( HBRUSH ) ( COLOR_APPWORKSPACE ); }
    inline HICON   GetRegistIcon()       { return LoadIcon( NULL, IDI_APPLICATION ); }
    inline HICON   GetRegistIconSm()     { return LoadIcon( NULL, IDI_WINLOGO ); }
    inline DWORD   GetCreateExStyle()    { return WS_EX_APPWINDOW; }
    inline LONG    GetCreateStyle()      { return WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX; }
    inline LPCTSTR GetCreateWindowName() { return ( APP_NAME " " VER_NAME ); }
    inline int     GetCreateLeft()       { return CW_USEDEFAULT; }
    inline int     GetCreateTop()        { return CW_USEDEFAULT; }
    inline int     GetCreateWidth()      { return CW_USEDEFAULT; }
    inline int     GetCreateHeight()     { return CW_USEDEFAULT; }
    int            MessageLoop();
    LRESULT        OnWmDestroy();
};
解説

Begin() とか Run() とか WinMain() から呼ばれるのはスタティックにしました。
まあ趣味の問題ですけど WinMain() みたいなグローバルスコープでクラスオブジェクトを直接いじるのは
何となくエレガントで無いかななんて思ってみたりなんかしたりして。
だからオブジェクトの生成と破棄もクラスメソッド内で行います。
それで良いと思います。


アプリケーションクラスの実装

今回は何もしないウィンドウを作成するだけなんで、特に重要な内容は無いです。

BOOL App :: Begin( LPSTR lpCmdLine, int nCmdShow )
{
    pApp = new App();

    if ( pApp->Init() )
    {
        ShowWindow( pApp->hwnd, nCmdShow );
        UpdateWindow( pApp->hwnd );

        return TRUE;
    }

    End();

    return FALSE;
}

int App :: Run()
{
    if ( pApp != NULL )
    {
        return pApp->MessageLoop();
    }

    return -1;
}

VOID App :: End()
{
    if ( pApp != NULL )
    {
        delete pApp;
        pApp = NULL;
    }
}

int App :: MessageLoop()
{
    MSG msg;

    while ( GetMessage( & msg, NULL, 0, 0 ) )
    {
        TranslateMessage( & msg );
        DispatchMessage( & msg );
    }

    return ( int ) msg.wParam;
}

LRESULT App :: OnWmDestroy()
{
    PostQuitMessage( 0 );

    return 0;
}
解説

ウィンドウメッセージ処理は基底クラスの方でやってくれてるので、ここでは WM_DESTROY メッセージに対応すれば良いだけです。


ウィンドウ基底クラスの定義

ウィンドウクラスの登録と作成、およびメッセージ処理を行います。
不用意に適当な実態を作成しないよう、抽象クラスにしてみました。

class Window
{
public :

    static LRESULT CALLBACK WindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );

public :

    HWND   hwnd;
    UINT   umsg;
    WPARAM wparam;
    LPARAM lparam;

    Window() : hwnd( NULL ) {}
    virtual                  ~ Window();
    virtual BOOL             Init();
    virtual BOOL             Init( HWND hParent );
    virtual BOOL             Init( HWND hParent, UINT id );
    virtual BOOL             Init( HWND hParent, HMENU hMenu );
    virtual BOOL             Regist();
    virtual HWND             Create();
    virtual HWND             Create( HWND hParent );
    virtual HWND             Create( HWND hParent, UINT id );
    virtual HWND             Create( HWND hParent, HMENU hMenu );
    virtual inline LPCTSTR   GetRegistClassName() = 0;
    virtual inline UINT      GetRegistStyle()      { return CS_HREDRAW | CS_VREDRAW; }
    virtual inline WNDPROC   GetRegistWndProc()    { return WindowProc; }
    virtual inline int       GetRegistClsExtra()   { return 0; }
    virtual inline int       GetRegistWndExtra()   { return sizeof ( HANDLE ); }
    virtual inline HINSTANCE GetRegistInstance()   { return GetModuleHandle( NULL ); }
    virtual inline HICON     GetRegistIcon()       { return NULL; }
    virtual inline HCURSOR   GetRegistCursor()     { return LoadCursor( NULL, IDC_ARROW ); }
    virtual inline HBRUSH    GetRegistBackGround() { return NULL; }
    virtual inline LPCTSTR   GetRegistMenuName()   { return NULL; }
    virtual inline HICON     GetRegistIconSm()     { return NULL; }
    virtual inline HINSTANCE GetCreateInstance()   { return GetRegistInstance(); }
    virtual inline HMENU     GetCreateMenu()       { return NULL; }
    virtual inline HWND      GetCreateParent()     { return NULL; }
    virtual inline int       GetCreateHeight()     { return 0; }
    virtual inline int       GetCreateWidth()      { return 0; }
    virtual inline int       GetCreateTop()        { return 0; }
    virtual inline int       GetCreateLeft()       { return 0; }
    virtual inline LONG      GetCreateStyle()      { return WS_VISIBLE | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; }
    virtual inline LPCTSTR   GetCreateWindowName() { return NULL; }
    virtual inline LPCTSTR   GetCreateClassName()  { return GetRegistClassName(); }
    virtual inline DWORD     GetCreateExStyle()    { return 0; }
    virtual LRESULT          MessageProc();
    virtual inline LRESULT   DefaultProc()         { return DefWindowProc( hwnd, umsg, wparam, lparam ); }
    virtual inline LRESULT   OnWmDestroy()         { return DefaultProc(); }
};
解説

大量の仮想関数がありますが、ほとんどウィンドウクラスパラメータとウィンドウ作成パラメータです。


ウィンドウプロシージャ

ウィンドウ作成後に最初に送られてくるメッセージを捕まえてオブジェクトに紐付けます。
オブジェクトに紐付けられたウィンドウメッセージは、ウィンドウオブジェクトの仮想関数である MessageProc() で処理されます。

LRESULT CALLBACK Window :: WindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    Window * pWnd;

    if ( uMsg == WM_NCCREATE && lParam != NULL )
    {
        pWnd = ( Window * ) ( ( LPCREATESTRUCT ) lParam )->lpCreateParams;

        SetWindowLong( hWnd, GWL_USERDATA, * ( LONG * ) & pWnd );
    }
    else
    {
        * ( LONG * ) & pWnd = GetWindowLong( hWnd, GWL_USERDATA );
    }

    if ( pWnd != NULL )
    {
        pWnd->hwnd   = hWnd;
        pWnd->umsg   = uMsg;
        pWnd->wparam = wParam;
        pWnd->lparam = lParam;

        return pWnd->MessageProc();
    }

    return DefWindowProc( hWnd, uMsg, wParam, lParam );
}

LRESULT Window :: MessageProc()
{
    switch ( umsg )
    {
    case WM_DESTROY : return OnWmDestroy();
    }

    return DefaultProc();
}
解説

今回は WM_DESTROY メッセージにしか対応してないのでこれだけです。
DefWindowProc() 関数を DefaultProc() 関数で仮想化してるのは将来ダイアログボックスなどに対応するためです。


ウィンドウの初期化

Init() はウィンドウの初期化関数です。
ウィンドウクラスの登録とウィンドウ作成を行います。
派生クラスはルートウィンドウだったり子ウィンドウだったりするので、オーバーロード関数で複数の引数パタンを用意しました。
オーバーロードなのかオーバーライドなのかちょっと不安だったのでグーグルで確認しました。
これはオーバーロードのやつで間違いありません。

BOOL Window :: Init()
{
    return Init( GetCreateParent() );
}

BOOL Window :: Init( HWND hParent )
{
    return Init( hParent, GetCreateMenu() );
}

BOOL Window :: Init( HWND hParent, UINT id )
{
    return Init( hParent, ( HMENU ) ( UINT_PTR ) id );
}

BOOL Window :: Init( HWND hParent, HMENU hMenu )
{
    if ( Regist() && Create( hParent, hMenu ) )
    {
        return TRUE;
    }

    return FALSE;
}

BOOL Window :: Regist()
{
    WNDCLASSEX wcx;
    ATOM       atom;

    if ( GetClassInfoEx( GetModuleHandle( NULL ), GetRegistClassName(), & wcx ) != 0 )
    {
        return TRUE;
    }

    ZeroMemory( & wcx, sizeof wcx );

    wcx.cbSize        = sizeof ( WNDCLASSEX );
    wcx.style         = GetRegistStyle();
    wcx.lpfnWndProc   = GetRegistWndProc();
    wcx.cbClsExtra    = GetRegistClsExtra();
    wcx.cbWndExtra    = GetRegistWndExtra();
    wcx.hInstance     = GetRegistInstance();
    wcx.hIcon         = GetRegistIcon();
    wcx.hCursor       = GetRegistCursor();
    wcx.hbrBackground = GetRegistBackGround();
    wcx.lpszMenuName  = GetRegistMenuName();
    wcx.lpszClassName = GetRegistClassName();
    wcx.hIconSm       = GetRegistIconSm();
    atom              = RegisterClassEx( & wcx );

    return ( atom != 0 );
}

HWND Window :: Create()
{
    return Create( GetCreateParent() );
}

HWND Window :: Create( HWND hParent )
{
    return Create( hParent, GetCreateMenu() );
}

HWND Window :: Create( HWND hParent, UINT id )
{
    return Create( hParent, ( HMENU ) ( UINT_PTR ) id );
}

HWND Window :: Create( HWND hParent, HMENU hMenu )
{
    CREATESTRUCT cs;

    ZeroMemory( & cs, sizeof cs );

    cs.lpCreateParams = this;
    cs.hInstance      = GetCreateInstance();
    cs.hMenu          = hMenu;
    cs.hwndParent     = hParent;
    cs.cy             = GetCreateHeight();
    cs.cx             = GetCreateWidth();
    cs.y              = GetCreateTop();
    cs.x              = GetCreateLeft();
    cs.style          = GetCreateStyle();
    cs.lpszName       = GetCreateWindowName();
    cs.lpszClass      = GetCreateClassName();
    cs.dwExStyle      = GetCreateExStyle();

    return CreateWindowEx( cs.dwExStyle, cs.lpszClass, cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
                           cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams );
}

Window :: ~ Window ()
{
    if ( IsWindow( hwnd ) )
    {
        DestroyWindow( hwnd );
    }
}
解説

ごらんの通りウィンドウクラス登録パラメータとウィンドウ作成パラメータはすべて仮想関数でゲットします。
なので派生クラスは好きなようにウィンドウを定義できます。

今回は以上です。