プログラムの終了 ~ WM_CLOSE メッセージと DestroyWindow 関数
プログラムの終了処理の流れがひと目でわかる図を描いてみました。
一番下方にありますので、それだけでも見て行ってくださいね。
では前置きはこの位にして、プログラムをメニュー (「ファイル」- 「終了」) から終了できるように変更しましょう。 出来上がりはこのようになります。
図1. メニューを選択するとプログラム終了の確認ポップアップが表示される
ではさっそく、解説は後回しにして、いつも通り、先にコードを書いてビルドしてみましょう。
前回までの simple.cpp の WindowProc を以下のように変更します。変更箇所は背景色をグレーで示しています。
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; }
simple.cpp の WindowProc 関数のみの差し替えであることに気をつけてください。 ファイル全体ではありません。
リソースファイル (resource.rc)、 リソースヘッダファイル (resource.h)、メイクファイル (makefile) は前回と全く同様です。 前回の記事はこちら「メニュー、アイコン、バージョン情報の設定~リソースファイルの作成」。
以上、必要なファイル (simple.cpp, resource.rc, resource.h, app.ico, makefile) をひとつのディレクトリに保存し、 Visual Studio コマンドプロンプトから nmake してください。これで実行可能なプログラムがビルドできるはずです。
以下のような結果になれば成功です。-a というオプションをつけているのは、全てのファイルをコンパイルし直しているからです。
> nmake -a
Microsoft (R) Program Maintenance Utility Version 9.00.30729.01
Copyright (C) Microsoft Corporation. All rights reserved.
if not exist ".\chk/" mkdir ".\chk"
cl /nologo /MT /W3 /Fo".\chk\\" /Fd".\chk\\" /c /Zi /DWIN32
/DUNICODE /D_UNICODE "simple.cpp"
simple.cpp
rc /l 0x411 /fo".\chk\resource.res" /d DEBUG "resource.rc"
Microsoft (R) Windows (R) Resource Compiler Version 6.1.6723.1
Copyright (C) Microsoft Corporation. All rights reserved.
link.exe user32.lib gdi32.lib /nologo /subsystem:windows
/pdb:".\chk\simple.pdb" /machine:I386 /out:".\chk\simple.exe" /DEBUG
/RELEASE ".\chk\simple.obj" ".\chk\resource.res"
chk ディレクトリに出来た simple.exe を実行してみてください。上記のスクリーンショットのように、 ヘルプメニューが動くようになっているでしょうか。
ウィンドウの破棄とプログラムの終了
さて、動作が確認できたところで解説です。じっくり読んでいただければそんなに難しい話ではありません。
ファイルメニューの「終了」を選んだ時のイベントハンドラで、SendMessage 関数を使って、メインウィンドウに WM_CLOSE メッセージを送信します。
SendMessage 関数のプロトタイプは以下です。
LRESULT SendMessage( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam );
hWnd で指定したウィンドウに、メッセージ Msg を送信します。 メッセージに付随するデータがある場合は、wParam、lParam に設定します。 ここでは WM_CLOSE メッセージを送っていますが、WM_CLOSE メッセージの場合、wParam、lParam は特に何も使わないので 0 を指定します。
WM_CLOSE のイベントハンドラでは、MessageBox 関数でポップアップメッセージを表示しています。 "Are you sure you want to quit thie program?" (このプログラムを本当に終了しますか?) という質問に「はい」 選んだ場合、MessageBox 関数は戻り値として IDYES を返します。
「プログラムを終了しますか?」 という質問に「はい」と答えたわけですから、ウィンドウを破棄しましょう。
ウィンドウを破棄するために、DestroyWindow 関数を呼びましょう。
BOOL DestroyWindow ( HWND hWnd );
DestroyWindow 関数は hWnd で指定したウィンドウに、WM_DESTROY メッセージを送信してそれを破棄します。
メニューのイベントハンドラで直接、修了の確認をしないのはなぜ?
ところで、メニューのイベントハンドラではプログラム終了の確認を行わないのはなぜでしょうか?
上記の例では、わざわざメニューのイベントハンドラでは WM_CLOSE メッセージを送るだけであり、 DestroyWindow は行っていません。
この理由は簡単です。
それは、閉じるボタンがクリックされて、 終了処理に入った場合、WM_CLOSE メッセージがウィンドウに送信されるからです。この場合、終了確認をスキップできてしまいます。 このため、メニューイベントハンドラでは WM_CLOSE メッセージを送るだけにして、WM_CLOSE にてウィンドウの破棄を行います。
例えば、これがメモ帳のようなアプリケーションだとすると、このタイミングでファイルを保存して終了することが出来ます。
図2. 終了処理の流れ
いかがでしょうか?
この図をみていただければ、メニューのイベントハンドラでプログラムの終了確認を行わない理由は明らかだと思います。
WM_DESTROY メッセージのイベントハンドラでは、PostQuitMessage 関数を用いて、WM_QUIT メッセージをメッセージキューにポストします。 また、PostQuitMessage 関数への引数として、プログラムの終了コードを渡すことが出来ます。ここで渡した値は WM_QUIT メッセージの wParam 値となります。「ウィンドウのあるプログラム作成 Hello, World 以前」 で学んだように、 WM_QUIT メッセージを受け取ると、GetMessage 関数が FALSE となるため、メッセージループが終了します。
プログラムを終了させる手順は以上です。