第16单元多文档界面程序 第16单元多文档界面程序 本单元教学目标 介绍多文档界面(MDI)程序的构造和编程方法。 学习要求 理解多文档界面(MDI)程序的构造,掌握其编程方法 授课内容 和框架窗口界面程序、单文档界面(SDI)程序和基于对话框的应用程序一样,多文档 界面(MDI)程序也是基本的MFC应用程序结构。MD程序的结构最复杂,功能也最强 其特点是用户一次可以打开多个文档,每个文档均对应不同的窗口:主窗口的菜单会自动随 着当前活动的子窗口的变化而变化:可以对子窗口进行层叠、平铺等各种操作;子窗口可以 在MD主窗口区域内定位、改变大小、最大化和最小化,当最大化子窗口时,它将占满 MDI主窗口的全部客户区。MD不仅可以在同一时间内同时打开多个文档,还可以为同一 文档打开多个视图。 16.1MDI应用程序 从程序员角度看,每个MDI应用程序必须有一个 CMDI Frame Wnd或其派生类的对象 该窗口称作MD框架窗口 CMDIFrameWnd是 FRame Wnd的派生类,除了拥有 CFrameWnd 类的全部特性外,还具有以下与MDI相关的特性: 1.与SD不同,MDI的框架窗口并不直接与一个文档和视图相关联。MDI的框架窗口 拥有客户窗口,在显示或隐藏控制条(包括工具条、状态栏、对话条)时,重新定位该子窗 2.MD客户窗口是MD1子窗口的直接父窗口,它负责管理主框架窗口的客户区以及 创建子窗口。每个MD主框架窗口都有且只有一个MD客户窗口 3.MD子窗口是 CMDIChildwnd或其派生类对象, CMDIChildwnd也是 CFrameWnd 的派生类,用于容纳视图和文档,相当于SDⅠ下的主框架窗口。每打开一个文档,框架就 自动为文档创建一个MDI子窗口。一个MD应用程序负责动态的创建和删除MDI子窗口 在任何时刻,最多只有一个子窗口是活动的(窗口标题栏颜色呈高亮显示)。MDI框架窗口始 终与当前活动子窗口相关联,命令消息在传给MDI框架窗口之前首先分派给当前活动子窗
第 16 单元 多文档界面程序 326 第 16 单元 多文档界面程序 本单元教学目标 介绍多文档界面(MDI)程序的构造和编程方法。 学习要求 理解多文档界面(MDI)程序的构造,掌握其编程方法。 授课内容 和框架窗口界面程序、单文档界面(SDI)程序和基于对话框的应用程序一样,多文档 界面(MDI)程序也是基本的 MFC 应用程序结构。MDI 程序的结构最复杂,功能也最强。 其特点是用户一次可以打开多个文档,每个文档均对应不同的窗口;主窗口的菜单会自动随 着当前活动的子窗口的变化而变化;可以对子窗口进行层叠、平铺等各种操作;子窗口可以 在 MDI 主窗口区域内定位、改变大小、最大化和最小化,当最大化子窗口时,它将占满 MDI 主窗口的全部客户区。MDI 不仅可以在同一时间内同时打开多个文档,还可以为同一 文档打开多个视图。 16.1 MDI 应用程序 从程序员角度看,每个 MDI 应用程序必须有一个 CMDIFrameWnd 或其派生类的对象, 该窗口称作 MDI 框架窗口。CMDIFrameWnd 是 CFrameWnd 的派生类,除了拥有 CFrameWnd 类的全部特性外,还具有以下与 MDI 相关的特性: 1.与 SDI 不同,MDI 的框架窗口并不直接与一个文档和视图相关联。MDI 的框架窗口 拥有客户窗口,在显示或隐藏控制条(包括工具条、状态栏、对话条)时,重新定位该子窗 口。 2.MDI 客户窗口是 MDI 子窗口的直接父窗口,它负责管理主框架窗口的客户区以及 创建子窗口。每个 MDI 主框架窗口都有且只有一个 MDI 客户窗口。 3.MDI 子窗口是 CMDIChildWnd 或其派生类对象,CMDIChildWnd 也是 CFrameWnd 的派生类,用于容纳视图和文档,相当于 SDI 下的主框架窗口。每打开一个文档,框架就 自动为文档创建一个 MDI 子窗口。一个 MDI 应用程序负责动态的创建和删除 MDI 子窗口。 在任何时刻,最多只有一个子窗口是活动的(窗口标题栏颜色呈高亮显示)。MDI 框架窗口始 终与当前活动子窗口相关联,命令消息在传给 MDI 框架窗口之前首先分派给当前活动子窗
第16单元多文档界面程序 4.在没有任何活动的MDI子窗口时,MDI框架窗口可以拥有自己的缺省菜单。当有 活动子窗口时,MDI框架窗口的菜单条会自动被子窗口的菜单所替代。框架会自动监视当 前活动的子窗口类型,并相应的改变主窗口的菜单。例如,在Ⅴ isual studio中,当选择对话 框模板编辑窗口或源程序窗口时,菜单会有所不同。但是,对于程序员来说,只需在 InitInstance()中注册文档时指定每一类子窗口(严格的讲是文档)所使用的菜单,而不必 显式的通过调用函数去改变主框架窗口的菜单,因为框架会自动完成这一任务。 5.MD框架窗口为层叠、平铺、排列子窗口和新建子窗口等一些标准窗口操作提供了 缺省的菜单响应。在响应新建子窗口命令时,框架调用 CDoc Template: CreateNewFrame() 为当前活动文档创建一个子窗口。 CreateNew Frame()不仅创建子窗口,还创建与文档相 对应的视图。 与开发基于对话框的应用程序和SD应用程序一样,使用 App Wizard可生成一个MD 应用程序框架,在此基础上,程序员可使用 ClassWizard和各种资源编辑器来充实自己的应 用程序 AppWizard为MDl程序框架创建了以下类: 类 “关于”对话框 CChildframe 子框架窗口,用于容纳视图 CMy App 应用程序类 绘图程序视图类 CMy View 绘图视图类 MAin Frame 框架窗口(用来容纳子窗口),是MDI应用程序的主窗口 可以看出,MDI比SD多了一个 CchildFrame(子框架窗口)类,而且 MAin Frame的 职责也不同了 另外,MD和SDI的初始化应用程序实例方法上也有所不同。MD1应用程序的 nitInstance()函数代码为: BOOL CDrawApp: InitInstance o //初始化工作 CMultiDocTemplate* pDocTemplate /MDI文档模板 pDoc Template new CMultiDocTemplate( IDR DRAWTYPE RUNTIME CLASS( CDrawDoc) RUNTIME CLASS(CChildFrame) RUNTIME CLASS(CDrawView)) MAin Frame* mAinfRame= new MAin Frame;//建立MDI主框架窗口
第 16 单元 多文档界面程序 327 口。 4.在没有任何活动的 MDI 子窗口时,MDI 框架窗口可以拥有自己的缺省菜单。当有 活动子窗口时,MDI 框架窗口的菜单条会自动被子窗口的菜单所替代。框架会自动监视当 前活动的子窗口类型,并相应的改变主窗口的菜单。例如,在 Visual Studio 中,当选择对话 框模板编辑窗口或源程序窗口时,菜单会有所不同。但是,对于程序员来说,只需在 InitInstance()中注册文档时指定每一类子窗口(严格的讲是文档)所使用的菜单,而不必 显式的通过调用函数去改变主框架窗口的菜单,因为框架会自动完成这一任务。 5.MDI 框架窗口为层叠、平铺、排列子窗口和新建子窗口等一些标准窗口操作提供了 缺省的菜单响应。在响应新建子窗口命令时,框架调用 CDocTemplate::CreateNewFrame() 为当前活动文档创建一个子窗口。CreateNewFrame()不仅创建子窗口,还创建与文档相 对应的视图。 与开发基于对话框的应用程序和 SDI 应用程序一样,使用 AppWizard 可生成一个 MDI 应用程序框架,在此基础上,程序员可使用 ClassWizard 和各种资源编辑器来充实自己的应 用程序。 AppWizard 为 MDI 程序框架创建了以下类: 类 说 明 CAboutDlg “关于”对话框 CChildFrame 子框架窗口,用于容纳视图 CMyApp 应用程序类 CmyDoc 绘图程序视图类 CMyView 绘图视图类 CMainFrame 框架窗口(用来容纳子窗口),是 MDI 应用程序的主窗口 可以看出,MDI 比 SDI 多了一个 CchildFrame(子框架窗口)类,而且 CMainFrame 的 职责也不同了。 另外,MDI 和 SDI 的初始化应用程序实例方法上也有所不同。MDI 应用程序的 InitInstance()函数代码为: BOOL CDrawApp::InitInstance() { … … // 初始化工作 CMultiDocTemplate* pDocTemplate; // MDI 文档模板 pDocTemplate = new CMultiDocTemplate( IDR_DRAWTYPE, RUNTIME_CLASS(CDrawDoc), RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CDrawView)); AddDocTemplate(pDocTemplate); CMainFrame* pMainFrame = new CMainFrame; // 建立 MDI 主框架窗口
if (!pAin Frame->LoadFrame(IDR MAINFRAME)) m pMainWnd->DragAcceptFileso /设置框架窗口特性 EnableShel10pen o Register ShellFi le Types ( True) //处理命令行 ParseCommandLine(cmdInfo) if ( ProcessShellCommand(cmdInfo)) return false pMainFrame-Show indow(m nCmdShow /显示主框架窗口 return TRUe 注册文档模板时,首先创建一个 MUlti DocTemplate类(对SD是 SIngle Doc Template 的模板对象,然后用 AddDoc Template()把它加入到文档模板链表中去。 CmultiDoc Template()构造函数有四个参数,第1个参数是文档使用的资源ID定义 第2个是文档类型,第3个是子窗口类型,第4个是视图类型。 与SD1不同,由于MDI的主框架窗口并不直接与文档相对应,因此无法通过创建文档 来创建主框架窗口,而需要自己去创建: CMain Frame* pMainFrame new MAinfRame if ( pMainFrame->LoadFrame (IDR MAINFRAME)) eturn False m pMainWnd mAinfRame 例16-1绘图程序。用户可以鼠标“拖曳”方式在视图中画直线段,线的粗细和颜色 可调。采用MD结构,可同时打开多个子窗口作图,所作图形可以文件形式保存在磁盘上。 说明:用 AppWizard生成一个MDl程序框架,在第4步打开 Advanced Options(高 级选项)对话框,在 Document Template Strings(文档模板字符串)选项卡中将 File extension (文件扩展名)设置为“pic”,即该程序的图形文件名后缀为“pic”。其他选项均使用缺省 设置 编辑该程序的主菜单,添加一个“颜色”选项和一个“宽度菜单”,其中包括4个选项, 其ID和 Caption分别设置为 Caption 命令消息响应函数 ID COLOR “颜色” OnColor ( ID WIDTHI “宽度=1” On Width () ID WIDTH3 宽度=3” On Width ( ID WIDTH5“宽度=5 On Widths ()
第 16 单元 多文档界面程序 328 if (!pMainFrame->LoadFrame(IDR_MAINFRAME)) return FALSE; m_pMainWnd = pMainFrame; m_pMainWnd->DragAcceptFiles(); // 设置框架窗口特性 EnableShellOpen(); RegisterShellFileTypes(TRUE); CCommandLineInfo cmdInfo; // 处理命令行 ParseCommandLine(cmdInfo); if (!ProcessShellCommand(cmdInfo)) return FALSE; pMainFrame->ShowWindow(m_nCmdShow); // 显示主框架窗口 pMainFrame->UpdateWindow(); return TRUE; } 注册文档模板时,首先创建一个 CMultiDocTemplate 类(对 SDI 是 CSingleDocTemplate) 的模板对象,然后用 AddDocTemplate()把它加入到文档模板链表中去。 CmultiDocTemplate()构造函数有四个参数,第 1 个参数是文档使用的资源 ID 定义, 第 2 个是文档类型,第 3 个是子窗口类型,第 4 个是视图类型。 与 SDI 不同,由于 MDI 的主框架窗口并不直接与文档相对应,因此无法通过创建文档 来创建主框架窗口,而需要自己去创建: CMainFrame* pMainFrame = new CMainFrame; if (!pMainFrame->LoadFrame(IDR_MAINFRAME)) return FALSE; m_pMainWnd = pMainFrame; [例 16-1] 绘图程序。用户可以鼠标“拖曳”方式在视图中画直线段,线的粗细和颜色 可调。采用 MDI 结构,可同时打开多个子窗口作图,所作图形可以文件形式保存在磁盘上。 说 明:用 AppWizard 生成一个 MDI 程序框架,在第 4 步打开 Advanced Options(高 级选项)对话框,在 Document Template Strings(文档模板字符串)选项卡中将 File extension (文件扩展名)设置为“pic”,即该程序的图形文件名后缀为“.pic”。其他选项均使用缺省 设置。 编辑该程序的主菜单,添加一个“颜色”选项和一个“宽度菜单”,其中包括 4 个选项, 其 ID 和 Caption 分别设置为: ID Caption 命令消息响应函数 ID_COLOR “颜色” OnColor() ID_WIDTH1 “宽度=1” OnWidth1() ID_WIDTH3 “宽度=3” OnWidth3() ID_WIDTH5 “宽度=5” OnWidth5()
第16单元多文档界面程序 ID WIDTH7 “宽度=7” On Width () 用 Class wizard在视图类中为上述菜单选项建立相应的消息响应函数,以及几个宽度菜 单选项相应的更新用户界面消息函数。 程序:用 Class wizard添加一个用于描述线段的类,并为其添加代码 class Cline public Cob ject public m point From;//线段起点 CPoint //线段终点 COlORREFm colorline /线段颜色 t m nWidth: //线段宽度 CLines CLine(point from, point to, COLORREF color, int width) CLine& operator=(CLine& line) void Serialize(Carchive& ar d DrawLine( CDC *pDC) virtual CLineo1 DECLARE SERIAL (CLine) IMPLEMENT SERIAL (CLine, CObject, 1) CLine: CLine(point from, point to, COLORREF color, int width) m pointFrom from m colorline colo m nWidth CLine& CLine: operator =(CLine& line) m pointFrom line m pointFrom m colorline line m colorline nWidth= lin Width return *this void CLine: Serialize(Carchive &ar) f(ar. IsS 0)
第 16 单元 多文档界面程序 329 ID_WIDTH7 “宽度=7” OnWidth7() 用 Class Wizard 在视图类中为上述菜单选项建立相应的消息响应函数,以及几个宽度菜 单选项相应的更新用户界面消息函数。 程 序:用 Class Wizard 添加一个用于描述线段的类,并为其添加代码: class CLine : public CObject { public: CPoint m_pointFrom; // 线段起点 CPoint m_pointTo; // 线段终点 COLORREFm_colorLine; // 线段颜色 int m_nWidth; // 线段宽度 CLine(){} CLine(POINT from, POINT to, COLORREF color, int width); CLine& operator=(CLine& line); void Serialize(CArchive& ar); void DrawLine(CDC *pDC); virtual ~CLine(){} DECLARE_SERIAL(CLine); }; IMPLEMENT_SERIAL(CLine, CObject, 1) CLine::CLine(POINT from, POINT to, COLORREF color, int width) { m_pointFrom = from; m_pointTo = to; m_colorLine = color; m_nWidth = width; } CLine& CLine::operator =(CLine& line) { m_pointFrom = line.m_pointFrom; m_pointTo = line.m_pointTo; m_colorLine = line.m_colorLine; m_nWidth= line.m_nWidth; return *this; } void CLine::Serialize(CArchive &ar) { if(ar.IsStoring())
第16单元多文档界面程序 ar < m pointFrom < m pointTo < m colorLine < m nWidth ar >>m point From >>m pointTo >>m colorline >>m nWidth; void CLine: DrawLine(CDC *pDC) penNew. CreatePen(PS SOLID, m nWidth, m colorLine ppenOld pDC->SelectObject( &penNew pDC->Move To(m point From) pDC->Line To(m pointTo) pDC->SelectObject(ppenOld) 修改文档类头文件。在文件首部添加如下代码: #define MAX Lines 300 并在文档类定义中添加如下数据成员 CLine m lineList [MAX LINES] int 然后利用 Class wizard为文档类重载成员函数 Delete Contents。修改该函数及文档类的 Serialize()函数: void CMy Doc:: DeleteContentsO CDocument: DeleteContentso void CMy Doc:: Serialize(CArchive& ar) f(ar. IsStoring o) ar < m n Count for (int i=0; i<m n Count; i++) m lineList[i]. Serialize(ar)
第 16 单元 多文档界面程序 330 ar << m_pointFrom << m_pointTo << m_colorLine << m_nWidth; else ar >> m_pointFrom >> m_pointTo >> m_colorLine >> m_nWidth; } void CLine::DrawLine(CDC *pDC) { CPen penNew, *ppenOld; penNew.CreatePen(PS_SOLID, m_nWidth, m_colorLine); ppenOld = pDC->SelectObject(&penNew); pDC->MoveTo(m_pointFrom); pDC->LineTo(m_pointTo); pDC->SelectObject(ppenOld); } 修改文档类头文件。在文件首部添加如下代码: #include "line.h" #define MAX_LINES 300 并在文档类定义中添加如下数据成员: public: CLine m_lineList[MAX_LINES]; int m_nCount; 然后利用 Class Wizard 为文档类重载成员函数 DeleteContents()。修改该函数及文档类的 Serialize()函数: void CMyDoc::DeleteContents() { m_nCount = 0; CDocument::DeleteContents(); } void CMyDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) ar << m_nCount; else ar >> m_nCount; for(int i=0; i<m_nCount; i++) m_lineList[i].Serialize(ar); }