MDI とメニュー


フレームメニューの登録

前回は、MDI のフレームとクライアントウィンドウを作成し
単一の MDI 子ウィンドウの簡単な作り方を単純に説明しました

今回は、動的に生成、削除することができる MDI 子ウィンドウを作りましょう
この処理は一見難しそうですが(というか、実質難しいのですが…)
その処理のほとんどを Windows が担当してくれるため、非常に簡単に実装できます

前回のプログラムでは、できるだけ簡単にサンプルプログラムを表現するため
メニューを作りませんでしたが、MDI フレームウィンドウはメニューの存在は前提になっています
そこで、まず次のようなリソースファイルとヘッダファイルを作ってください
//resource.h
#define IDM_EXIT 0x1001
#define IDM_NEW 0x2001
//リソーススクリプト
#include "resource.h"

KITTY MENU {
	POPUP "File(&F)" {
		MENUITEM "終了(&X)" , IDM_EXIT
	}
	POPUP "Window(&W)" {
		MENUITEM "新規作成(&N)" , IDM_NEW
	}
}
見てのとおり、何の変哲もないリソーススクリプトです
これを、これまで通りウィンドウクラスに指定してフレームウィンドウに設定します

ここで重要なのが CLIENTCREATESTRUCT 構造体の hWindowMenu メンバです
このメンバには、作成された MDI 子ウィンドウを表すメニューアイテムを挿入するための
サブメニューのハンドルを指定します
メニューバーでもかまいませんが、そうなると MDI 子ウィンドウを生成するたびに
トップレベルのメニューバーに子ウィンドウの名前が追加されること担ってしまいます
#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;

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

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

		hClientWindow = CreateWindow(TEXT("MDICLIENT") , NULL ,
			WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE ,
			0 , 0 , 0 , 0 , hWnd , (HMENU)1 , hIns , &ccsClient
		);
		return 0;
	case WM_COMMAND:
		switch(LOWORD(wp)) {
		case IDM_NEW:
			CreateMDIWindow(MDI_CHILD , TITLE , 0 ,
				CW_USEDEFAULT , CW_USEDEFAULT ,
				CW_USEDEFAULT , CW_USEDEFAULT ,
				hClientWindow , hIns , 0
			);
			return 0;
		case IDM_EXIT:
			SendMessage(hWnd , WM_CLOSE , 0 , 0);
			return 0;
		}
	}
	return DefFrameProc(hWnd , hClientWindow , 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 )) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;
}


このプログラムは、Window メニューの新規作成を選択すれば
新しく MDI 子ウィンドウを作成し Window メニューのアイテムに
生成された MDI 子ウィンドウの名前を追加します
もちろん、MDI 子ウィンドウを閉じれば、関連付けられたメニューアイテムも削除されます


MDI アクセラレータ

MDI 子ウィンドウには、デフォルトでアクセラレータが用意されています
これを導入すれば、全てのユーザーは MDI アプリケーションにおいて
統一された操作性が与えられるでしょう

MDI デフォルトのアクセラレータキーストロークを処理するには
TranslateMDISysAccel() 関数を用いてメッセージループを処理します
BOOL TranslateMDISysAccel(
	HWND hWndClient ,
	LPMSG lpMsg
);
hWndClient には、MDI クライアントウィンドウのハンドルを
lpMsg には、メッセージキューから取り出した MSG 構造体へのポインタを指定します
メッセージがシステムコマンドに変換されたならば 0 以外、変換されなければ 0 を返します
変換された場合は、このメッセージを他の関数で処理してはいけません

メニューデフォルトのアクセサレータキーは次のようになっています

アクセラレータキー機能
Alt + -MDI 子ウィンドウのシステムメニューを開く
Ctrl + F4アクティブな MDI 子ウィンドウを閉じる
Ctrl + F6次の MDI 子ウィンドウをアクティブにする
Ctrl + Shift + F6前の MDI 子ウィンドウをアクティブにする

以下のサンプルプログラムは、上のプログラムにアクセラレータキーをサポートしたものです
リソーススクリプトとヘッダファイルは上のプログラムのものを使ってください
#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 hClientWindow;

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

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

		hClientWindow = CreateWindow(TEXT("MDICLIENT") , NULL ,
			WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE ,
			0 , 0 , 0 , 0 , hWnd , (HMENU)1 , hIns , &ccsClient
		);
		return 0;
	case WM_COMMAND:
		switch(LOWORD(wp)) {
		case IDM_NEW:
			CreateMDIWindow(MDI_CHILD , TITLE , 0 ,
				CW_USEDEFAULT , CW_USEDEFAULT ,
				CW_USEDEFAULT , CW_USEDEFAULT ,
				hClientWindow , hIns , 0
			);
			return 0;
		case IDM_EXIT:
			SendMessage(hWnd , WM_CLOSE , 0 , 0);
			return 0;
		}
	}
	return DefFrameProc(hWnd , hClientWindow , 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(hClientWindow , &msg)) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return msg.wParam;
}
MDI 子ウィンドウを生成してアクセラレータキーを試してみてください


TranslateMDISysAccel()

BOOL TranslateMDISysAccel(
	HWND hWndClient ,
	LPMSG lpMsg
);
MDI ウィンドウのウィンドウメニューコマンドで使われる
アクセラレータキーストロークを変換し、適切な子ウィンドウに送ります

hWndClient - MDI クライアントウィンドウのハンドルを指定します
lpMsg - メッセージキューから取り出した MSG 構造体へのポインタを指定します

戻り値 - 変換されたならば 0 以外、変換されなければ 0



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