メッセージクラッカによるコードの書き換え
前回までに書いてきた simple.cpp のウィンドウプロシージャは以下の通りです。
LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { int id; HWND hwndCtl; UINT codeNotify; PAINTSTRUCT ps; HDC hdc; RECT rect; switch( uMsg ) { case WM_CLOSE: if ( IDYES == MessageBox ( hwnd, TEXT("Are you sure you want to quit this program?"), TEXT("Confirmation"), MB_YESNO ) ) { DestroyWindow(hwnd); } break; case WM_COMMAND: id = (int) LOWORD(wParam); hwndCtl = (HWND) lParam; codeNotify = (UINT) HIWORD(wParam); switch( id ) { case IDM_FILE_EXIT: SendMessage ( hwnd, WM_CLOSE, 0, 0 ); break; case IDM_HELP_ABOUT: ::MessageBox ( NULL, TEXT("Hello, About!"), TEXT("About This Program"), MB_OK ); break; } break; case WM_PAINT: hdc = BeginPaint ( hwnd, &ps ); GetClientRect ( hwnd, &rect ); DrawText( hdc, TEXT("Hello, world!"), -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE ); EndPaint( hwnd, &ps ); break; case WM_SIZE: InvalidateRect( hwnd, NULL, TRUE ); break; case WM_DESTROY: ::PostQuitMessage( 0 ); break; default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0; }
全ての処理がウィンドウプロシージャ内に記載されています。このままコードを書き足していくと、 異常に巨大な switch 文ができてしまい収集がつきません。
この問題に対処すべく、Windows SDK には メッセージクラッカ と呼ばれるマクロ群が用意されています。
メッセージクラッカは Windows SDK に付属する windowx.h に定義されています。
メッセージクラッカの仕組みについては、当サイト内の資料 メッセージクラッカ を参照してください。
メッセージクラッカを使ってコードを書き換えると、ウィンドウプロシージャは次のようになります。
LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch( uMsg ) { HANDLE_MSG( hwnd, WM_CLOSE, Main_OnClose ); HANDLE_MSG( hwnd, WM_COMMAND, Main_OnCommand ); HANDLE_MSG( hwnd, WM_DESTROY, Main_OnDestroy ); HANDLE_MSG( hwnd, WM_PAINT, Main_OnPaint ); HANDLE_MSG( hwnd, WM_SIZE, Main_OnSize ); default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0; }
だいぶすっきりしていませんか?
ここで登場した HANDL_MSG というのがマクロになってます。Main_OnClose, Main_OnCommand, ... というのは私が定義した関数です。それぞれ、以下の内容です。
void Main_OnClose( HWND hwnd ) { if ( IDYES == MessageBox ( hwnd, TEXT("Are you sure you want to quit this program?"), TEXT("Confirmation"), MB_YESNO ) ) { DestroyWindow(hwnd); } } void Main_OnCommand( HWND hwnd, int id, HWND hwndCtl, UINT codeNotify ) { switch( id ) { case IDM_FILE_EXIT: SendMessage ( hwnd, WM_CLOSE, 0, 0 ); break; case IDM_HELP_ABOUT: ::MessageBox ( NULL, TEXT("Hello, About!"), TEXT("About This Program"), MB_OK ); break; } } void Main_OnDestroy( HWND hwnd ) { ::PostQuitMessage( 0 ); } void Main_OnPaint( HWND hwnd ) { HDC hdc; PAINTSTRUCT ps; RECT rect; hdc = BeginPaint ( hwnd, &ps ); GetClientRect ( hwnd, &rect ); DrawText( hdc, TEXT("Hello, world!"), -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE ); EndPaint( hwnd, &ps ); } void Main_OnSize( HWND hwnd, UINT state, int cx, int cy ) { InvalidateRect( hwnd, NULL, TRUE ); }
それでは、どのようにしてこれらの関数のプロトタイプを決めれば良いか、説明します。
メッセージクラッカの使い方
一例として、もとの WM_COMMAND メッセージハンドラを、メッセージクラッカを用いて書き換える方法を説明します。
- windowsx.h をインクルードする
- windowsx.h を開く
- windowsx.h 内で WM_COMMAND を検索する
- Cls_OnCommand のプロトタイプをコメントからヘッダファイルにコピー&ペースト
名前は変えても構いません。 - Cls_OnCommand を書く
もとのウィンドウプロシージャの wParam や lParam から取得してきた値が、 関数への引数として渡されているので、それにあわせてコードも変更する。
- ウィンドウプロシージャの case WM_COMMAND: から break; を次の一文に置き換える。
HANDLE_MSG( hwnd, WM_COMMAND, Cls_OnCommand );
上記の手順を WM_COMMAND 以外のメッセージにも適用します。すると上記のような関数とウィンドウプロシージャが出来上がります。
出来上がり
コードの断片ばかりでわかりにくかったかもしれませんので、それぞれのファイルを示します。
simple.h
#pragma once #include <windows.h> #define WND_CLASS_NAME TEXT("My_Window") LRESULT CALLBACK WindowProc( HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam); void Main_OnClose( HWND hwnd ); void Main_OnCommand( HWND hwnd, int id, HWND hwndCtl, UINT codeNotify ); void Main_OnDestroy( HWND hwnd ); void Main_OnPaint( HWND hwnd ); void Main_OnSize( HWND hwnd, UINT state, int cx, int cy );
simple.cpp
#include "simple.h" #include <windowsx.h> #include "resource.h" int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { HWND hWnd; WNDCLASSEX wcl; wcl.cbSize = sizeof(WNDCLASSEX); wcl.hInstance = hInstance; wcl.lpszClassName = WND_CLASS_NAME; wcl.lpfnWndProc = WindowProc; wcl.style = NULL; wcl.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APP)); wcl.hIconSm = NULL; wcl.hCursor = LoadCursor(NULL, IDC_ARROW); wcl.lpszMenuName = MAKEINTRESOURCE (IDR_MENU_MAIN); wcl.cbClsExtra = 0; wcl.cbWndExtra = 0; wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); if(!RegisterClassEx(&wcl)) { return FALSE; } hWnd = CreateWindowEx( NULL, WND_CLASS_NAME, TEXT("Windows Programming Primer - Simple"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); if(!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); MSG msg; BOOL bRet; while( ( bRet = GetMessage( &msg, hWnd, 0, 0 ) ) != 0) { if (bRet == -1) { break; } else { DispatchMessage( &msg ); } } return msg.wParam; } LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch( uMsg ) { HANDLE_MSG( hwnd, WM_CLOSE, Main_OnClose ); HANDLE_MSG( hwnd, WM_COMMAND, Main_OnCommand ); HANDLE_MSG( hwnd, WM_DESTROY, Main_OnDestroy ); HANDLE_MSG( hwnd, WM_PAINT, Main_OnPaint ); HANDLE_MSG( hwnd, WM_SIZE, Main_OnSize ); default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0; } void Main_OnClose( HWND hwnd ) { if ( IDYES == MessageBox ( hwnd, TEXT("Are you sure you want to quit this program?"), TEXT("Confirmation"), MB_YESNO ) ) { DestroyWindow(hwnd); } } void Main_OnCommand( HWND hwnd, int id, HWND hwndCtl, UINT codeNotify ) { switch( id ) { case IDM_FILE_EXIT: SendMessage ( hwnd, WM_CLOSE, 0, 0 ); break; case IDM_HELP_ABOUT: ::MessageBox ( NULL, TEXT("Hello, About!"), TEXT("About This Program"), MB_OK ); break; } } void Main_OnDestroy( HWND hwnd ) { ::PostQuitMessage( 0 ); } void Main_OnPaint( HWND hwnd ) { HDC hdc; PAINTSTRUCT ps; RECT rect; hdc = BeginPaint ( hwnd, &ps ); GetClientRect ( hwnd, &rect ); DrawText( hdc, TEXT("Hello, world!"), -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE ); EndPaint( hwnd, &ps ); } void Main_OnSize( HWND hwnd, UINT state, int cx, int cy ) { InvalidateRect( hwnd, NULL, TRUE ); }
makefile, resource.rc 及び resource.h は前回と全く同様です。
これでビルドができるはずです。動作は前回と全く同様になれば成功です。