もくじ
https://tera1707.com/entry/2022/02/06/144447
アプリ外側から、ウインドウを閉じるにはどうするのが正しいのか?
今のところ、閉じたいウインドウに、WM_CLOSE
を送ってあげるのが正しいと思う。
で、閉じる側のウインドウでも、WM_CLOSEが来たら、ウインドウを破棄して、メッセージループを閉じるようにしておいてあげる必要がある。
※作り方によっては、WM_CLOSEを全く無視するようにもできちゃうので。
msdocsに、ウインドウを閉じるときの流れの資料があったので、それを見ながら、閉じるときの流れを追いかけてみる。
https://learn.microsoft.com/ja-jp/windows/win32/learnwin32/closing-the-window
いまのところの正しい流れだと思っているもの
アプリ外部から、ウインドウを閉じるのはこうやる。
★ウインドウを閉じるまでの流れ★
- 別アプリから、閉じたいウインドウにWM_CLOSEを送信する
- WM_CLOSEが飛んできたときに、DestroyWindow(hWnd)を実行する
- この時点で、ウインドウは消える
- WM_DESTROYが飛んでくるので、そこでPostQuitMessage(0)を実行する(0はGetMessageの戻り値になる)
- ここまで来たら、GetMessageが0を返して、メッセージループを抜ける
- 通常は、プログラム終了する
※↑の図は、msdocsの図。閉じる際の確認が不要であれば、赤で×した部分は不要と思う。
WM_CLOSEについて
- WM_CLOSEをウインドウが受けると、閉じるかどうかの確認処理をMsgBox等で行える。
- WM_CLOSEは、「×」ボタンや
ALT+F4
押下でウインドウを閉じようとしたときにくるMsg。 - 確認が不要な場合は、確認無しで
DestroyWindow(hWnd)
を呼ぶべし。 - また、
DefWindowProc()
は標準で、WM_CLOSE到来時にDestroyWIndow()
を呼んでくれるので、破棄するだけならWM_CLOSEの自前の記述を省略してOK。 - WM_CLOSE到来時に、return *;すると、閉じる処理をキャンセルできる。
- WM_CLOSEは、「×」ボタンや
WM_DESTROYについて
- WinProc内から、
DestryWindow(hWnd)
を呼ぶと、その場でWindowが破棄されて、WM_DESTROY
が来る。 WM_DESTROY
が来たら、自前でPostQuitMessage(0);
を呼ぶべし。PostQuitMessage(0);
が呼ばれると、メッセージループのGetMessage()
が0を返して、メッセージループを抜ける。
PostQuitMessage(0);
の引数はメッセージループを抜けた後、GetMessage()
の第一引数のWPARAMとして取り出せる。(PostQuitMessage(99);
とかにしておくと、msg.wParamが99になる。)WM_CLOSEと違って、
DefWindowProc()
は標準でWM_DESTROY到来時にPostQuitMessage(0)
は実行してくれない。(戻り値に好きな値を入れないといけないからか?) (WM_QUITは特別扱いで、WinProcにくる前に優先?して処理されるっぽい)
WM_QUIT
閉じるときのmsdocsの図の中にWM_QUITが出てくるが、WM_QUITは、WindowProcに記述する必要はないらしい。
PostQuitMessage()を呼んで、メッセージキューに入れてもらうのがよいらしい。
(SendMessageでWM_QUITを呼んではいけない、とどこかに書いてあった)
その他メモ
WInProcがreturn 0をする、イコール、そのMsgを自前で処理しましたよ、ということになるっぽい。
参考プログラム
ほぼ、VisualStudio2022の↓のテンプレートそのままなのだが、一応実験に使ったコードを上げておく。 (プロジェクト名が「WindowsProject3」。このcpp以外はテンプレートのまま。)
#include "framework.h" #include "WindowsProject3.h" #define MAX_LOADSTRING 100 // グローバル変数: HINSTANCE hInst; // 現在のインターフェイス WCHAR szTitle[MAX_LOADSTRING]; // タイトル バーのテキスト WCHAR szWindowClass[MAX_LOADSTRING]; // メイン ウィンドウ クラス名 // このコード モジュールに含まれる関数の宣言を転送します: ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // グローバル文字列を初期化する LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadStringW(hInstance, IDC_WINDOWSPROJECT3, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance); // アプリケーション初期化の実行: if (!InitInstance (hInstance, nCmdShow)) return FALSE; HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT3)); MSG msg; // メイン メッセージ ループ: while (GetMessage(&msg, nullptr, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int) msg.wParam; } ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEXW wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT3)); wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT3); wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassExW(&wcex); } BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { hInst = hInstance; // グローバル変数にインスタンス ハンドルを格納する HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CLOSE: // WM_CLOSEは、DestroyWindow(hWnd);するだけなら // DefWindowProcでもやってくれるので、省略してもOK DestroyWindow(hWnd); break; case WM_DESTROY: // WM_DESTROYは、DefWindowProcでPostQuitMessageしてはくれないので必要 PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
参考
ウインドウを閉じる
https://learn.microsoft.com/ja-jp/windows/win32/learnwin32/closing-the-window
ウインドウプロシージャ―について
https://learn.microsoft.com/ja-jp/windows/win32/winmsg/about-window-procedures
メッセージループ
メッセージループ
WM_QUITを自前で処理する必要はないらしい
https://learn.microsoft.com/en-us/windows/win32/learnwin32/window-messages#the-message-loop