Windows MDI编程

一.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; 
  • 五个函数

    1. DefFrameProc(框架窗口缺省返回)
    2. DefMDIChildProc(子窗口过程函数,缺省返回)
    3. TranslateMDISysAccel(快捷键处理)
    4. ArrangeIconicWindows(图标处理)
    5. 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;
}
}

}
注意,这是未完成版本!!! 由于作者工作原因,暂时没有时间完成,但是基本功能已经实现,有个小问题如果你解决了欢迎发送给我,谢谢! 请用VS2010打开。内含四个DLL工程,及一个DLL 的测试工程。 其中一个问题是,打开子窗口之后子窗口内容没有显示,单击子窗口客户区之后其内容才显示,目前作者没有解决。 /************************************************************************/ /* MdiPlus框架说明 */ /************************************************************************/ /** 项目名称: MdiPlus框架 版本号: v1.0.1 第一作者: Jef 地址: 中国/江苏 日期: 20100616 电子邮箱: dungeonsnd@126.com 版权: 1.本框架完全开源。您可以免费使用本程序,但在用于商业用途前请获得第一作者的许可。 2.您使用本程序而导致任何伤害以及经济损失,由过错方依法承担所有责任, 一概与第一作者及合作单位无关。 3.如果您使用本程序则表示您已经同意此版本协议!否则请勿使用! 项目功能: MdiPlus框架是在Windows平台下把SDI程序行为与MDI程序行为结合在一起的编程手段。 框架最适合: a.以GDI方式输出为主的应用程序。 b.需要多种文档模板输出的MDI程序。 c.需要一个类似于SDI应用程序的固定(禁止改变大小)的View进行绘图。 框架充分进行模块化,把MDI的窗口创建类放在一个单独的DLL中(DllMdiWnd.dll),开发者应该继承 这个类来得到一个文档模板(这样继承者不必考虑创建细节,只负责绘图),并且每一个文档模板最好 放到一个DLL中(如ADoc.dll,BDoc.dll,CDoc.dll)。开发者建立主程序来调用ADoc.dll,BDoc.dll, CDoc.dll,每个文档模板(如ADoc.dll)可产生多个对应子窗口。 不过,DllMdiWnd.dll并没有提供view/doc支持,它的职责只是创建及销毁调用者的需要子窗口。 (如果子窗口是new创建出来的,ADoc.dll的基类CMdiPlus中自己会负责delete,开发者可以不调用 delete)。 当然,如果有必要你可以在DLL(A.dll,B.dll,C.dll)中实现view/doc支持。建议根据 需要开发独立的数据模型模块。 如果按照MVC模式的划分,DllMdiWnd.dll可以理解为C(控制器,创建与销毁窗口),继承类(如ADoc.dll, BDoc.dll,CDoc.dll)类似于V(视图,负责显示数据),这个框架没有提供M(模型,数据存储)。开发者 应该结合不同的文档模板来实现M。 版本历史: v1.0.1 20100616 第一版本 如何使用: 方法1. 进行项目开发时先建立DLL工程(建议选择 add mfc header win32 dll),然后把 DllMdiWnd.dll拷贝到新建项目中。根据应用需要的不同文档模板建立多个对应的Dll工程,这些Dll 都继承自DllMdiWnd.dll的导出类,假定为ADoc.dll,BDoc.dll,CDoc.dll,这些Dll隐式链接DllMdiWnd.dll。 接着建立支持doc/view类型的MDI MFC主程序工程,隐式链接ADoc.dll,BDoc.dll,CDoc.dll。 另外要特别注意的是,主程序要进行必要的修改,如CChildFrame中要修改窗口样式。建立的MDI 主程序运行过程中将完全类似于SDI程序。如,弹出子窗口时它的视图仍然保持最大化(普通的MDI应用 程序中任意一个子窗口从最大化还原时,其它窗口都将处于非最大化状态),并且它永远处于弹出的 子窗口下面,只能(且必须)建立一个CChildFrame。主程序的所有绘图工作都在这个 CChildFrame对应的view上进行。如果需要调用子窗口,只需要创建ADoc.dll(或BDoc.dll,CDoc.dll)导 出类的对象即可。子窗口的所有绘图工作都在ADoc.dll(或BDoc.dll,CDoc.dll)中实现,所以主程序与 ADoc.dll(或BDoc.dll,CDoc.dll)应该同时开发。 方法2. 开发者可以自己根据需要直接修改Demo版进行开发。 其它: 框架有点类似于多个文档模板的MDI应用程序,但是普通的MDI程序在新建一个文档前无法绘图,除非 子类化CMainFrame或截获CMDIClient的消息。这些方式不便进行模块化开发。MdiPlus框架主要优点是在 新建一个文档前就有一个View进行绘图,并且不同功能进行模块化,各模块放到Dll中(如果必要,你也 可以直接写成静态库链接到最终的执行程序里),适合一个项目多人同时进行开发。 **/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值