MDI とメッセージ


MDI 子ウィンドウを制御する

MDI 子ウィンドウを自由に作成することができるようになりました
次の課題は、この MDI 子ウィンドウをプログラムから制御し、操作することです

MDI 子ウィンドウを制御しているのは MDI クライアントウィンドウです
プログラムは、この MDI クライアントウィンドウにメッセージを送って要求を伝えます
必要でない限り MDI 子ウィンドウを直接操作するようなことは避けるべきでしょう

MDI 子ウィンドウに対して、どのような操作が必要になるでしょうか
例えば、ユーザーがメニューから「保存」を選択した場合
どの MDI 子ウィンドウのドキュメントが対象なのかを調べる必要があります
この動作を実現するには、どの子ウィンドウがアクティブになっているかを知る必要があります

特定の子ウィンドウをアクティブにするには WM_MDIACTIVATE メッセージを使います
これは、通常はアクセラレータによって処理されるものですが
特定のウィンドウをアクティブにしたい場合に送信する必要があります

WPRAM にはアクティブにする MDI 子ウィンドウのハンドルを
LPARAM は送信する場合はなにも指定しません
ただし、受信した時は非アクティブになろうとしている MDI 子ウィンドウのハンドルです
戻り値は 0 です

現在アクティブである MDI 子ウィンドウを取得したいならば
WM_MDIGETACTIVE メッセージを送信します
パラメータはありません
成功すれば現在アクティブな MDI 子ウィンドウが、そうでなければ NULL が返ります

また、指定したハンドルを基準に次の子ウィンドウをアクティブにしたいならば
WM_MDIACTIVATE よりも WM_MDINEXT を使います
WPARAM には基準となる MDI 子ウィンドウのハンドルで、この次のウィンドウが対象です
LPARAM に 0 を指定すれば次のウィンドウがアクティブになり
それ以外を指定すれば前のウィンドウがアクティブになります。戻り値は常に 0 です

クライアントウィンドウは、乱雑した MDI 子ウィンドウを整列させる方法も提供しています
ウィンドウを並べて表示するには WM_MDICASCADE メッセージを使えます
これは、最小化されていない MDI 子ウィンドウを重ねて並べます

WPARAM には MDITILE_SKIPDISABLED 定数を指定することができます
これは、使用禁止状態の MDI 子ウィンドウを対象から外すことを意味しています
LPARAM 使用しません
成功すれば 0 以外、失敗すれば 0 が返ります

最小化されている MDI 子ウィンドウを整列させるには
WM_MDIICONARRANGE メッセージを使います
パラメータ、戻り値共にありません
MDI 子ウィンドウは最小化しても移動させることができるため、並べなおすことができると便利です

WM_MDITILE メッセージを使えば、ウィンドウ一杯に並べることもできます
これを使えば、最小化されていない MDI 子ウィンドウを並べられます
WPARAM には、どのように並べるかを表す定数を指定します

定数解説
MDITILE_HORIZONTALMDI クライアントウィンドウの横幅一杯に
上下に並べて表示する
MDITILE_SKIPDISABLED使用禁止状態の MDI 子ウィンドウを除外する
MDITILE_VERTICALMDI クライアントウィンドウの高さ一杯に
左右に並べて表示する

LPARAM は使用しません
成功した時は 0 以外、失敗した時は 0 を返します

また、指定ウィンドウを最大化や元に戻す処理も存在します
最大化するには WM_MDIMAXIMIZE メッセージを
元に戻すには WM_MDI_RESTORE メッセージを送信します

これら二つのメッセージの追加情報と戻り値は同じで
WPARAM に対象となる MDI 子ウィンドウのハンドルを指定します
LPARAM は使用しません。戻り値は常に 0 です

最後に、WM_MDICREATE に対照の WM_MDIDESTROY メッセージを紹介します
これは、名前から理解できるように MDI 子ウィンドウを閉じます
WPARAM に閉じたい MDI 子ウィンドウのハンドルを指定します
LPARAM は使用しません。戻り値は常に 0 です

さて、どっと MDI で使用するメッセージを列挙しましたが
これらは単純なメッセージなので、理解するのは簡単でしょう
これらのメッセージは、全て MDI クライアントウィンドウに渡します
//resource.h
#define IDM_EXIT 0x1001
#define IDM_NEW 0x2001
#define IDM_CAS 0x2002
#define IDM_ARR 0x2003
#define IDM_MAX 0x2004
#define IDM_NEXT 0x2005
#define IDM_RES 0x2006
#define IDM_TILH 0x2007
#define IDM_TILV 0x2008
#define IDM_DEL 0x2009
//リソーススクリプト
#include "resource.h"

KITTY MENU {
	POPUP "File(&F)" {
		MENUITEM "終了(&X)" , IDM_EXIT
	}
	POPUP "Window(&W)" {
		MENUITEM "新規作成(&N)" , IDM_NEW
		MENUITEM "重ねて表示(&C)" , IDM_CAS
		MENUITEM "最小化を整列(&A)" , IDM_ARR
		MENUITEM "最大化(&M)" , IDM_MAX
		MENUITEM "次のウィンドウ(&E)" , IDM_NEXT
		MENUITEM "元のサイズ(&R)" , IDM_RES
		POPUP "並べて表示(&T)" {
			MENUITEM "上下(&H)" , IDM_TILH
			MENUITEM "左右(&V)" , IDM_TILV
		}
		MENUITEM "閉じる(&D)" , IDM_DEL
	}
}
#include <windows.h>
#include "resource.h"

#define TITLE TEXT("Kitty on your lap")
#define MDI_FRAME TEXT("FRAMEWINDOW")
#define MDI_CHILD TEXT("MDICHILD")

#define ID_CHILDWND 0x100

HINSTANCE hIns;
HWND hClient;

LRESULT CALLBACK FrameProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	CLIENTCREATESTRUCT ccsClient;
	HWND hActive;

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		ccsClient.hWindowMenu = GetSubMenu(GetMenu(hWnd) , 1);
		ccsClient.idFirstChild = ID_CHILDWND;

		hClient = CreateWindow(TEXT("MDICLIENT") , NULL ,
			WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE ,
			0 , 0 , 0 , 0 , hWnd , (HMENU)1 , hIns , &ccsClient
		);
		return 0;
	case WM_COMMAND:
		hActive = (HWND)SendMessage(hClient , WM_MDIGETACTIVE , 0 , 0);
		switch(LOWORD(wp)) {
		case IDM_EXIT:
			SendMessage(hWnd , WM_CLOSE , 0 , 0);
			return 0;
		case IDM_NEW:
			CreateMDIWindow(MDI_CHILD , TITLE , 0 ,
				CW_USEDEFAULT , CW_USEDEFAULT ,
				CW_USEDEFAULT , CW_USEDEFAULT ,
				hClient , hIns , 0
			);
			return 0;
		case IDM_CAS:
			SendMessage(hClient , WM_MDICASCADE ,
				MDITILE_SKIPDISABLED , 0);
			return 0;
		case IDM_ARR:
			SendMessage(hClient , WM_MDIICONARRANGE , 0 , 0);
			return 0;
		case IDM_MAX:
			SendMessage(hClient ,
				WM_MDIMAXIMIZE , (WPARAM)hActive , 0);
			return 0;
		case IDM_NEXT:
			SendMessage(hClient ,
				WM_MDINEXT , (WPARAM)hActive , 0);
			return 0;
		case IDM_RES:
			SendMessage(hClient ,
				WM_MDIRESTORE , (WPARAM)hActive , 0);
			return 0;
		case IDM_TILH:
			SendMessage(hClient , WM_MDITILE ,
				MDITILE_HORIZONTAL , 0);
			return 0;
		case IDM_TILV:
			SendMessage(hClient , WM_MDITILE ,
				MDITILE_VERTICAL , 0);
			return 0;
		case IDM_DEL:
			SendMessage(hClient ,
				WM_MDIDESTROY ,(WPARAM)hActive , 0);
		}
	}
	return DefFrameProc(hWnd , hClient , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow) {
	HWND hWnd;
	MSG msg;
	WNDCLASS winc;

	hIns = hInstance;

	winc.style 		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= FrameProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)(COLOR_APPWORKSPACE + 1);
	winc.lpszMenuName	= TEXT("KITTY");
	winc.lpszClassName	= MDI_FRAME;

	if (!RegisterClass(&winc)) return 1;

	winc.lpfnWndProc		= DefMDIChildProc;
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszClassName	= MDI_CHILD;

	if (!RegisterClass(&winc)) return 1;

	hWnd = CreateWindow(
			MDI_FRAME , TITLE ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hWnd == NULL) return 1;

	while (GetMessage(&msg , NULL , 0 , 0 )) {
		if (!TranslateMDISysAccel(hClient , &msg)) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return msg.wParam;
}


これまで紹介してメッセージを駆使した MDI ウィンドウプログラムです
前回紹介したプログラムを拡張して、今回のメッセージを使えるようにしました
これは単純なわりに面白いプログラムだと思うので、ぜひ試してみてください

また、一部のメッセージは関数で表現することも可能になっています
例えば、最小化されたウィンドウを整列するには
ArrangeIconicWindows() 関数を使えます

UINT ArrangeIconicWindows(HWND hWnd);

hWnd には MDI 子ウィンドウを含むクライアントウィンドウを指定します
成功すれば最小化されているウィンドウの数、失敗すれば 0 が返ります

最小化されていないウィンドウを並べて表示する WM_MDIICONARRANGE は
代わりに CascadeWindows() 関数を使うこともできます
この処理に関しては、関数の方が比較的柔軟に対応できます
WORD WINAPI CascadeWindows(
	HWND hwndParent,
	UINT wHow , CONST RECT *lpRect,
	UINT cKids , const HWND FAR *lpKids
);
hwndParent は親ウィンドウのハンドルを指定します
MDI アプリケーションであれば、MDI クライアントウィンドウを指定しますが
NULL を指定すればデスクトップウィンドウが指定されたと判断されます

wHow は 0 または MDITILE_SKIPDISABLED 配置フラグを指定します
lpRect には、子ウィンドウが配置される長方形の範囲を示す RECT 構造体を指定します
NULL を指定すれば、ウィンドウの領域全体が対象になります

cKids には、lpKids ウィンドウハンドルの配列数を指定します
lpKids が NULL の場合、この引数は無視されます
lpKids は整列するウィンドウのウィンドウハンドルの配列へのポインタを指定します
この配列に含まれるウィンドウが対象となりますが、NULL を指定することもできます
NULL を指定すれば、クライアントウィンドウに含まれる子ウィンドウ全てが対象になります
関数が成功すれば操作されたウィンドウの数が、失敗すれば NULL が返ります

そして、最小化されていないウィンドウを並べて表示する WM_MDITILE に代わる
TileWindows() 関数を使うこともできます
WORD WINAPI TileWindows(
	HWND hwndParent,
	UINT wHow , CONST RECT *lpRect,
	UINT cKids , const HWND FAR *lpKids
);
hwndParent には親ウィンドウのハンドルを指定します
NULL を指定すれば、ディスクトップウィンドウが指定されたと判断します

wHow には、ウィンドウをどのように並べるかを示すフラグを指定します
これは、WM_MDITILE の WPARAM に指定したフラグと同じです
lpRect はウィンドウを整列させる領域を表す RECT 構造体へのポインタを指定します
NULL を指定すれば親ウィンドウのクライアント領域全体が対象になります

cKids は lpKids ウィンドウハンドルの配列数を指定します
lpKids が NULL の場合、この引数は無視されます
lpKids は整列するウィンドウハンドルの配列へのポインタを指定します
整列はこの配列に含まれるウィンドウが対象となりますが、NULL を指定することもできます
NULL を指定すれば、クライアントウィンドウに含まれる子ウィンドウ全てが対象になります
関数が成功すれば操作されたウィンドウの数が、失敗すれば NULL が返ります
#include <windows.h>
#include "resource.h"

#define TITLE TEXT("Kitty on your lap")
#define MDI_FRAME TEXT("FRAMEWINDOW")
#define MDI_CHILD TEXT("MDICHILD")

#define ID_CHILDWND 0x100

HINSTANCE hIns;
HWND hClient;

LRESULT CALLBACK FrameProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	CLIENTCREATESTRUCT ccsClient;
	HWND hActive;

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		ccsClient.hWindowMenu = GetSubMenu(GetMenu(hWnd) , 1);
		ccsClient.idFirstChild = ID_CHILDWND;

		hClient = CreateWindow(TEXT("MDICLIENT") , NULL ,
			WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE ,
			0 , 0 , 0 , 0 , hWnd , (HMENU)1 , hIns , &ccsClient
		);
		return 0;
	case WM_COMMAND:
		hActive = (HWND)SendMessage(hClient , WM_MDIGETACTIVE , 0 , 0);
		switch(LOWORD(wp)) {
		case IDM_EXIT:
			SendMessage(hWnd , WM_CLOSE , 0 , 0);
			return 0;
		case IDM_NEW:
			CreateMDIWindow(MDI_CHILD , TITLE , 0 ,
				CW_USEDEFAULT , CW_USEDEFAULT ,
				CW_USEDEFAULT , CW_USEDEFAULT ,
				hClient , hIns , 0
			);
			return 0;
		case IDM_CAS:
			CascadeWindows(hClient , 0 , NULL , 0 , NULL);
			return 0;
		case IDM_ARR:
			ArrangeIconicWindows(hClient);
			return 0;
		case IDM_MAX:
			SendMessage(hClient ,
				WM_MDIMAXIMIZE , (WPARAM)hActive , 0);
			return 0;
		case IDM_NEXT:
			SendMessage(hClient ,
				WM_MDINEXT , (WPARAM)hActive , 0);
			return 0;
		case IDM_RES:
			SendMessage(hClient ,
				WM_MDIRESTORE , (WPARAM)hActive , 0);
			return 0;
		case IDM_TILH:
			TileWindows(hClient , 
				MDITILE_HORIZONTAL , NULL , 0 , NULL);
			return 0;
		case IDM_TILV:
			TileWindows(hClient , 
				MDITILE_VERTICAL , NULL , 0 , NULL);
			return 0;
		case IDM_DEL:
			SendMessage(hClient ,
				WM_MDIDESTROY ,(WPARAM)hActive , 0);
		}
	}
	return DefFrameProc(hWnd , hClient , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow) {
	HWND hWnd;
	MSG msg;
	WNDCLASS winc;

	hIns = hInstance;

	winc.style 		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= FrameProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)(COLOR_APPWORKSPACE + 1);
	winc.lpszMenuName	= TEXT("KITTY");
	winc.lpszClassName	= MDI_FRAME;

	if (!RegisterClass(&winc)) return 1;

	winc.lpfnWndProc		= DefMDIChildProc;
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszClassName	= MDI_CHILD;

	if (!RegisterClass(&winc)) return 1;

	hWnd = CreateWindow(
			MDI_FRAME , TITLE ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hWnd == NULL) return 1;

	while (GetMessage(&msg , NULL , 0 , 0 )) {
		if (!TranslateMDISysAccel(hClient , &msg)) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return msg.wParam;
}
これは、上のプログラムのメッセージ処理を一部関数に置き換えたプログラムです
関数で表現したということ以外に変わりはないので、動作はまったく同じです


ArrangeIconicWindows()

UINT ArrangeIconicWindows(HWND hWnd);

最小化されているウィンドウを整列させます

hWnd - MDI 子ウィンドウを含むウィンドウのハンドルを指定します

戻り値 - 最小化されているウィンドウの数、失敗すれば 0

CascadeWindows()

WORD WINAPI CascadeWindows(
	HWND hwndParent,
	UINT wHow , CONST RECT *lpRect,
	UINT cKids , const HWND FAR *lpKids
);
最小化されていないウィンドウを並べて表示します

hwndParent - 親ウィンドウのハンドルを指定します
wHow - 0 または MDITILE_SKIPDISABLED 配置フラグを指定します
lpRect - 長方形の範囲を示す RECT 構造体を指定します
cKids - lpKids ウィンドウハンドルの配列数を指定します
lpKids - 整列するウィンドウのウィンドウハンドルの配列へのポインタを指定します

戻り値 - 操作されたウィンドウの数、失敗すれば 0

TileWindows()


WORD WINAPI TileWindows(
	HWND hwndParent,
	UINT wHow , CONST RECT *lpRect,
	UINT cKids , const HWND FAR *lpKids
);
最小化されていないウィンドウを並べて表示します

hwndParent - は親ウィンドウのハンドルを指定します
wHow - ウィンドウをどのように並べるかを示すフラグを指定します
lpRect - 整列させる領域を表す RECT 構造体へのポインタを指定します
cKids - lpKids ウィンドウハンドルの配列数を指定します
lpKids - 整列するウィンドウハンドルの配列へのポインタを指定します

戻り値 - 操作されたウィンドウの数、失敗すれば 0

wHow には次の定数のいずれかを指定することができます

定数解説
MDITILE_HORIZONTALMDI クライアントウィンドウの横幅一杯に
上下に並べて表示する
MDITILE_SKIPDISABLED使用禁止状態の MDI 子ウィンドウを除外する
MDITILE_VERTICALMDI クライアントウィンドウの高さ一杯に
左右に並べて表示する



前のページへ戻る次のページへ