ウィンドウクラスの登録

ウィンドウを操作可能にする

ウィンドウの作成の項で作成したウィンドウはただ画面に表示されるだけで、何も操作ができないものでした。
これでは意味がないので、もう少しカスタマイズして実際に操作できるウィンドウを作ります。

前回のCreateWindow関数は、第一引数のlpClassNameには"STATIC"という文字列を指定していました。
これはシステムクラスというもので、Windowsがあらかじめ用意しているコントロール類で使用する名前です。
自分でウィンドウを作る際には、まず作りたいウィンドウのウィンドウクラスを作成し、システムに登録する必要があります。
そのためにWNDCLASS構造体またはWNDCLASSEX構造体を使用します。

マルチバイト文字版は「WNDCLASSA/WNDCLASSEXA」構造体、ワイド文字版は「WNDCLASSW/WNDCLASSEXW」構造体です。
AもWも付けない場合はVisual Studioの設定によりどちらになるかが決定されます。
(標準ではワイド文字版)
詳しくは三種類の同名関数を参照してください。

まずはWNDCLASS構造体を使用してみます。
先に全体のコードを示します。


#include <windows.h>

int APIENTRY wWinMain(
	HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPWSTR lpCmdLine, 
	int nCmdShow)
{
	//ウィンドウクラス登録用の構造体
	WNDCLASS wc;
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = DefWindowProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wc.lpszMenuName = NULL;
	wc.lpszClassName = L"MyTestApp";

	//上で作ったウィンドウクラスを登録
	if (!RegisterClass(&wc)) {
		return 0;
	}

	//クラス名をWNDCLASS構造体で指定したものに変更
	HWND hWnd = CreateWindow(
		//L"STATIC", L"テストアプリ",
		L"MyTestApp", L"テストアプリ",
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT,
		NULL,
		NULL,
		hInstance,
		NULL
	);

	if (!hWnd) {
		return 0;
	}
	ShowWindow(hWnd, nCmdShow); 

	MessageBox(NULL, L"ウィンドウアプリのテスト", L"タイトル", MB_OK);

	return 0;
}

このコードで作成されるウィンドウは見た目は前ページのものとほぼ同じですが、ウィンドウの位置やサイズの変更などの基本的な操作が可能です。

WNDCLASS構造体

typedef struct tagWNDCLASSW {
 UINT style;
 WNDPROC lpfnWndProc;
 int cbClsExtra;
 int cbWndExtra;
 HINSTANCE hInstance;
 HICON hIcon;
 HCURSOR hCursor;
 HBRUSH hbrBackground;
 LPCSTR lpszMenuName;
 LPCSTR lpszClassName;
} WNDCLASSW, *PWNDCLASSW, *NPWNDCLASSW, *LPWNDCLASSW;
ウィンドウクラスを定義する構造体。
この構造体をRegisterClass関数に渡すことでシステムにウィンドウクラスを登録する。

style

styleメンバはクラスのスタイルを指定します。
CreateWindow関数で使用したウィンドウスタイルとは別物です。
これもいくつかの定数が用意されていますが、通常はCS_HREDRAWCS_VREDRAWを組み合わせたものが用いられます。
これはウィンドウの横幅または縦幅が変更された時に再描画を行う、という意味になります。
(Horizontal/Vertical ReDraw)

その他に使う可能性のあるものとしてはCS_DBLCLKSがあります。
これはウィンドウ上でダブルクリックしたことをウィンドウに通知する場合に指定します。
これを指定しないとダブルクリックに対する処理ができません。

他にもいろいろありますが、ここでは使用しないので省略します。
ちなみに「CS_○○」は「Class Style」の意味です。

lpfnWndProc

lpfnWndProcメンバはウィンドウプロシージャを指定します。
ウィンドウプロシージャについては次ぺージで改めて説明しますが、ウィンドウに対して発生する通知(イベント)を処理するための関数です。

ここにはとりあえずDefWindowProcという関数を指定します。
これはウィンドウにごく基本的な操作を提供するデフォルト関数です。

なお、このメンバ変数はポインタ型ですが、関数名を直接書いた場合の評価値はその関数へのポインタです。
なのでアドレス演算子(&演算子)は必要ありません。
関数名の最後に丸括弧を書くと関数の実行の意味になるので、丸括弧は書かないでください。
(そもそも引数が足りないのでコンパイルエラーになります)


WNDCLASS wc;
wc.lpfnWndProc = DefWindowProc;

//これはエラー
wc.lpfnWndProc = DefWindowProc();

cbClsExtra、cbWndExtra

cbClsExtracbWndExtraメンバは追加のメモリ領域を確保するためのものです。
通常は必要ないので、両方とも0を指定します。

hInstance

hInstanceメンバはインスタンスハンドルです。
WinMain関数の引数で渡されるhInstanceをそのまま指定します。

hIcon

hIconメンバはアイコンの指定です。
アイコンの読み込みにはLoadIcon関数を使用します。

HICON LoadIconW(
 HINSTANCE hInstance,
 LPCWSTR lpIconName
);
モジュールhInstanceからアイコンlpIconNameをロードする。

第一引数はアイコンのデータが格納されているモジュールのインスタンスを指定します。
第二引数はアイコンの名前を指定します。
戻り値はHICON型で、hIconメンバに直接渡すことができます。

配布用などのきちんとしたソフトを作る場合はアイコンを自前で用意して読み込みますが、今回はシステムがあらかじめ用意しているアイコンを使用します。

第一引数にNULLを指定するとシステム定義のアイコンを使用します。
第二引数にはIDI_APPLICATIONという定数を使用します。
これはWindows上でアイコン未定義の実行ファイル(.exeファイル)に使用されるデフォルトのアイコンです。

hCursor

hCursorメンバはマウスカーソルの指定です。
アプリケーション独自のマウスカーソルを使用する場合にここで指定できます。
マウスカーソルの読み込みにはLoadCursor関数を使用します。

HCURSOR LoadCursorW(
 HINSTANCE hInstance,
 LPCWSTR lpCursorName
);
モジュールhInstanceからマウスカーソルlpCursorNameをロードする。

第一引数はカーソルのデータが格納されているモジュールのインスタンスを指定します。
第二引数はカーソルの名前を指定します。
戻り値はHCURSOR型で、hCursorメンバに直接渡すことができます。

使い方はhIconメンバの時と同じです。
第一引数にNULLを指定するとシステム既定のカーソルを使用します。
IDC_ARROWは一般的なカーソルを意味する定数です。

LoadImage関数

マイクロソフトによると、LoadIcon関数とLoadCursor関数は非推奨というわけではありませんが、LoadImage関数の使用を促しています。
といってもWindows10の時点ではまだ使用可能ですし、LoadImage関数は引数が多くて少し面倒なのでLoadIcon関数やLoadCursor関数を使用しても問題はないと思います。
一応、LoadImage関数の説明をしておきます。

HANDLE LoadImageW(
 HINSTANCE hInst,
 LPCWSTR name,
 UINT type,
 int cx,
 int cy,
 UINT fuLoad
);
アイコン、カーソル、ビットマップをロードする。

使用箇所だけを抜粋します。


WNDCLASSEX wcex;
wcex.hIcon = (HICON)LoadImage(
	NULL,
	IDI_APPLICATION, IMAGE_ICON,
	0, 0,
	LR_DEFAULTSIZE | LR_SHARED);
wcex.hCursor = (HCURSOR)LoadImage(
	NULL,
	IDC_ARROW, IMAGE_CURSOR,
	0, 0,
	LR_DEFAULTSIZE | LR_SHARED);

第一、第二引数はhIcon/LoadCursor関数と同じです。

第三引数(type)はロードする画像のタイプを定数で指定します。
アイコンならIMAGE_ICON、カーソルならIMAGE_CURSOR、ビットマップならIMAGE_BITMAPを指定します。
その他Enhanced Metafile(IMAGE_ENHMETAFILEを指定)というものもあります。

第四、第五引数(cxcy)は画像のサイズです。
ここに0を指定し、次の第六引数でLR_DEFAULTSIZEを指定すると、システムが最適なサイズを自動的に設定する仕組みになっています。

第六引数(fuLoad)はロードオプションです。
ここではLR_DEFAULTSIZELR_SHAREDを指定しています。
LR_SHAREDを指定すると、同じ画像を2回以上ロードする場合に2回目以降は以前にロードしたハンドルを返します。
(つまりシェアする)
リソースが不要になると自動的に破棄されます。
ウィンドウクラスへの登録の場合は必ずこのオプションを指定します。
この二つ以外にも定数は用意されていますが、今は関係ないので省略します。

戻り値はHANDLE型で、これはHICON型HCURSOR型にキャストできます。

hbrBackground

hbrBackgroundメンバはウィンドウの背景色の指定です。
ここではCOLOR_WINDOWという定数に1を加算したものを指定しています。
hbrBackgroundメンバはHBRUSH型なので、HBRUSH型へのキャストが必要です。

この定数はシステムカラーというもので、Windowsのユーザー設定によって実際に使用される色が変わります。
定数の実体は単なる整数値で、0から順に番号が割り振られています。
しかしHBRUSH型に0を指定するとNULLの意味になってしまうので、1をプラスしてひとつずらす決まりになっています。

HBRUSH型というのはブラシのハンドルです。
ブラシはグラフィック機能で使用するもので、特定の色で面を塗りつぶします。
システム定義のものや自分で定義することも可能です。

lpszMenuName

lpszMenuNameメンバはメニューの名前です。
今回は使用しないのでNULLを指定します。

lpszClassName

lpszClassNameメンバはウィンドウクラスの名前です。
名前は何でも構いませんが、他と被らないものが望ましいです。
ソフトウェアの名前や、開発者名(会社名)を組み合わせたものを指定するのが一般的です。

RegisterClass関数

作成したWNDCLASS構造体の変数は、RegisterClass関数を使用してシステムに登録します。

ATOM RegisterClassW(
 const WNDCLASSW *lpWndClass
);
ウィンドウクラスlpWndClassをシステムに登録する。
戻り値はアトム。
失敗した場合は0を返す。

引数はWNDCLASSWのポインタなので、アドレス演算子が必要です。

アトムというのは実体は整数値で、文字列を整数値で識別するための仕組みです。
文字列をシステムに登録し、番号を割り振っておくことで、整数値で文字列を管理することができます。
今回は登録はせず、関数の成功/失敗の判定にのみ使用しています。

ウィンドウクラスの登録後、CreateWindow関数でウィンドウを作成します。
CreateWindow関数の第一引数lpClassNameを、WNDCLASS構造体のlpszClassNameメンバで指定した文字列と同じものにすることで、登録したウィンドウクラスの機能を持ったウィンドウを作成することができます。

WNDCLASSEX構造体

ウィンドウクラスの定義にはWNDCLASSEX構造体を使用することもできます。
WNDCLASS構造体との違いは、最初と最後にメンバが一つずつ追加されている点です。


#include <windows.h>

int APIENTRY wWinMain(
	HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPWSTR lpCmdLine, 
	int nCmdShow)
{
	//WNDCLASSEX
	WNDCLASSEX wcex;
	wcex.cbSize = sizeof(WNDCLASSEX); //追加メンバ
	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = DefWindowProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = L"MyTestApp";
	wcex.hIconSm = NULL; //追加メンバ

	//RegisterClassEx
	if (!RegisterClassEx(&wcex)) {
		return 0;
	}

	HWND hWnd = CreateWindow(
		L"MyTestApp", L"テストアプリ",
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT,
		NULL,
		NULL,
		hInstance,
		NULL
	);

	if (!hWnd) {
		return 0;
	}
	ShowWindow(hWnd, nCmdShow); 

	MessageBox(NULL, L"ウィンドウアプリのテスト", L"タイトル", MB_OK);

	return 0;
}
typedef struct tagWNDCLASSEXW {
 UINT cbSize;
 UINT style;
 WNDPROC lpfnWndProc;
 int cbClsExtra;
 int cbWndExtra;
 HINSTANCE hInstance;
 HICON hIcon;
 HCURSOR hCursor;
 HBRUSH hbrBackground;
 LPCWSTR lpszMenuName;
 LPCWSTR lpszClassName;
 HICON hIconSm;
} WNDCLASSEXW, *PWNDCLASSEXW, *NPWNDCLASSEXW, *LPWNDCLASSEXW;
ウィンドウクラスを定義する構造体。
この構造体をRegisterClassEx関数に渡すことでシステムにウィンドウクラスを登録する。

WNDCLASS構造体から追加されたメンバは以下です。

cbSize

cbSizeメンバはWNDCLASSEX構造体のサイズを指定します。
具体的にはsizeof(WNDCLASSEXW)の戻り値を指定します。

ちなみにsizeof演算子の戻り値はsize_t型ですが、Visual Studioではunsigned int型の別名なので、結局はUINT型と同じものです。

hIconSm

hIconSmメンバは小さいアイコンを指定します。
これにNULLを指定した場合、hIconメンバで指定したアイコンリソースから適切なサイズのアイコンを検索します。

RegisterClassEx関数

WNDCLASSEX構造体変数は、RegisterClassEx関数を使用してシステムに登録します。

ATOM RegisterClassExW(
 const WNDCLASSEXW *lpWndClassEx
);
ウィンドウクラスlpWndClassExをシステムに登録する。
戻り値はアトム。
失敗した場合は0を返す。

WNDCLASS構造体ならRegisterClass関数を、WNDCLASSEX構造体ならRegisterClassEx関数を使用する必要があります。

処理ごとに関数にまとめておく

コードがちょっと長くなってきたので、このあたりでまとまった処理ごとに関数化しておきましょう。
(別に関数化しなくても動きますが、コードが読みにくくなります)


#include <windows.h>

//グローバル変数
HINSTANCE hInst;	//インスタンス
WCHAR szTitle[] = L"テストアプリ";		//タイトル
WCHAR szWindowClass[] = L"MyTestApp";	//ウィンドウクラス名

//関数プロトタイプ宣言
ATOM MyRegisterClass(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);

int APIENTRY wWinMain(
	HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPWSTR lpCmdLine, 
	int nCmdShow)
{
	MyRegisterClass(hInstance);
	if (!InitInstance(hInstance, nCmdShow))
	{
		return 0;
	}

	MessageBox(NULL, L"ウィンドウアプリのテスト", L"タイトル", MB_OK);

	return 0;
}

//ウィンドウクラスの登録
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;
	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = DefWindowProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = szWindowClass;
	wcex.hIconSm = NULL;

	return RegisterClassEx(&wcex);
}

//ウィンドウの作成
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
	//グローバル変数にインスタンスハンドルを格納する
	hInst = hInstance; 

	HWND hWnd = CreateWindow(
		szWindowClass, szTitle,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT,
		NULL,
		NULL,
		hInstance,
		NULL
	);

	if (!hWnd) {
		return FALSE;
	}
	ShowWindow(hWnd, nCmdShow);
	return TRUE;
}

変数名や関数名はVisual Studioが自動生成するコードで使われているものと同じにしています。

複数の関数から共通して使用されるデータはグローバル変数化しています。
グローバル変数はあまり使用すべきではありませんが、インスタンスハンドルやクラス名、ウィンドウタイトル程度ならば許容範囲かと思います。
ちなみにWCHAR型はwchar_t型の別名です。

ウィンドウクラスの登録やウィンドウの作成を関数化したことで、WinMain関数がすっきりしました。
MyRegisterClass関数やInitInstance関数に変更がある場合はその都度説明します。
特に説明がない場合はこのコードと同じということです。