ウインドウの正しい閉じ方(外からどうすればいいか?ウインドウの中では何をすればいいか?)

もくじ
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_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

メッセージループ

https://learn.microsoft.com/ja-jp/windows/win32/winmsg/about-messages-and-message-queues#message-loop

メッセージループ
WM_QUITを自前で処理する必要はないらしい

https://learn.microsoft.com/en-us/windows/win32/learnwin32/window-messages#the-message-loop