EternalWindows
ウインドウ管理 / ウインドウの作成

ウインドウクラスを登録したら、それを基にウインドウを作成できます。 ウインドウを作成するには、CreateWindowExを呼び出します。

HWND CreateWindowEx(
    DWORD dwExStyle,
    LPCTSTR lpClassName,
    LPCTSTR lpWindowName,
    DWORD dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hMenu,
    HINSTANCE hInstance,
    LPVOID lpParam
);

dwExStyleは、拡張ウインドウスタイルを指定します。 ウインドウスタイルとは、ウインドウの概観に関わる一連の定数のことです。 拡張という言葉はこの場合、より高度な概観(透明なウインドウなど)に 関する定数のことだと考えて問題ないでしょう。 指定できる定数の一部を次に示します。

定数 意味
WS_EX_ACCEPTFILES ウインドウにファイルをドロップした際、WM_DROPFILESが送られる。
WS_EX_LAYERED レイヤードウインドウを作成する。 不透明度は、SetLayeredWindowAttributesで指定する。
WS_EX_TOPMOST ウインドウを常に最前面に表示する。
WS_EX_CLIENTEDGE 縁が沈んで見える境界線を持つ。
WS_EX_WINDOWEDGE 盛り上がった縁の境界線を持つ。
WS_EX_OVERLAPPEDWINDOW WS_EX_CLIENTEDGEとWS_EX_WINDOWEDGEの組み合わせ。
WS_EX_TOOLWINDOW ウインドウタイトルに閉じるボタンだけが表示されるツールウインドウを作成する。 ウインドウハンドル引数にNULLを指定すると、タスクバーに表示されないトップレベルウインドウ扱いになる。 一方、ウインドウハンドル引数に親ウインドウハンドルを指定すると、 親ウインドウと連動して最小化するウインドウになる。 ウインドウハンドル引数に親ウインドウハンドルを指定して、さらにWS_CHILDとウインドウIDを指定した場合は、 親ウインドウ内だけで動作する子ウインドウになる。
WS_EX_COMPOSITED WinodwsXPより追加されたスタイル。 ウインドウへの描画が自動的にダブルバッファリングされ、ちらつきがなくなる。

lpClassNameは、ウインドウクラスのクラス名を指定します。 ここで指定したクラス名で識別されるウインドウクラスを基に、 ウインドウが作成されることになります。 当然、WNDCLASSEX構造体のlpszClassNameに指定した文字列を指定します。 lpWindowNameは、ウインドウのタイトルとする文字列を指定します。 dwStyleは、ウインドウスタイルを指定します。 指定できる定数の一部を次に示します。

定数 意味
WS_VISIBLE ウインドウを可視状態で作成する。
WS_OVERLAPPED オーバーラップウインドウを作成する。 オーバーラップウインドウはタイトルと枠を持つ。
WS_CAPTION タイトルバーを持つウインドウを作成する。
WS_SYSMENU タイトルバーにウインドウメニューボックスを配置する。
WS_THICKFRAME サイズ変更を許可する。
WS_MINIMIZEBOX 最小化ボタンの使用を許可する。
WS_MAXIMIZEBOX 最大化ボタンの使用を許可する。
WS_OVERLAPPEDWINDOW WS_OVERLAPPED、WS_CAPTION、WS_SYSMENU、WS_THICKFRAME、 WS_MINIMIZEBOX、WS_MAXIMIZEBOXの組み合わせ。

xは、ウインドウの水平位置を指定します。 yは、ウインドウの垂直位置を指定します。 nWidthは、ウインドウの幅を指定します。 nHeightは、ウインドウの高を指定します。 hWndParentは、親ウインドウのハンドルを指定します。 親ウインドウ事体を作成する場合は、NULLを指定します。 hMenuは、メニューのハンドルを指定します。 メニューが不要な場合は、NULLを指定します。 hInstanceは、インスタンスハンドルを指定します。 Windows2000以降では、この引数にNULLを指定することもできます。 lpParamは、CREATESTRUCT構造体のlpCreateParamsメンバに関連しますが、 これについては別の節で取り上げます。

今回のプログラムは、CreateWindowExの引数をいくつか工夫して指定し、 どのような効果が現れるのかを確認します。

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(WS_EX_TOPMOST, szAppName, szAppName, WS_OVERLAPPEDWINDOW & ~WS_MINIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, 300, 200, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

プログラムを実行すると、いつものような真っ白なウインドウが表示されますが、 幅や高さが狭くなっており、最小化ボタンが使用できなくなっています。 また、ウインドウは常に前面に表示されます。 これらは全て、CreateWindowExによってもたらされた結果です。

hwnd = CreateWindowEx(WS_EX_TOPMOST, szAppName, szAppName, WS_OVERLAPPEDWINDOW & ~WS_MINIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, 300, 200, NULL, NULL, hinst, NULL);	

第1引数は拡張スタイルでした。 WS_EX_TOPMOSTを指定すると、ウインドウが非アクティブになっても常に表示されます。 拡張スタイルが必要ないときは、0をしても構いません。 第2引数は、ウインドウクラスのクラス名、第3引数はウインドウのタイトルです。 このプログラムでは、どちらにもszAppNameを指定していますから、 クラス名とウインドウのタイトルの名前は同じになります。 これらを別々に分けたい場合は、szAppNameの代わりにszClassNameとszWindowNameという変数を用意し、 これらを第2引数と第3引数に指定すればよいでしょう。 第4引数は、ウインドウスタイルです。 この部分は少し複雑なことになっています。

WS_OVERLAPPEDWINDOW & ~WS_MINIMIZEBOX

先の表で示したように、WS_OVERLAPPEDWINDOWには基本的なスタイルが全て含まれます。 それに対して、& ~WS_MINIMIZEBOX するとはどういうことかというと、 WS_OVERLAPPEDWINDOWからWS_MINIMIZEBOXのビットを取り除くという処理になります。 こうすることにより、最小化以外の定数を順に指定する手間が省けます。 WS_OVERLAPPEDWINDOW ^ WS_MINIMIZEBOX とすることもできますが、 個人的には前者の方が分かりやすいと思います。

CreateWindowExの第6引数、第7引数はウインドウの位置です。 プログラムではCW_USEDEFAULTとしています。 この定数は関数側に位置を任せるという意味を持っています。 注意しなければならないのは、片方にCW_USEDEFAULTを指定したならば、 もう片方もCW_USEDEFAULTを指定しなければならない点です。 第8引数、第9引数はウインドウの幅と高さです。 これらの引数にもCW_USEDEFAULTを指定することもできます。 これ以降の引数については、必要になった際に説明します。

CreateWindowExが失敗する理由は、主に2つあります。 1つは、登録していないウインドウクラスのクラス名を指定した場合、 もう1つはWM_CREATEで-1を返した場合です。 クラス名が不定である場合は、内部でウインドウが作成されることはなく、 CreateWindowExはNULLを返すことになります。 WM_CREATEは、CreateWindowExが内部でウインドウプロシージャに送るメッセージで、 このメッセージが送られている時点で既にウインドウは作成されています。 もし、ここで-1以外の値を返した場合は、ウインドウハンドルが返ります。 しかし、-1を返すとCreateWindowExはウインドウを破棄するべきと解釈し、 ウインドウプロシージャにWM_DESTROYを送ります。 この結果、CreateWindowExはNULLを返すことになります。

MessageBoxとPostQuitMessage

CreateWindowExの失敗時にMessageBoxを表示したいような場合は、 いくつか難しい問題について考慮しなければなりません。 メッセージボックスは一種の独立したウインドウであり、 自身へのメッセージを取得するために、GetMessageを呼び出す設計になっています。 MessageBoxが直ぐに制御を返さないことからもわかるように、 この関数はGetMessageの呼び出しによるメッセージループを持っており、 WM_QUITを取得したときにはメッセージループから脱出することになっています。 アプリケーションがWM_DESTROYでPostQuitMessageを呼び出すのは、 言うまでもなくアプリケーションのメッセージープを終了させるためですが、 このポストされたWM_QUITはMessageBoxのメッセージループも取得できるため、 PostQuitMessageがMessageBoxを終了させるための機能として働いてしまうことがあります。 この結果、表示の音は鳴るものの、MessageBoxは直ぐに制御を返してしまうことになり、 引数に指定した文字列を確認することはできなくなります。

上記の問題を解決するには、WM_CREATEが-1を返してWM_DESTROYが送られたときには、 PostQuitMessageを呼ばないようにするのが最も妥当な方法でしょう。 CreateWindowExでMessageBoxを呼び出さないという方法もありますが、 メッセージループまでコードが進んでいないのに、WM_QUITがポストされるというのは、 やはり気分のいいものではありません。 具体的にどうするかですが、次のようなif文を追加するのはどうでしょうか。

case WM_DESTROY:
	if (g_hwndMain != NULL)
		PostQuitMessage(0);
	return 0;

このg_hwndMainという変数はグローバルに宣言されているものとし、 値がNULLであれば、CreateWindowExはまだ制御を返していないことを意味します。 そのようなときにWM_DESTROYが送られるというのは、 WM_CREATEが-1を返したから以外に理由は考えられませんから、 PostQuitMessageを呼び出すわけにはいけません。 WM_DESTROYからすればこの変数は一種のフラグとして働いているため、 本来ならばBOOL型として宣言するべきだと思われますが、 グローバルなウインドウハンドルがあれば他の場面でも何かと便利であるため、 HWND型として宣言することにしました。 ちなみに、WM_DESTROYのwParamとlParamは常に0となるため、 パラメータからWM_DESTROYが送られた原因を知ることはできません。 g_hwndMainは、次のように初期化します。

g_hwndMain = hwnd;

while (GetMessage(&msg, NULL, 0, 0) > 0) {
	TranslateMessage(&msg);
	DispatchMessage(&msg);
}

メッセージループに入る直前に、g_hwndMainにhwndを代入します。 これにより、以後にWM_DESTROYが送られたときはPostQuitMessageが呼ばれるようになります。



戻る