Win32 APIでウィンドウを作成する


開発環境

  • Visual Studio 2022
  • Windows11

最低限覚えるべき概念

  • ウィンドウクラス
  • ウィンドウプロシージャ
  • メッセージとメッセージキュー

ウィンドウクラス

ウィンドウクラスはウィンドウ生成における共通情報をまとめたテンプレート。 基本的にアプリケーションが起動時に作成 & 登録を行う。
アプリケーション終了時に登録が解除されるため、解除処理を書く必要はない。

PlantUML Diagram

参考: https://learn.microsoft.com/ja-jp/windows/win32/winmsg/window-classes

ウィンドウプロシージャ

ウィンドウプロシージャ(WndProc)は特定のクラスに属する全てのウィンドウからのメッセージを処理する関数。

メインウィンドウ用のWndProcは、WM_DESTROYというメッセージを受け取った際に、PostQuitMessage関数を実行するのが定石な気がする。

// ウィンドウプロシージャ
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg)
{
case WM_DESTROY:
// メッセージキューにWM_QUITメッセージをエンキュー
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}

参考: https://learn.microsoft.com/ja-jp/windows/win32/winmsg/window-procedures

メッセージとメッセージキュー

メッセージはシステム・アプリケーションから来るイベント通知。メッセージキューは、それらを一時的に格納するFIFOキュー。
キューを通さずウィンドウプロシージャが直接呼ばれることもある。

Windowsのデスクトップアプリでは、これらを以下のように組み合わせる。

PlantUML Diagram

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

ウィンドウの生成

ウィンドウの生成には、CreateWindowEx関数を使用する。その際、以下の要素が必要になる。

- 拡張ウィンドウスタイル
- ウィンドウクラス名
- ウィンドウのタイトル
- ウィンドウスタイル
- ウィンドウの左上の座標 (x,y)
- ウィンドウのサイズ (width, height)
- 親ウィンドウのハンドル
- メニューのハンドル
- ウィンドウを追跡するためのインスタンスハンドル
- 追加パラメータ

以下の4つはシンプルなウィンドウを作成する際には不要なのでNULLを指定するのがお勧め。

  • 親ウィンドウのハンドル
  • メニューのハンドル
  • 追加パラメータ

また以下の4つはCW_USEDEFAULTを指定するのが楽。

  • ウィンドウの左上の座標 (x,y)
  • ウィンドウのサイズ (width, height)

拡張ウィンドウスタイルとウィンドウスタイルは、とりあえず以下が無難。

  • 拡張ウィンドウスタイル:0
  • ウィンドウスタイル:WS_OVERLAPPEDWINDOW

興味がある場合はWS_OVERLAPPEDWINDOWからWinUser.hを開いて、どんなスタイルが定義されているか確認するのがお勧め。

実装

#include <Windows.h>
#include <string>
// ウィンドウプロシージャ
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg)
{
case WM_DESTROY:
// メッセージキューにWM_QUITメッセージをエンキュー
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
// ウィンドウクラスの生成と登録
WNDCLASS wc = {};
wc.lpfnWndProc = MainWndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = L"MainWindowClass";
RegisterClass(&wc);
// ウィンドウの生成
HWND hwnd;
hwnd = CreateWindowEx(
0,
L"MainWindowClass",
L"Main Window",
WS_OVERLAPPEDWINDOW |
WS_HSCROLL |
WS_VSCROLL,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL
);
// ウィンドウの表示
ShowWindow(hwnd, nShowCmd);
// メッセージループ
while (true)
{
MSG msg = {};
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Sleep(30);
}
return 0;
}