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