一.windows MDI程序基本框架
一个多文档程序由一个框架窗口作为顶层父窗口,一个客户区窗口作为子窗口的容器,以及若干个子窗口也称文档窗口组成,其组织框架如上图所示。
二 . MDI核心API
一个窗口类:
MDICLIENT
二个数据结构:
CLIENTCREATESTRUCT
定义如下
typedef struct tagCLIENTCREATESTRUCT { // ccs
HANDLE hWindowMenu; //与MDI子窗口关联的菜单
UINT idFirstChild; //第一个子窗口关联菜单的ID,以后每增加一个子窗口,在此值上+1
} CLIENTCREATESTRUCT
MDICREATESTRUCT
定义如下,与CreateWindow窗口参数相对性
typedef struct tagMDICREATESTRUCT { // mdic
LPCTSTR szClass;
LPCTSTR szTitle;
HANDLE hOwner;
int x;
int y;
int cx;
int cy;
DWORD style;
LPARAM lParam;
} MDICREATESTRUCT;
五个函数
- DefFrameProc(框架窗口缺省返回)
- DefMDIChildProc(子窗口过程函数,缺省返回)
- TranslateMDISysAccel(快捷键处理)
- ArrangeIconicWindows(图标处理)
- CreateMDIWindows(一般用于多线程)
十二条消息
除了WM_MDICREATE以外,还有其它的MDI相关的窗口消息. 列表如下:
WM_MDIACTIVATE 这条消息由应用程序发送给客户窗口,告诉客户窗口激活所选择的MDI子窗口. 当客户窗口受到消息后,
它将激活所选择的MDI子窗口和发送WM_MDIACTIVATE消息给将被激活的子窗口和将变为非活动窗口的子窗口.
这条消息的用途是双方面的:应用程序可以用它来激活所希望的子窗口.同时它又可以被MDI子窗口本身用作活动/非活动窗口的指示器.举个例子,假如每一个MDI子窗口都有不同的菜单,
那么当它变为活动或是非活动窗口的时候,它可以利用这个机会来改变框架窗口的菜单 WM_MDICASCADE WM_MDITILE
WM_MDIICONARRANGE 这些消息处理如何排列MDI子窗口. 举个例子,
假如你希望MDI子窗口排列成层叠的样式,发送WM_MDICASCADE消息给客户窗口.
WM_MDIDESTROY 发送这条消息给客户窗口来关闭一个MDI子窗口. 你应该使用这条消息而不是调用DestroyWindow
因为假如这个MDI子窗口最大化的话, th这条消息将会恢复框架窗口的标题. 假如你使用DestroyWindow,
框架窗口的标题将不会被恢复. WM_MDIGETACTIVE 发送这条消息检索当前活动MDI子窗口的句柄. WM_MDIMAXIMIZE
WM_MDIRESTORE 发送 WM_MDIMAXIMIZE来最大化MDI子窗口和WM_MDIRESTORE来将它恢复成以前的状态.
对于这些操作总是使用这些消息. 假如你使用参数为SW_MAXIMIZE来调用ShowWindow时,MDI子窗口最大化并没有问题,
但是当你试图将它恢复成以前的状态时,问题就来了. 但是你可以用调用ShowWindow来最小化MDI子窗口.
WM_MDINEXT 发送这条消息给客户窗口,根据wParam和lParam里相应的值来激活下一个或是前一个MDI子窗口.
WM_MDIREFRESHMENU 发送这条消息给客户窗口来刷新框架窗口的菜单. 注意在发送了这条消息之后,
你必须调用DrawMenuBar 来更新菜单条.
WM_MDISETMENU 发送这条消息给客户窗口来取代框架窗口的整个菜单或是窗口子菜单. 你必须使用这条消息而不是用SetMenu.
在发送了这条消息之后, 你必须调用DrawMenuBar来更新菜单条.
正常情况下当活动的MDI子窗口有它自己的菜单而且你希望用这个活动的子窗口自身的菜单来取代框架窗口的菜单时,你将使用这条消息.一个简单的MDI例子,仅仅创建一个MDI窗口,不做任何处理,如果需要处理,请在处理子窗口对象的消息:
#include <windows.h>
#include <tchar.h>
#include "resource.h"
LRESULT WINAPI FrameProc(HWND hFrame, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI ChildProc(HWND hFrame, UINT uMsg, WPARAM wParam, LPARAM lParam);
void ShowError(TCHAR *pszErrorPos);
//框架窗口类名
TCHAR g_szFrameClsname[] = _T("FrameWnd");
//应用程序名称
TCHAR g_szAppName[] = _T("MDIDemo");
//子窗口类名
TCHAR g_szChildName[] = _T("Child");
int WINAPI WinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd)
{
MSG msg = { 0 };
WNDCLASS wdFrame = { 0 };
HWND hFrame = NULL;
wdFrame.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);
wdFrame.hCursor = ::LoadCursor(NULL, IDC_ARROW);
wdFrame.hInstance = hInstance;
wdFrame.lpfnWndProc = FrameProc;
wdFrame.lpszClassName = g_szFrameClsname;
wdFrame.lpszMenuName = MAKEINTRESOURCE(IDR_MENU_FRAME);
wdFrame.style = CS_VREDRAW | CS_HREDRAW;
if (!RegisterClass(&wdFrame))
{
ShowError(_T("Registe FramClass"));
return -1;
}
wdFrame.lpfnWndProc = ChildProc;
wdFrame.lpszClassName = g_szChildName;
wdFrame.lpszMenuName = NULL;
if (!RegisterClass(&wdFrame))
{
ShowError(_T("Create Child"));
return -1;
}
hFrame = CreateWindow(g_szFrameClsname, g_szAppName, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
if (!hFrame)
{
ShowError(_T("Create FrameWnd"));
return - 1;
}
ShowWindow(hFrame, nShowCmd);
UpdateWindow(hFrame);
while (::GetMessage(&msg, NULL, 0, 0))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT WINAPI FrameProc(HWND hFrame, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hClient, hChild;
static HINSTANCE hInstance;
switch (uMsg)
{
case WM_CREATE:
{
//创建客户窗口
CLIENTCREATESTRUCT ccs = { 0 };
ccs.hWindowMenu = GetSubMenu(GetMenu(hFrame), 0);
ccs.idFirstChild = 500;//第一个子窗口与之关联的菜单项ID
hInstance = GetModuleHandle(NULL);
hClient = CreateWindow(_T("MDICLIENT"), NULL, WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,
0, 0, 0, 0,
hFrame, NULL, hInstance, &ccs);
if (!hClient)
{
ShowError(_T("Create Client"));
return -1;
}
}
return 0;
case WM_SIZE:
{
MoveWindow(hClient, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
}
return 0;
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case ID_FILE_OPEN:
{
//创建子窗口
MDICREATESTRUCT mcs = { 0 };
mcs.x = CW_USEDEFAULT;
mcs.y = CW_USEDEFAULT;
mcs.cx = CW_USEDEFAULT;
mcs.cy = CW_USEDEFAULT;
mcs.hOwner = hInstance;
mcs.style = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN;
mcs.szClass = g_szChildName;
hChild = (HWND)::SendMessage(hClient, WM_MDICREATE,
0, (LPARAM)(LPMDICREATESTRUCT)&mcs);
if (!hChild)
{
ShowError(_T("Create Child"));
return -1;
}
}
return 0;
case ID_FILE_CLOSE:
{
}
return 0;
default:
break;
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
break;
}
return DefFrameProc(hFrame, hClient, uMsg, wParam, lParam);
}
LRESULT WINAPI ChildProc(HWND hChild, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HINSTANCE hInstance;
static HWND hClent;
HMENU hMenu, hSubMenu;
switch (uMsg)
{
case WM_CREATE:
{
//关联对应的菜单,本程序只拥有一个菜单,如果想每个子文档关联不同的菜单也是可以的,最后需要重绘菜单 DrawMenu
hInstance = ::GetModuleHandle(NULL);
hMenu = ::LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU_FRAME));
hSubMenu = ::GetSubMenu(hMenu, 0);
SendMessage(GetParent(hChild), WM_MDISETMENU, (WPARAM)hMenu, (LPARAM)hSubMenu);
}
return 0;
case WM_QUERYENDSESSION:
case WM_CLOSE:
{
if (IDOK == MessageBox(hChild, _T("OK to Close Windows?"), _T("CLose Windows"), MB_ICONQUESTION | MB_OK))
{
break;
}
}
return 0;
default:
break;
}
return DefMDIChildProc(hChild, uMsg, wParam, lParam);
}
void ShowError(TCHAR *pszErrorPos)
{
DWORD dwError = GetLastError();
HLOCAL hLocal = NULL;
TCHAR szError[255] = _T("");
if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, dwError, MAKELANGID(LANG_CHINESE_SIMPLIFIED, SUBLANG_CHINESE_SIMPLIFIED),
(LPWSTR)&hLocal, 0, NULL))
{
if (NULL != hLocal)
{
wsprintf(szError, _T("%s Error is : %s"), pszErrorPos, LocalLock(hLocal));
MessageBox(NULL, szError, _T("Error"), MB_OK | MB_ICONERROR);
LocalFree(hLocal);
return;
}
}
}