第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 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; } 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; for(int i=0; i<m_nCount; i++) m_lineList[i].Serialize(ar); }
第16单元多文档界面程序 331 修改视图类头文件,在视图类定义中添加如下数据成员 public COLORREF m color Curi;∥当前颜色 m n Curr Width;∥当前线宽 ∥是否按下鼠标左键 CPoint m pointfrom ∥当前线始端 CPoint m point To ∥当前线终端 并在视图类构造函数中加入相应的初始化代码: CMy View CMy View): m point From(,0), m point To(0,0) m color Curr =RGB(255, 0, 0) m n Width=3 然后修改视图类的 On Draw()函数和消息响应函数 void CMyView: OnDraw (CDC* pDC) CMy Doc* pDoc GetDocumento ASSERT VALID (pDoc) for(int i=0; im nCount: i++) pDoc->m lineList[i]. DrawLine( pDC) void CMyView: OnLBut tonDown(UINT nFlags, CPoint point) CView:: OnLBut ton Down(n Flags, point) m pointFrom m pointTo point SetCapture o void CMyView:: OnLBut tonUp (UINT nFlags, CPoint point) CView: OnLBut tonUp(nFlags, point) if(m cApture CMyDoc* pDoc = GetDocumentO ASSERT VALID(pDoc) m pointto= point
第 16 单元 多文档界面程序 331 修改视图类头文件,在视图类定义中添加如下数据成员: public: COLORREF m_colorCurr; // 当前颜色 int m_nCurrWidth; // 当前线宽 BOOL m_bCaptured; // 是否按下鼠标左键 CPoint m_pointFrom; // 当前线始端 CPoint m_pointTo; // 当前线终端 并在视图类构造函数中加入相应的初始化代码: CMyView::CMyView(): m_pointFrom(0,0), m_pointTo(0,0) { m_bCaptured = FALSE; m_colorCurr = RGB(255, 0, 0); m_nCurrWidth = 3; } 然后修改视图类的 OnDraw()函数和消息响应函数: void CMyView::OnDraw(CDC* pDC) { CMyDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); for(int i=0; im_nCount; i++) pDoc->m_lineList[i].DrawLine(pDC); } void CMyView::OnLButtonDown(UINT nFlags, CPoint point) { CView::OnLButtonDown(nFlags, point); m_pointFrom = m_pointTo = point; SetCapture(); m_bCaptured = TRUE; } void CMyView::OnLButtonUp(UINT nFlags, CPoint point) { CView::OnLButtonUp(nFlags, point); if(m_bCaptured) { CMyDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); m_pointTo = point;
第16单元多文档界面程序 332 m cAptured False: ReleaseCapture o Doc->m lineList lpDoc->m nCount]= CLine(m pointFrom, m pointTo m_colorCurr, m nCurrWidth) Doc->UpdateAllViews(this) Invalidate o void CMyView: OnMouse Move UInT nFlags, CPoint point CView:: OnMouseMove(n Flags, point) lientDC dc(this dc. SetROP2(R2 NOT ne dc. Move To(m dc LineTo(m pointTo void CMyView:: OnColorO CColorDialog dlg(m colorCurr) (dlg DoModal== IDOK m colorCurr dlg. GetColoro void CMyView: OnWidth1o m n CurrWidth h30 n CurrWidth =3:
第 16 单元 多文档界面程序 332 m_bCaptured = FALSE; ReleaseCapture(); pDoc->m_lineList[pDoc->m_nCount] = CLine(m_pointFrom,m_pointTo, m_colorCurr, m_nCurrWidth); pDoc->m_nCount++; pDoc->SetModifiedFlag(); pDoc->UpdateAllViews(this); Invalidate(); } } void CMyView::OnMouseMove(UINT nFlags, CPoint point) { CView::OnMouseMove(nFlags, point); if(m_bCaptured) { CClientDC dc(this); dc.SetROP2(R2_NOT); dc.MoveTo(m_pointFrom); dc.LineTo(m_pointTo); m_pointTo = point; dc.MoveTo(m_pointFrom); dc.LineTo(m_pointTo); } } void CMyView::OnColor() { CColorDialog dlg(m_colorCurr); if(dlg.DoModal() == IDOK) m_colorCurr = dlg.GetColor(); } void CMyView::OnWidth1() { m_nCurrWidth = 1; } void CMyView::OnWidth3() { m_nCurrWidth = 3; }
第16单元多文档界面程序 void CMyView:: OnWidth5O rrWidth = 5: void CMyView: OnWidth7O m n CurrWidth =7 void CMyView: OnUpdateWidthl(CCmdUI= pCmdUI) pCmdUI-Set Check(m nCurrWidth = 1) void CMyView: OnUpdateWidth3(CCmdUI=k pCmdUI) mdUI->SetCheck(m nCurrWidth = 3) void CMyView: OnUpdateWidth5(CCmdUI* pCmdUI) pCmdUI->SetCheck(m nCurrWidth = 5) void CMyView: OnUpdateWidth7(CCmdUI=* pCmdUI) CmdUI->SetCheck(m nCurrWidth = 7) 输入输出:在窗口客户区按下鼠标 左键后移动鼠标(“拖曳”)可显示一条 宽=1 始端不变、终端移动的黑色细线段,放 Hiol xI 松鼠标按键后,该线变为预先确定的颜 色和粗细。使用菜单选项可改变线段的 颜色和粗细(图16-1)。 分析:为了存储所绘图形,自 定义的线段类 CLine应可序列化。为此 CLine类包含了一个没有参数的构造函 数、一个重载的赋值运算符、 Serialize 图16-1绘图程序 ()成员函数,以及 DECLARE SERIAL()宏和 IMPLEMENT SERIAL宏 在文档类的 Serialize()成员函数中调用了 CLine类的 Serialize()函数 所有的鼠标消息和菜单消息响应函数均在视图类中。其中用到了CDC类的成员函数 StrOp2()在移动鼠标期间将绘图模式设置为异或,该模式下有两个特点,一是第2次画
第 16 单元 多文档界面程序 333 void CMyView::OnWidth5() { m_nCurrWidth = 5; } void CMyView::OnWidth7() { m_nCurrWidth = 7; } void CMyView::OnUpdateWidth1(CCmdUI* pCmdUI) { pCmdUI->SetCheck(m_nCurrWidth == 1); } void CMyView::OnUpdateWidth3(CCmdUI* pCmdUI) { pCmdUI->SetCheck(m_nCurrWidth == 3); } void CMyView::OnUpdateWidth5(CCmdUI* pCmdUI) { pCmdUI->SetCheck(m_nCurrWidth == 5); } void CMyView::OnUpdateWidth7(CCmdUI* pCmdUI) { pCmdUI->SetCheck(m_nCurrWidth == 7); } 输入输出:在窗口客户区按下鼠标 左键后移动鼠标(“拖曳”)可显示一条 始端不变、终端移动的黑色细线段,放 松鼠标按键后,该线变为预先确定的颜 色和粗细。使用菜单选项可改变线段的 颜色和粗细(图 16-1)。 分 析:为了存储所绘图形,自 定义的线段类 CLine 应可序列化。为此, CLine 类包含了一个没有参数的构造函 数、一个重载的赋值运算符、Serialize ()成员函数,以及 DECLARE_SERIAL()宏和 IMPLEMENT_SERIAL 宏。 在文档类的 Serialize()成员函数中调用了 CLine 类的 Serialize()函数。 所有的鼠标消息和菜单消息响应函数均在视图类中。其中用到了 CDC 类的成员函数 SetROP2()在移动鼠标期间将绘图模式设置为异或,该模式下有两个特点,一是第 2 次画 图 16-1 绘图程序
第16单元多文档界面程序 同一条线可恢复背景色(擦除),一是无论背景色和画线色的设置如何,均可保证所画线段 可见,因此特别适合绘制变化图形 但是,由于用户的鼠标可以在屏幕上任意移动。当鼠标移出窗口外时,窗口无法收到鼠 标消息。此时,如果松开了鼠标左键,应用程序由于无法接受到该条消息而不会终止当前笔 划,这样就造成了错误。如何避免这种情况发生呢?解决的办法是要让窗口在鼠标移出窗口 外时仍然能接受到鼠标消息。幸好, Windows提供了一个API函数 Set Capture()解决了这 一问题。 OnLButton Down()中调用 SetCapture()用于捕获鼠标,无论鼠标光标位置在何处, 都会将鼠标消息送给调用它的那一个窗口。在用完后,需要用 Release Capture()释放窗口 对鼠标的控制,否则其他窗口将无法接收到鼠标消息(在 OnLButtonUp()函数中)。这样, 即使用户在“拖曳”鼠标时越出当前窗口的客户区,也不会发生错误。 OnLButtonUp()函数中的语句 Update All Views( this 用于通知所有的视图数据已更新,这是MD的特别用法 OnUpdate Widthl()等函数用来在相应菜单选项前加上被选择标记 自学内容 162滚动视图 到目前为止,我们接触到的程序的窗口客户区均受限于计算机屏幕的大小,而这对某些 应用来说是不方便的。例如,若要开发“所见即所得”的文本编辑或图形编辑程序,就希望 屏幕上出现的文字或图形与打印机的输出大小接近。在 Windows程序中,这可以通过“滚 动视图”技术实现,即在窗口客户区的右方和下方分别添加一个垂直滚动条和一个水平滚动 条,使客户区变为一个更大的虚拟客户区的观察窗口。 MFC提供了 SCroll view类来实现滚动视图。 CScrollview类自动处理滚动条消息并“滚 动”客户区画面。因此利用 CScrollview类显示文档时,可以不必理会客户区的实际大小 只要将其当作一张很大的输出平面即可。 SCroll view类的 OnPrepareDC()成员函数会根 据水平和垂直滚动条的位置自动设定DC原点。因此也可以说,客户区的左上角坐标其实是 水平、垂直滚动条的位置,而两个滚动条的活动范围( scroll size)就是虚拟客户区的大小 CScrollview类有以下重要成员函数实现上述功能: 1.设置虚拟客户区的大小 void CScrollView: Set ScrollSizes( int n MapMode, SIZE size Total const SIZE& sizePage = size Default, const SIZE& sizeline =size Default 其中参数 n Map Mode为映射模式,可参看10.3:“GDl坐标系”。参数 size Total是整个虚拟 客户区的大小; size Page是每次“翻页”时的滚动量,也就是用户按下滚动条把柄时的滚动
第 16 单元 多文档界面程序 334 同一条线可恢复背景色(擦除),一是无论背景色和画线色的设置如何,均可保证所画线段 可见,因此特别适合绘制变化图形。 但是,由于用户的鼠标可以在屏幕上任意移动。当鼠标移出窗口外时,窗口无法收到鼠 标消息。此时,如果松开了鼠标左键,应用程序由于无法接受到该条消息而不会终止当前笔 划,这样就造成了错误。如何避免这种情况发生呢?解决的办法是要让窗口在鼠标移出窗口 外时仍然能接受到鼠标消息。幸好,Windows 提供了一个 API 函数 SetCapture()解决了这 一问题。 OnLButtonDown()中调用 SetCapture()用于捕获鼠标,无论鼠标光标位置在何处, 都会将鼠标消息送给调用它的那一个窗口。在用完后,需要用 ReleaseCapture()释放窗口 对鼠标的控制,否则其他窗口将无法接收到鼠标消息(在 OnLButtonUp()函数中)。这样, 即使用户在“拖曳”鼠标时越出当前窗口的客户区,也不会发生错误。 OnLButtonUp()函数中的语句 UpdateAllViews(this); 用于通知所有的视图数据已更新,这是 MDI 的特别用法。 OnUpdateWidth1()等函数用来在相应菜单选项前加上被选择标记。 自学内容 16.2 滚动视图 到目前为止,我们接触到的程序的窗口客户区均受限于计算机屏幕的大小,而这对某些 应用来说是不方便的。例如,若要开发“所见即所得”的文本编辑或图形编辑程序,就希望 屏幕上出现的文字或图形与打印机的输出大小接近。在 Windows 程序中,这可以通过“滚 动视图”技术实现,即在窗口客户区的右方和下方分别添加一个垂直滚动条和一个水平滚动 条,使客户区变为一个更大的虚拟客户区的观察窗口。 MFC 提供了 CScrollView 类来实现滚动视图。CScrollView 类自动处理滚动条消息并“滚 动”客户区画面。因此利用 CScrollView 类显示文档时,可以不必理会客户区的实际大小, 只要将其当作一张很大的输出平面即可。CScrollView 类的 OnPrepareDC()成员函数会根 据水平和垂直滚动条的位置自动设定 DC 原点。因此也可以说,客户区的左上角坐标其实是 水平、垂直滚动条的位置,而两个滚动条的活动范围(scroll size)就是虚拟客户区的大小。 CScrollView 类有以下重要成员函数实现上述功能: 1.设置虚拟客户区的大小: void CScrollView::SetScrollSizes ( int nMapMode, SIZE sizeTotal, const SIZE& sizePage = sizeDefault, const SIZE& sizeLine = sizeDefault ); 其中参数 nMapMode 为映射模式,可参看 10.3:“GDI 坐标系”。参数 sizeTotal 是整个虚拟 客户区的大小;sizePage 是每次“翻页”时的滚动量,也就是用户按下滚动条把柄时的滚动
第16单元多文档界面程序 335 量。 sideline是每次跳一小格的滚动量,以上参数的单位均为逻辑坐标的单位。 2.取滚动条坐标(客户区左上角坐标)和虚拟客户区大小: CPoint CScroll View: GetScrollPosition () const CSize CScroll View Get TotalSize ( const 3.将客户区左上角滚动到指定坐标 void CScrollView: ScrollToPosition(POINT pt 其中参数pt为指定的客户区坐标。 4.取滚动位置和虚拟客户区大小的物理坐标 CPoint CScroll View: Get DeviceScrollPosition () const void CScroll View: GetDevice ScrollSizes( int& n Map Mode SIZE& size Total SIZE& sizePage SIZE& size Line ) const 要使应用程序支持卷滚,可在用 App Wizard生成框架程序时就指定视图的基类为 CSrollview。做法是在 AppWizard的 MFC AppWizard-Step6of6对话框中,在应用程序所包 含的类中选择视图类,然后在 Base class下拉列表框中选择应用程序视图类的基类为 如果要手工修改视图类的基类为 CScrollview,可按以下步骤操作: 1.修改视图类所对应的头文件,将所有用到Cvew的地方改为 SCroll o可以使 用文本替换对话框(在编辑菜单中)中的替换功能,进行全局替换。 2.确定虚拟客户区的大小。这项工作通常在视图派生类的 OnInitial Update()成员函 数或 On Create()成员函数中通过调用 Setscrollsizes()成员函数来完成。 3.如果在视图类的消息响应函数(如鼠标消息函数)中使用了 CClientDC设备,则要注意 该设备仍以实际客户区的左上角为原点,在存储有关数据时,可能要将其转换为虚拟客户区 坐标。转换方法很简单,只要将坐标值加上客户区左上角在虚拟客户区的坐标值即可。然而, OnDraw()函数无需任何修改,因其使用的设备由 CScrollview自动维护 163对话视图 对于人事档案管理、名片管理、图书管理这类应用程序,视图的主要作用是显示各项文 档资料,同时又要提供方便的修改手段。如例15-1,采用对话框编辑档案材料比较方便 然而,基于对话框的应用程序不提供文档类,数据存取不方便 MFC提供了 CForm view类,该类成员兼有对话框和视图的特点,最适合作文档管理类 应用程序的用户界面 要使应用程序支持CFomⅤiew类,可在用 App Wizard生成框架程序时就指定视图的基 类为 CForm view。做法是在 AppWizard的 MFC App Wizard-Step6of6对话框中,在应用程 序所包含的类中选择视图类,然后在 Base Class下拉列表框中选择应用程序视图类的基类为 CForm view a
第 16 单元 多文档界面程序 335 量。sizeLine 是每次跳一小格的滚动量,以上参数的单位均为逻辑坐标的单位。 2.取滚动条坐标(客户区左上角坐标)和虚拟客户区大小: CPoint CScrollView::GetScrollPosition() const; CSize CScrollView::GetTotalSize() const; 3.将客户区左上角滚动到指定坐标 void CScrollView::ScrollToPosition(POINT pt); 其中参数 pt 为指定的客户区坐标。 4.取滚动位置和虚拟客户区大小的物理坐标 CPoint CScrollView::GetDeviceScrollPosition()const; void CScrollView::GetDeviceScrollSizes(int& nMapMode, SIZE& sizeTotal, SIZE& sizePage, SIZE& sizeLine)const; 要使应用程序支持卷滚,可在用 AppWizard 生成框架程序时就指定视图的基类为 CSrollView。做法是在 AppWizard 的 MFC AppWizard-Step 6 of 6 对话框中,在应用程序所包 含的类中选择视图类,然后在 Base Class 下拉列表框中选择应用程序视图类的基类为 CScrollView。 如果要手工修改视图类的基类为 CScrollView,可按以下步骤操作: 1.修改视图类所对应的头文件,将所有用到 CView 的地方改为 CScrollView。可以使 用文本替换对话框(在编辑菜单中)中的替换功能,进行全局替换。 2.确定虚拟客户区的大小。这项工作通常在视图派生类的 OnInitialUpdate()成员函 数或 OnCreate()成员函数中通过调用 SetScrollSizes()成员函数来完成。 3.如果在视图类的消息响应函数(如鼠标消息函数)中使用了 CClientDC 设备,则要注意 该设备仍以实际客户区的左上角为原点,在存储有关数据时,可能要将其转换为虚拟客户区 坐标。转换方法很简单,只要将坐标值加上客户区左上角在虚拟客户区的坐标值即可。然而, OnDraw()函数无需任何修改,因其使用的设备由 CScrollView 自动维护。 16.3 对话视图 对于人事档案管理、名片管理、图书管理这类应用程序,视图的主要作用是显示各项文 档资料,同时又要提供方便的修改手段。如例 15-1,采用对话框编辑档案材料比较方便。 然而,基于对话框的应用程序不提供文档类,数据存取不方便。 MFC 提供了 CFormView 类,该类成员兼有对话框和视图的特点,最适合作文档管理类 应用程序的用户界面。 要使应用程序支持 CFormView 类,可在用 AppWizard 生成框架程序时就指定视图的基 类为 CFormView。做法是在 AppWizard 的 MFC AppWizard-Step 6 of 6 对话框中,在应用程 序所包含的类中选择视图类,然后在 Base Class 下拉列表框中选择应用程序视图类的基类为 CFormView