EternalWindows
ウインドウ管理 / ウインドウサイズ

ウインドウの位置やサイズの決定をウインドウの作成時に限らず、 任意のタイミングで行えたら非常に便利です。 SetWindowPosを呼び出せば、ウインドウの位置やサイズを変更できることに加え、 Zオーダー(ウインドウが重なり合うときの列)の変更も可能となります。

BOOL SetWindowPos(
    HWND hWnd,
    HWND hWndInsertAfter,
    int X,
    int Y,
    int cx,
    int cy,
    UINT uFlags
);

hWndは、変更対象となるウインドウハンドルを指定します。 hWndInsertAfterは、Zオーダーを決めるためのウインドウハンドルを指定します。 hWndで指定したウインドウは、この引数で指定したウインドウの後ろに置かれます。 この引数は、次のいずれかの定数を取ることもできます。

定数 意味
HWND_BOTTOM ウインドウをZオーダーの最後に置く。
HWND_NOTOPMOST ウインドウを最前面ウインドウの後ろに置く。
HWND_TOP ウインドウをZオーダーの先頭に置く。
HWND_TOPMOST ウインドウを最前面ウインドウではないすべてのウインドウの前に置く。 このウインドウは、アクティブでないときにも最前面に表示される。

Xは、ウインドウの左上隅の新しいx座標を指定します。 Yは、ウインドウの左上隅の新しいy座標を指定します。 cxは、ウインドウの新しい幅を指定します。 cyは、ウインドウの新しい高さを指定します。 uFlagsは、ウインドウのサイズと位置の変更に関するフラグを指定します。 指定できる定数の一部を次に示します。

定数 意味
SWP_FRAMECHANGED SetWindowLongによるスタイル変更を有効にする。
SWP_NOMOVE XとYを使わない。
SWP_NOSIZE cxとcyを使わない。
SWP_NOZORDER hWndInsertAfterを使わない。
SWP_NOACTIVATE ウインドウをアクティブ化しない。 この定数を指定しない場合、ウインドウはアクティブ化される。

SWP_NOで始まる定数は非常に便利です。 たとえば、ウインドウの位置のみを変更したいような場合、 次のようにSetWindowPosを呼び出すことができます。

SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);

このコードでは、SWP_NOSIZEとSWP_NOZORDERを指定しているため、 Zオーダーとサイズに関する引数に適切な値を指定せずに済みます。 このおかげで、目的の引数のみを変更することができ、 変更するつもりのない値を事前に取得して指定することが不要になります。

SetWindowPosは、ウインドウの移動よりもサイズ調整によく使われます。 たとえば、ゲームではクライアント領域の幅と高さを固定したいことから、 SetWindowPosに目的の幅と高さを指定することがあります。

SetWindowPos(hwnd, NULL, 0, 0, 640, 480, SWP_NOMOVE | SWP_NOZORDER);

恐らく、このコードを書いた開発者は、クライアント領域のサイズが (640, 480)になることを望んでいると思われますが、実際には希望通りになりません。 SetWindowPosが要求するのは、クライアント領域のサイズではなくウインドウのサイズであるため、 SetWindowPosでクライアント領域のサイズを決定するならば、 そのサイズを考慮したウインドウのサイズを指定しなくてはなりません。 これは、AdjustWindowRectExで取得できます。

BOOL AdjustWindowRectEx(
    LPRECT lpRect,
    DWORD dwStyle,
    BOOL bMenu,
    DWORD dwExStyle
);

lpRectは、希望するクライアント領域のサイズを指定します。 関数から制御が返ると、この変数に指定したクライアント領域のサイズを考慮した ウインドウサイズが格納されます。 dwStyleは、ウインドウスタイルを指定します。 bMenuは、ウインドウがメニューを持つときはTRUE、そうでないときはFALSEを指定します。 dwExStyleは、拡張ウインドウスタイルを指定します。

次に、AdjustWindowRectExとSetWindowPosによるサイズ変更の例を示します。 クライアント領域のサイズは、(640, 480)になることを前提にし、 ウインドウスタイルはWS_OVERLAPPEDWINDOW、拡張ウインドウスタイルは0とします。

SetRect(&rc, 0, 0, 640, 480);
AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW, FALSE, 0);
SetWindowPos(hwnd, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER);

まず、SetRectという関数でRECT構造体のメンバをまとめて初期化します。 ここでは、クライアント領域のサイズを格納しなければならないため、 第2引数と第3引数にはクライアント領域の左上隅、 第4引数と第5引数にはクライアント領域の右下隅の値を指定します。 関数から制御が返った後、rc.right - rc.leftで求められるウインドウの幅は、 640というクライアント領域の幅を考慮しているので、 これをSetWindowPosに指定します。

今回のプログラムは、指定したクライアント領域を持つウインドウを作成します。 また、おまけの機能としてウインドウを中央に配置するコードが加えられています。

#include <windows.h>

void CenterWindow(HWND hwnd);
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;
	RECT       rc;
	DWORD      dwStyle;

	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;

	dwStyle = WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX;
	SetRect(&rc, 0, 0, 640, 480);
	AdjustWindowRectEx(&rc, dwStyle, FALSE, 0);

	hwnd = CreateWindowEx(0, szAppName, szAppName, dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, 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_LBUTTONDOWN:
		CenterWindow(hwnd);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

void CenterWindow(HWND hwnd)
{
	int  x, y;
	int  nScreenWidth, nScreenHeight;
	RECT rc;

	GetWindowRect(hwnd, &rc);
	
	nScreenWidth  = GetSystemMetrics(SM_CXFULLSCREEN);
	nScreenHeight = GetSystemMetrics(SM_CYFULLSCREEN);

	x = (nScreenWidth - (rc.right - rc.left)) / 2;
	y = (nScreenHeight - (rc.bottom - rc.top)) / 2;

	SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

ウインドウのサイズ決定は、ウインドウが作成されてからSetWindowPosで行うこともできますが、 ウインドウを作成する時点で適切なサイズを指定したほうが分かりやすいと思い、 WinMainで次の処理を実行しています。

dwStyle = WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX;
SetRect(&rc, 0, 0, 640, 480);
AdjustWindowRectEx(&rc, dwStyle, FALSE, 0);

hwnd = CreateWindowEx(0, szAppName, szAppName, dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hinst, NULL);
if (hwnd == NULL)
	return 0;

CreateWindowExもSetWindowPos同様、ウインドウの幅と高さを要求するので、 AdjustWindowRectExを求めた値を指定すれば、 目的のクライアント領域のサイズを考慮したウインドウが作成されます。 クライアント領域のサイズを明示的に指定する以上、 最大化やサイズ変更が変更されることを防ぐため、 それらのウインドウスタイルのビットは取り除いています。

ウインドウ上でマウスの左ボタンを押すと、CenterWindowという自作関数が呼ばれます。 この関数は、SetWindowPosとその他の関数を呼び出して、 ウインドウをスクリーンの中央に配置します。

void CenterWindow(HWND hwnd)
{
	int  x, y;
	int  nScreenWidth, nScreenHeight;
	RECT rc;

	GetWindowRect(hwnd, &rc);
	
	nScreenWidth  = GetSystemMetrics(SM_CXFULLSCREEN);
	nScreenHeight = GetSystemMetrics(SM_CYFULLSCREEN);

	x = (nScreenWidth - (rc.right - rc.left)) / 2;
	y = (nScreenHeight - (rc.bottom - rc.top)) / 2;

	SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

考え方としては、スクリーンの幅からウインドウの幅を減算し、 その空いたスクリーンの幅の半分の位置にウインドウを移動させるというものです。 GetWindowRectは、ウインドウの現在の位置をRECT構造体で返す関数で、 (rc.right - rc.left)とすれば、ウインドウの幅が求まります。 GetSystemMetricsは、様々なシステムのメトリックを取得する関数で、 たとえば、カーソルやスクロールバーの幅を取得できます。 SM_CX(Y)FULLSCREENを指定すると、スクリーンの幅(高さ)が返ります。 新しい位置が求められたら、それをSetWindowPosに指定します。 ここでは、位置を変更するのみであってサイズやZオーダーは変更しませんから、 フラグにはSWP_NOSIZEとSWP_NOZORDERを指定します。


戻る