ファイルビュワー作成日誌 #001 : ウィンドウズアプリケーション
はじめに
以前ウィンドウズプログラミング講座などという仰々しいタイトルを付けたもんだから、
敷居が高くなって何も書けない状態が続いてしまったので、ブログタイトルを作成日誌に変更しました。
これで公開ソースに多少バグがあっても大目に見てもらえるつーか何の問題もないよね。
一応バイナリ、テキスト、画像、動画、Xファイルの5種類のファイル形式に対応したビュワーは大方出来てるので、
ソース整理を兼ねて頭から連載する予定です。
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 ); } }
解説
ごらんの通りウィンドウクラス登録パラメータとウィンドウ作成パラメータはすべて仮想関数でゲットします。
なので派生クラスは好きなようにウィンドウを定義できます。
今回は以上です。