Presentation is loading. Please wait.

Presentation is loading. Please wait.

第6 章框架窗口、文档和视图 尽管窗口、文档和视图是MFC的基础,但可能也是最不易理解的部分,因为其概

Similar presentations


Presentation on theme: "第6 章框架窗口、文档和视图 尽管窗口、文档和视图是MFC的基础,但可能也是最不易理解的部分,因为其概"— Presentation transcript:

1 第6 章框架窗口、文档和视图 尽管窗口、文档和视图是MFC的基础,但可能也是最不易理解的部分,因为其概
第6 章框架窗口、文档和视图 尽管窗口、文档和视图是MFC的基础,但可能也是最不易理解的部分,因为其概 念性比传统编程所需的Windows API函数更强一些 6.1 框架窗口 框架窗口可分为两类:一类是应用程序主窗口,另一类是文档窗口。 6.1.1 主窗口和文档窗口 主框架窗口是应用程序直接放置在桌面(DeskTop)上的那个窗口,每个应用程序 只能有一个主框架窗口,主框架窗口的标题栏上往往显示应用程序的名称。

2 6.1.1 主窗口和文档窗口 文档窗口对于单文档应用程序来说,它和主框架窗口是一致的,即主框架窗口就
是文档窗口;而对于多文档应用程序,文档窗口是主框架窗口的子窗口,如图 6.1所示。 主框架窗口 文档窗口

3 6.1.2 窗口风格的设置 窗口风格既可以通过MFC AppWizard来设置,也可以在主窗口或文档窗口类的
PreCreateWindow函数中修改CREATESTRUCT结构,或是可以调用CWnd类的 成员函数ModifyStyle和ModifyStyleEx来更改。 1. 窗口风格 窗口风格通常有一般(以WS_为前缀)和扩展(以WS_EX_为前缀)两种形式。这两 种形式的窗口风格可在函数CWnd::Create或CWnd::CreateEx参数中指定,其中 CreateEx函数可同时支持以上两种风格,而CWnd::Create只能指定窗口的一般 风格。需要说明的是,对于控件和对话框这样的窗口来说,它们的窗口风格可直 接通过其属性对话框来设置。常见的一般窗口风格如表6.1所示。

4 1. 窗口风格 表6.1 窗口的一般风格

5 6.1.2 窗口风格的设置 2. 用MFC AppWizard设置
步中),允许用户指定有关SDI和MDI框架窗口的属性,图6.2表示了Advanced Options对话框的Window Styles页面,其中的选项含义见表6.2。但在该对话框 中,用户只能设定少数几种窗口风格。 图6.2 高级选项对话框 表6.2 高级选项对话框窗口风格的各项含义

6 6.1.2 窗口风格的设置 3. 修改CREATESTRUCT结构
当窗口创建之前,系统自动调用PreCreateWindow虚函数。在用MFC AppWizard创建文档 应用程序结构时,MFC已为主窗口或文档窗口类自动重载了该虚函数。用户可以在此函数中 通过修改CREATESTRUCT结构来设置窗口的绝大多数风格。 例如,在单文档应用程序中,框架窗口默认的风格是WS_OVERLAPPEDWINDOW和FWS_ ADDTOTITLE的组合,更改其风格可如下列的代码: BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { // 新窗口不带有[最大化]按钮 cs.style &= ~WS_MAXIMIZEBOX; // 将窗口的大小设为1/3屏幕并居中 cs.cy = ::GetSystemMetrics(SM_CYSCREEN) / 3; cs.cx = ::GetSystemMetrics(SM_CXSCREEN) / 3; cs.y = ((cs.cy * 3) - cs.cy) / 2; cs.x = ((cs.cx * 3) - cs.cx) / 2; return CFrameWnd::PreCreateWindow(cs); } 代码中,前面有“::”作用域符号的函数是指全局函数,一般都是一些API函数。“cs.style &= ~WS_MAXIMIZEBOX;”中的“~”是按位取“反”运算符,它将WS_MAXIMIZEBOX的值按位取 反后,再和cs.style值按位“与”,其结果是将cs.style值中的WS_MAXIMIZEBOX标志位清零。

7 6.1.2 窗口风格的设置 4. 使用ModifyStyle和ModifyStyleEx
CWnd类中的成员函数ModifyStyle和ModifyStyleEx也可用来更改窗口的风格,其中Modify StyleEx还可更改窗口的扩展风格。这两个函数具有相同的参数,其含义如下。 BOOL ModifyXXXX( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 ); 其中,参数dwRemove用来指定需要删除的风格,dwAdd用来指定需要增加的风格,nFlags 表示SetWindowPos的标志,0(默认)表示更改风格的同时不调用SetWindowPos函数。 由于框架窗口在创建时不能直接设定其扩展风格,因此只能通过调用ModifyStyle函数来进 行。例如用MFC ClassWizard为一个多文档应用程序Ex_MDI的子文档窗口类CChildFrame添 加OnCreateClient消息处理,并增加下列代码: BOOL CChildFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { ModifyStyle(0, WS_VSCROLL, 0); return CMDIChildWnd::OnCreateClient(lpcs, pContext); } 这样,当窗口创建客户区时就会调用虚函数OnCreateClient。运行结果如图6.3所示。

8 6.1.2 窗口风格的设置 添加的滚动条 图6.3 为文档子窗口添加垂直滚动条

9 6.1.3 窗口状态的改变 1. 用ShowWindow改变窗口的显示状态
当应用程序运行时,Windows会自动调用应用程序框架内部的WinMain函数,并 自动查找该应用程序类的全局变量theApp,然后自动调用用户应用程序类的虚函 数InitInstance,该函数会进一步调用相应的函数来完成主窗口的构造和显示工 作,如下面的代码(以单文档应用程序项目Ex_SDI为例): BOOL CEx_SDIApp::InitInstance() { m_pMainWnd->ShowWindow(SW_SHOW); // 显示窗口 m_pMainWnd->UpdateWindow(); // 更新窗口 return TRUE; } 代码中,m_pMainWnd是主框架窗口指针变量,ShowWindow是CWnd类的成员 函数,用来按指定的参数显示窗口,该参数的值如表6.3所示。

10 1. 用ShowWindow改变窗口的显示状态

11 6.1.3 窗口状态的改变 通过指定ShowWindow函数的参数值可以改变改变窗口显示状态。例如下面的代
码是将窗口的初始状态设置为“最小化”: BOOL CEx_SDIApp::InitInstance() { ... m_pMainWnd->ShowWindow(SW_SHOWMINIMIZED); m_pMainWnd->UpdateWindow(); return TRUE; }

12 6.1.3 窗口状态的改变 2. 用SetWindowPos或MoveWindow改变窗口的大小和位置
CWnd中的SetWindowPos是一个非常有用的函数;它不仅可以改变窗口的大 小、位置,而且还可以改变所有窗口在堆栈排列的次序(Z次序),这个次序是根 据它们在屏幕出现的先后来确定的。 其中,参数pWndInsertAfter表示窗口对象指针,它可以下列预定义窗口对象的 地址: wndBottom 将窗口放置在Z次序中的底层 wndTop 将窗口放置在Z次序中的顶层 wndTopMost 设置最顶窗口 wndNoTopMost 将窗口放置在所有最顶层的后面,若此窗口不是最顶 窗口,则此标志无效

13 2. 用SetWindowPos或MoveWindow改变窗口的大小和位置
x和y表示窗口新的左上角坐标,cx和cy分别表示表示窗口新的宽度和高度, nFlags表示窗口新的大小和位置方式,如表6.4所示。 表6.4 常用nFlags值及其含义

14 2. 用SetWindowPos或MoveWindow改变窗口的大小和位置
void MoveWindow( int x, int y, int nWidth, int nHeight, BOOL bRepaint = TRUE ); void MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE ); 其中,参数x和y表示窗口新的左上角坐标,nWidth和nHeight表示窗口新的宽度和高度, bRepaint用于指定窗口是否重绘,lpRect表示窗口新的大小和位置。 // 使用SetWindowPos函数的示例 m_pMainWnd->SetWindowPos(NULL,100,100,0,0,SWP_NOSIZE|SWP_NOZORDER); // 使用MoveWindow函数的示例 CRect rcWindow; m_pMainWnd->GetWindowRect(rcWindow); m_pMainWnd->MoveWindow(100,100,rcWindow.Width(),rcWindow.Height(),TRUE); 当然,改变窗口的大小和位置的CWnd成员函数还不止以上两个。例如CenterWindow函数 是使窗口居于父窗口中央,就像下面的代码: CenterWindow(CWnd::GetDesktopWindow()); // 将窗口置于屏幕中央 AfxGetMainWnd()->CenterWindow(); // 将主框架窗口居中

15 6.2 文档模板 用MFC AppWizard创建的单文档(SDI)或多文档(MDI)应用程序均包含应用程序
类、文档类、视图类和框架窗口类,这些类是通过文档模板来有机地联系在一 起。

16 6.2.1 文档模板类 文档应用程序框架结构是在程序运行一开始构造的,在单文档应用程序(设项目名为
Ex_SDI)的应用程序类InitInstance函数中,可以看到这样的代码: BOOL CEx_SDIApp::InitInstance() { CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, // 资源ID RUNTIME_CLASS(CEx_SDIDoc), // 文档类 RUNTIME_CLASS(CMainFrame), // 主框架窗口类 RUNTIME_CLASS(CEx_SDIView)); // 视图类 AddDocTemplate(pDocTemplate); return TRUE; }

17 6.2.2 文档模板字串资源 表6.5 文档模板字符串的含义

18 6.2.2 文档模板字串资源 实际上,文档模板字串资源内容既可直接通过字串资源编辑器进行修改,也可
以在文档应用程序创建向导的第四步中,通过“Advanced Options”对话框中的 “Document Template Strings”页面来指定,如图6.4所示。 图6.4 Advanced Options对话框 5 3 6 2 1 4

19 6.2.3 使用多个文档类型 [例Ex_MDIDemo] 使用多个文档类型
(1) 用MFC AppWizard创建一个默认的多文档应用程序项目Ex_MDIDemo。 (2) 打开项目工作区窗口中String Table的资源项,双击该项下的String Table, 打开字符串表资源,如图6.5所示。 图6.5 Ex_MDIDemo字符串表资源

20 [例Ex_MDIDemo] (3) 双击IDR_MAINFRAME列表项,弹出字符串属性对话框,将其标题修改为“多个文
档类型实例”,结果如图6.6所示。 (4) 双击IDR_EX_MDITYPE列表项,在字符串属性对话框中,将其内容修改为: \nPicture\nMDIDemo图片\n图片文件(*.bmp)\n.bmp\nExMDIDemo.Document\nEx_MDI Document (5) 拖动字符串表编辑器右边的滚动块,直到出现最后一个字符串项,双击最后的空 行,在字符串属性对话框中将ID设为IDR_OTHERTYPE,标题内容设为: \nTxt\nMDIDemo文本\n文本文件(*.txt, *.cpp,*.h)\n.txt;*.cpp;*.h\nExMDIDemo.Document\n Ex_MDI Document 结果如图6.7所示。 图6.6 修改IDR_MAINFRAME字符串标题 图6.7 添加新的字符串项

21 [例Ex_MDIDemo] (6) 按快捷键Ctrl+W,打开MFC ClassWizard,单击[Add Class]按钮,从弹出的
菜单中选择New,出现“New Class”对话框,在Name框中输入类名COtherDoc, 在Base class组合框中选择基类CDocument,结果如图6.8所示。 图6.8 添加新的文档类COtherDoc (7) 单击[OK]按钮,新的文档类COtherDoc就添加到Ex_MDIDemo项目中。类似的,再添加一个新的视图类COtherView,基类为CView。单击[确定]按钮,关闭MFC ClassWizard对话框。

22 [例Ex_MDIDemo] (8) 修改CEx_MDIDemoApp::InitInstance函数代码,如下所示:
BOOL CEx_MDIDemoApp::InitInstance() { CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate( IDR_EX_MDITYPE, RUNTIME_CLASS(CEx_MDIDemoDoc), RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CEx_MDIDemoView)); AddDocTemplate(pDocTemplate); IDR_OTHERTYPE, // 指定新的资源 RUNTIME_CLASS(COtherDoc), // 指定新的文档类 RUNTIME_CLASS(COtherView)); // 指定新的视图类 return TRUE; }

23 [例Ex_MDIDemo] (9) 在类CEx_MDIDemoApp源代码文件Ex_MDIDemo.cpp的开始处,添加包含
前面创建的两个派生类的头文件: #include "Ex_MDIDemoView.h" #include "OtherDoc.h" #include "OtherView.h" (10) 编译运行并测试。在程序运行的一开始弹出文档类型的“新建”对话框,如图 6.9所示。选择“MDIDemo图片”,单击[确定]后,出现CEx_MDIDemo主框架窗口 界面,同时出现标题为 “Picture1”的文档窗口。选择“文件”→ “新建”菜单,又会 出现如图6.9所示的“新建”对话框,选择“MDIDemo文本”,单击[确定]后,出现标 题为 “Txt1”的文档窗口。结果如图6.10所示。选择“文件”→“打开”菜单,出现如图 6.11所示的文件打开对话框。

24 [例Ex_MDIDemo] 图6.9 文档类型新建对话框 图6.10 多类型文档窗口显示 图6.11 文件打开对话框中的文件类型

25 6.3 文档序列化 用户处理的数据往往需要存盘作永久备份。将文档类中的数据成员变量的值保存
在磁盘文件中,或者将存储的文档文件中的数据读取到相应的成员变量中。这个 过程称为序列化(Serialize)。

26 6.3.1 文档序列化过程 在使用MFC程序结构进行文件序列化操作之前,先来看看对文档不同操作后的具 体程序运行过程。 1. 创建空文档
应用程序类的InitInstance函数在调用了AddDocTemplate函数之后,会通过 CWinApp:: ProcessShellCommand间接调用CWinApp的另一个非常有用的成员 函数OnFileNew,并依次完成下列工作: (1) 构造文档对象,但并不从磁盘中读数据。 (2)构造主框架类CMainFrame的对象,并创建该主框架窗口,但不显示。 (3)构造视图对象,并创建视图窗口,也不显示。 (4)通过内部机制,使文档、主框架和视图“对象”之间“真正”建立联系。注意与 AddDocTemplate函数的区别,AddDocTemplate函数建立的是“类”之间的联系。 (5)调用文档对象的CDocument::OnNewDocument虚函数,并调用CDocument:: DeleteContents虚函数来清除文档对象的内容。 (6)调用视图对象的CView::OnInitialUpdate虚函数对视图进行初始化操作。 (7)调用框架对象的CFrameWnd::ActiveFrame虚函数,以便显示出带有菜单、工 具栏、状态栏以及视图窗口的主框架窗口。

27 6.3.1 文档序列化过程 2. 打开文档 当MFC AppWizard创建应用程序时,它会自动将“文件(File)”菜单中的“打开(Open)”命令(ID 号为ID_FILE_OPEN)映射到CWinApp的OnFileOpen成员函数。这一结果可以从应用类 (.cpp)的消息入口处得到验证: BEGIN_MESSAGE_MAP(CEx_SDIApp, CWinApp) …… ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew) ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen) // Standard print setup command ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup) END_MESSAGE_MAP() OnFileOpen函数还会进一步完成下列工作: 弹出通用文件“打开”对话框,供用户选择一个文档。 (1) 文档指定后,调用文档对象的CDocument:: OnOpenDocument虚函数。该函数将打开 文档,并调用DeleteContents清除文档对象的内容,然后创建一个CArchive对象用于数据 的读取,接着又自动调用Serialize函数。 (2) 调用视图对象的CView::OnInitialUpdate虚函数。 (3) 除了使用“文件(File)”→“打开(Open)”菜单项外,用户也可以选择最近使用过的文件列表 来打开相应的文档。在应用程序的运行过程中,系统会记录下4个(默认)最近使用过的文 件,并将文件名保存在Windows的注册表中。当每次启动应用程序时,应用程序都会最近 使用过的文件名称显示在“文件(File)”菜单中。

28 6.3.1 文档序列化过程 3. 保存文档 当MFC AppWizard创建应用程序时,它会自动将“文件(File)”菜单中的“保存(Save)” 命令与文档类CDocument的OnFileSave函数在内部关联起来,但用户在程序框架 中看不到相应的代码。OnFileSave函数还会进一步完成下列工作: (1) 弹出通用文件“保存”对话框,让用户提供一个文件名。 (2) 调用文档对象的CDocument::OnSaveDocument虚函数,接着又自动调用 Serialize函数,将CArchive对象的内容保存在文档中。

29 6.3.1 文档序列化过程 4. 关闭文档 当用户试图关闭文档(或退出应用程序)时,应用程序会根据用户对文档的修改与
否来进一步完成下列任务: (1) 若文档内容已被修改,则弹出一个消息对话框,询问用户是否需要将文档保 存。当用户选择“是”,则应用程序执行OnFileSave过程。 (2) 调用CDocument::OnCloseDocument虚函数,关闭所有与该文档相关联的文 档窗口及相应的视图,调用文档类CDocument的DeleteContents清除文档数据。

30 6.3.2 文档序列化操作 从上述的单文档序列化过程可以看出:打开和保存文档时,系统都会自动调用
Serialize函数。事实上,MFC AppWizard在创建文档应用程序框架时已在文档类 中重载了Serialize函数,通过在该函数中添加代码可达到实现数据序列化的目的。 例如,在Ex_SDI单文档应用程序的文档类中有这样的默认代码: void CEx_SDIDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) // 当文档数据需要存盘时 { // TODO: add storing code here } else // 当文档数据需要读取时 { // TODO: add loading code here 代码中,Serialize函数的参数ar是一个CArchive类引用变量。通过判断ar.IsStoring 的结果是“真”还是“假”就可决定向文档写或读数据。

31 表6.6 ar中可以使用<<和>>运算符的数据类型
6.3.2 文档序列化操作 CArchive(归档)类提供对文件数据进行缓存,它同时还保存一个内部标记,用来 标识文档是存入(写盘)还是载入(读盘)。每次只能有一个活动的存档与ar相连。 通过CArchive类可以简化文件操作,它提供“<<”和“>>”运算符,用于向文件写入 简单的数据类型以及从文件中读取它们,表6.6列出了CArchive所支持的的常用 数据类型。 表6.6 ar中可以使用<<和>>运算符的数据类型

32 6.3.2 文档序列化操作 除了“<<”和“>>”运算符外,CArchive类还提供成员函数ReadString和WriteString 用来从一个文件对象中读写一行文本,它们的原型如下: Bool ReadString(CString& rString ); LPTSTR ReadString( LPTSTR lpsz, UINT nMax ); void WriteString( LPCTSTR lpsz ); 其中,lpsz用来指定读或写的文本内容,nMax用来指定可以读出的最大字符个 数。需要说明的是,当向一个文件写一行字符串时,字符 ‘\0’和‘\n’都不会写到文 件中,在使用时要特别注意。 下面举一个简单的示例来说明Serialize函数和CArchive类的文档序列化操作方 法。

33 6.3.2 文档序列化操作 [例Ex_SDIArchive] 一个简单的文档序列化示例
(1) 用MFC AppWizard创建一个默认的单文档应用程序Ex_SDIArchive。 (2) 打开String Table资源,将文档模板字串资源IDR_MAINFRAME内容修改为: 文档序列化操作\n\n\n自定义文件(*.my)\n.my\nExSDIArchive.Document\nEx_SDI Document (3) 为CEx_SDIArchiveDoc类添加下列成员变量: public: char m_chArchive[100]; // 读写数据时使用 CString m_strArchive; // 读写数据时使用 BOOL m_bIsMyDoc; // 用于判断文档 (4) 在CEx_SDIArchiveDoc类构造函数中添加下列代码: CEx_SDIArchiveDoc::CEx_SDIArchiveDoc() { m_bIsMyDoc = FALSE; }

34 [例Ex_SDIArchive] (5) 在CEx_SDIArchiveDoc::OnNewDocument函数中添加下列代码:
BOOL CEx_SDIArchiveDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; strcpy(m_chArchive, "&这是一个用于测试文档的内容!"); m_strArchive = "这是一行文本!"; m_bIsMyDoc = TRUE; return TRUE; }

35 [例Ex_SDIArchive] (6) 在CEx_SDIArchiveDoc::Serialize函数中添加下列代码:
void CEx_SDIArchiveDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) if (m_bIsMyDoc) // 是自己的文档 for (int i=0; i<sizeof(m_chArchive); i++) ar<<m_chArchive[i]; ar.WriteString( m_strArchive ); } else AfxMessageBox("数据无法保存!"); }else ar>>m_chArchive[0]; // 读取文档首字符 if (m_chArchive[0] == '&') // 是自己的文档

36 for (int i=1; i<sizeof(m_chArchive); i++)
ar>>m_chArchive[i]; ar.ReadString( m_strArchive ); CString str; str.Format("%s%s",m_chArchive,m_strArchive); AfxMessageBox(str); m_bIsMyDoc = TRUE; }else // 不是自己的文档 { m_bIsMyDoc = FALSE; AfxMessageBox("打开的文档无效!"); }

37 [例Ex_SDIArchive] (7) 将文档模板字串资源IDR_MAINFRAME内容修改如下:
文档序列化操作\n\n\n自定义文件(*.my)\n.my\nExSDIArchive.Document\nEx_SDI Document (8) 编译运行并测试。程序运行后,选择“文件”→“另存为”菜单,指定一个文档名 1.my,然后选择“文件”→“新建”菜单,再打开该文档,结果就会弹出对话框,显 示该文档的内容,如图6.12所示。 图6.12 显示文档内容

38 6.3.3 使用简单数组集合类 简单数组集合类是一个大小动态可变的数组,数组中的元素可用下标运算符“[ ]”来访
问(从0开始),设置或获取元素数据。若要设置超过数组当前个数的元素的值,可以指 定是否使数组自动扩展。当数组不需扩展时,访问数组集合类的速度与访问标准C++ 中的数组的速度同样快。以下的基本操作对所有的简单数组集合类都适用。 1. 简单数组集合类的构造及元素的添加 对简单数组集合类构造的方法都是一样的,均是使用各自的构造函数,它们的原型如 下: CByteArray CByteArray( ); CDWordArray CDWordArray( ); CObArray CObArray( ); CPtrArray CPtrArray( ); CStringArray CStringArray( ); CUIntArray CUIntArray( ); CWordArray CWordArray( ); 下面的代码说明了简单数组集合类的两种构造方法: CObArray array; // 使用默认的内存块大小 CObArray* pArray = new CObArray; // 使用堆内存中的默认的内存块大小

39 6.3.3 使用简单数组集合类 1. 简单数组集合类的构造及元素的添加 它们的原型如下: void SetSize( int nNewSize, int nGrowBy = -1 ); int GetSize( ) const; 向简单数组集合类添加一个元素,可使用成员函数Add和Append,原型如下: int Add( CObject* newElement ); int Append( const CObArray& src ); 其中,Add函数是向数组的末尾添加一个新元素,且数组自动增1。如果调用的 函数SetSize的参数nGrowBy 的值大于1,那么扩展内存将被分配。此函数返回 被添加的元素序号,元素序号就是数组下标。参数newElement表示要添加的相 应类型的数据元素。而Append函数是向数组的末尾添加由src指定的另一个数组 的内容。函数返回加入的第一个元素的序号。

40 6.3.3 使用简单数组集合类 2. 访问简单数组集合类的元素
在MFC中,一个简单数组集合类元素的访问既可以使用GetAt函数,也可使用“[]” 操作符,例如: // CObArray::operator []示例 CObArray array; CAge* pa; // CAge是一个用户类 array.Add( new CAge( 21 ) ); // 添加一个元素 array.Add( new CAge( 40 ) ); // 再添加一个元素 pa = (CAge*)array[0]; // 获取元素0 array[0] = new CAge( 30 ); // 替换元素0; // CObArray::GetAt示例 array.Add( new CAge( 21 ) ); // 元素 0 array.Add( new CAge( 40 ) ); // 元素 1

41 6.3.3 使用简单数组集合类 3. 删除简单数组集合类的元素 删除简单数组集合类中的元素一般需要进行以下几个步骤:
(1) 使用函数GetSize和整数下标值访问简单数组集合类中的元素。 (2) 若对象元素是在堆内存中创建的,则使用delete操作符删除每一个对象元素。 (3) 调用函数RemoveAll删除简单数组集合类中的所有元素。 例如,下面代码是一个CObArray的删除示例: CObArray array; CAge* pa1; CAge* pa2; array.Add( pa1 = new CAge( 21 ) ); array.Add( pa2 = new CAge( 40 ) ); ASSERT( array.GetSize() == 2 ); for (int i=0;i<array.GetSize();i++) delete array.GetAt(i); array.RemoveAll();

42 6.3.4 文档序列化示例 这是一个综合示例,如图6.13所示。它首先通过对话框来输入一个学生记录,记
录包括学生的姓名、学号和三门成绩。然后将记录内容保存到一个对象数组集合 类对象中,最后通过文档序列化将记录保存到一个文件中。当添加记录或打开一 个记录文件,还会将数据显示在文档窗口(即视图)中。 图6.13 Ex_Student运行结果

43 6.3.4 文档序列化示例 [例Ex_Student] 文档序列化示例 1) 添加用于学生记录输入的对话框
(1) 用MFC AppWizard创建一个默认的单文档应用程序Ex_Student。 (2) 向应用程序中添加一个对话框资源,打开属性对话框将其字体设置为“宋体, 9”,标题改为“添加学生记录”,取默认的ID号IDD_DIALOG1,将OK和Cancel按 钮的标题分别改为“确 定”和“取 消”。 (3) 参看图6.13的控件布局,用编辑器为对话框添加如下表6.7所示的一些控件。 表6.7 添加的控件

44 [例Ex_Student] (4) 双击对话框模板或按Ctrl+W快捷键,为对话框资源IDD_DIALOG1创建一个对
话框类CAddDlg。 (5) 打开ClassWizard的Member Variables标签,在Class name中选择CAddDlg, 选中所需的控件ID标识符,双击鼠标或单击Add Variables按钮。依次为表6.8中的 控件增加成员变量。 表6.8 控件变量

45 [例Ex_Student] 2) 添加一个CStudent类并使该类可序列化
一个可序列化的类必须是CObject的一个派生类,且在类声明中,需要包含 DECLARE_SERIAL宏调用,而在类的实现文件中包含IMPLEMENT_SERIAL宏 调用,这个宏有3个参数:前2个参数分别表示类名和基类名,第3个参数表示应 用程序的版本号。最后还需要重载Serialize函数,使该类的数据成员进行相关序 列化操作。 由于使用ClassWizard无法添加一个CObject派生类,因此必须手动进行。为了 简化类文件的复杂性,我们创建的这个CStudent类的声明和实现代码是直接添加 在Ex_StudentDoc.h和Ex_StudentDoc.cpp文件中的,具体如下: // 在Ex_StudentDoc.h文件中的class CEx_StudentDoc前添加的 class CStudent : public CObject { CString strName; // 姓名 CString strID; // 学号 float fScore1, fScore2, fScore3; // 三门成绩 float fAverage; // 平均成绩 DECLARE_SERIAL(CStudent)

46 public: CStudent() {}; CStudent(CString name, CString id, float f1, float f2, float f3); void Serialize(CArchive &ar); void Display(int y, CDC *pDC); // 在坐标为(0,y)处显示数据 }; // 在Ex_StudentDoc.cpp文件中添加的CStudent实现代码 CStudent::CStudent(CString name, CString id, float f1, float f2, float f3) { strName = name; strID = id; fScore1 = f1; fScore2 = f2; fScore3 = f3; fAverage = (float)((f1 + f2 + f3)/3.0); } void CStudent::Display(int y, CDC *pDC)

47 { CString str; str.Format("%s %s %f %f %f %f", strName, strID, fScore1, fScore2, fScore3, fAverage); pDC->TextOut(0, y, str); } IMPLEMENT_SERIAL(CStudent, CObject, 1) void CStudent::Serialize(CArchive &ar) if (ar.IsStoring()) ar<<strName<<strID<<fScore1<<fScore2<<fScore3<<fAverage; else ar>>strName>>strID>>fScore1>>fScore2>>fScore3>>fAverage;

48 [例Ex_Student] 3) 添加并处理菜单项
“添加(&A)”(ID_STUREC_ADD)。 (2) 用ClassWizard为CEx_StudentDoc类添加ID_STUREC_ADD的COMMAND消息映射, 并在映射函数中添加下列代码: void CEx_StudentDoc::OnSturecAdd() { CAddDlg dlg; if (IDOK == dlg.DoModal()) // 添加记录 CStudent *pStudent = new CStudent(dlg.m_strName, dlg.m_strID, dlg.m_fScore1, dlg.m_fScore2, dlg.m_fScore3); m_stuObArray.Add(pStudent); SetModifiedFlag(); // 设置文档更改标志 UpdateAllViews(NULL); // 更新视图 }

49 [例Ex_Student] 3) 添加并处理菜单项
(3) 在Ex_StudentDoc.cpp文件的开始处,增加包含CAddDlg的头文件。 #include "Ex_StudentDoc.h" #include "AddDlg.h“ 4) 完善代码 (1) 在Ex_StudentDoc.h文件中,为CEx_StudentDoc类添加下列成员变量和成员 函数: public: CObArray m_stuObArray; int GetAllRecNum(void); CStudent * GetStudentAt(int nIndex);

50 [例Ex_Student] 4) 完善代码 (2) 在Ex_StudentDoc.cpp文件中,添加函数的实现代码:
CStudent * CEx_StudentDoc::GetStudentAt(int nIndex) { if ((nIndex < 0) || nIndex > m_stuObArray.GetUpperBound()) return 0; // 超界处理 return (CStudent *)m_stuObArray.GetAt(nIndex); } int CEx_StudentDoc::GetAllRecNum() return m_stuObArray.GetSize();

51 [例Ex_Student] 4) 完善代码 (3) 在CEx_StudentDoc析构函数中添加下列代码:
CEx_StudentDoc::~CEx_StudentDoc() { int nIndex = GetAllRecNum(); while (nIndex--) delete m_stuObArray.GetAt(nIndex); m_stuObArray.RemoveAll(); } (4) 在Serialize函数中添加下列代码: void CEx_StudentDoc::Serialize(CArchive& ar) if (ar.IsStoring()) m_stuObArray.Serialize(ar); } else

52 [例Ex_Student] 4) 完善代码 (5) 在CEx_StudentView::OnDraw函数中添加下列代码:
void CEx_StudentView::OnDraw(CDC* pDC) { CEx_StudentDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); int y = 0; for (int nIndex = 0; nIndex < pDoc->GetAllRecNum(); nIndex++) pDoc->GetStudentAt(nIndex)->Display(y, pDC); y += 16; } (6) 打开文档的字串资源IDR_MAINFRAME,将其内容修改为: Ex_Student\nStudentRec\nEx_Stu\n记录文件(*.rec)\n.rec\nExStudent.Document\nEx_Stu Document (7) 编译运行并测试,结果如前图6.13所示。

53 6.3.5 使用CFile类 在MFC中,CFile类是一个文件I/O的基类。它直接支持非缓冲、二进制的磁盘文
件的输入输出,也可以使用其派生类处理文本文件(CStdioFile)和内存文件 (CMemFile)。CFile类的读写功能类似于C语言中的fread和fwrite,而CStdioFile 类的读写功能类似于C语言中的fgets和fputs。 使用CFile类可以打开或关闭一个磁盘文件、向一个文件读或写数据等。下面分 别说明。 1. 文件的打开和关闭 在MFC中,使用CFile打开一个文件通常使用下列两个步骤: (1) 构造一个不带任何参数的CFile对象; (2) 调用成员函数Open并指定文件路径以及文件标志。

54 6.3.5 使用CFile类 CFile类的Open函数原型如下:
BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL ); 其中,lpszFileName用来指定一个要打开的文件路径,该路径可以是相对的、绝 对的或是一个网络文件名(UNC)。nOpenFlags用来指定文件打开的标志,它的 值见表6.9。 表6.9 CFile类的文件访问方式

55 6.3.5 使用CFile类 2. 文件的读写和定位 CFile类支持文件的读、写和定位操作。它们相关函数的原型如下:
UINT Read( void* lpBuf, UINT nCount ); 此函数将文件中指定大小的数据读入指定的缓冲区,并返回向缓冲区传输的字节 数。需要说明的是,这个返回值可能小于nCount,这是因为可能到达了文件的 结尾。 void Write( const void* lpBuf, UINT nCount ); 此函数将缓冲区的数据写到文件中。参数lpBuf用来指定要写到文件中的数据缓 冲区的指针,nCount表示从数据缓冲区传送的字节数。对于文本文件,每行的 换行符也被计算在内。 LONG Seek( LONG lOff, UINT nFrom ); 此函数用来定位文件指针的位置,若要定位的位置是合法的,此函数将返回从文 件开始的偏移量。否则,返回值是不定的且激活一个CFileException对象。参数 lOff用来指定文件指针移动的字节数,nFrom表示指针移动方式,它可以是 CFile::begin(从文件的开始位置)、CFile::current(从文件的当前位置)或 CFile::end(从文件的最后位置,但lOff必须为负值才能在文件中定位,否则将超 出文件)等。

56 6.3.5 使用CFile类 3.获取文件的有关信息 CFile 还支持获取文件状态,包括文件是否存在、创建与修改的日期和时间、逻
辑大小和路径等。 BOOL GetStatus( CFileStatus& rStatus ) const; static BOOL PASCAL GetStatus( LPCTSTR lpszFileName, CFileStatus& rStatus ); 若指定文件的状态信息成功获得,该函数返回TRUE,否则返回FALSE。其中, 参数lpszFileName用来指定一个文件路径,这个路径可以是相对的或是绝对的, 但不能是网络文件名。rStatus用来存放文件状态信息,它是一个CFileStatus结 构类型,该结构具有下列成员: CTime m_ctime 文件创建日期和时间 CTime m_mtime 文件最后一次修改日期和时间 CTime m_atime 文件最后一次访问日期和时间 LONG m_size 文件大小的字节数 BYTE m_attribute 文件属性 char m_szFullName[_MAX_PATH] 文件名

57 6.3.5 使用CFile类 4. CFile和CArchive类之间的关联
CFile theFile; theFile.Open(..., CFile::modeWrite); CArchive archive(&theFile, CArchive::store); 其中,CArchive构造函数的原型如下: CArchive( CFile* pFile, UINT nMode, int nBufSize = 4096, void* lpBuf = NULL ); 参数pFile用来指定与之关联的文件指针。nBufSize表示内部文件的缓冲区大小, 默认值为4096字节。lpBuf表示自定义的缓冲区指针,若为NULL,则表示缓冲区 建立在堆内存中,当对象清除时,缓冲区内存也被释放;若指明用户缓冲区,对 象消除时,缓冲区内存不会被释放。nMode用来指定文档是用于存入还是读取, 它可以是CArchive::load(读取数据)、CArchive::store(存入数据)或CArchive:: bNoFlushOnDelete(当析构函数被调用时,避免文档自动调用Flush 。若设置这 个标志,则必须在析构函数被调用之前调用Close。否则文件数据将被破坏)。 也可将一个CArchive 对象与CFile类指针相关联,如下面的代码(ar是CArchive 对象): const CFile* fp = ar.GetFile();

58 6.4 视图及视图类 MFC中的CView类及其它的派生类封装了视图的各种不同的功能,它们为用户实
现最新的Windows特性提供了很大的便利。这些视图类如表6.10所示,它们都可 以作为文档应用程序中视图类的基类,其设置的方法是在MFC AppWizard创建 SDI/MDI的第6步中进行基类的选择。

59 6.4.1 一般视图类的使用 表6.10 CView的派生类及其功能描述

60 6.4.1 一般视图类的使用 1. CEditView类 CEditView类对象是一种视图,像CEdit类一样,它也提供窗口编辑控制功能,可 以用来执行简单文本操作,如打印、查找、替换、剪贴板的剪切、复制和粘贴 等。由于CEditView类自动封装上述功能的映射函数,因此只要在文档模板中使 用CEditView类,那么应用程序的“编辑”菜单和“文件”菜单里的菜单项都可自动激 活。

61 6.4.1 一般视图类的使用 [例Ex_Edit] 创建一个基于CEditView类的单文档应用程序。
(1) 选择“文件”→“新建”菜单,在弹出的“新建”对话框中选择“工程”标签,选择MFC AppWizard(exe)的项目类型,指定项目工作文件夹位置,输入项目名Ex_Edit,单击 [确定]按钮。 (2) 在向导的第1步中,将应用程序类型选为“单个文档”(SDI)。 (3) 保留默认选项,单击[下一步]按钮,直到出现向导的第6步,将CEx_EditView的基 类选为CEditView,如图6.14所示。 (4) 单击[完成]按钮,编译运行,打开一个文档,结果如图6.15所示。 图6.14 更改CEx_EditView的基类 图6.15 Ex_Edit运行结果

62 6.4.1 一般视图类的使用 2. CRichEditView类
据量的文本。CRichEditView类被设计成与CRichEditDoc和CRichEditCntrItem类 一起使用,它们可实现一个完整的ActiveX包容器应用程序。 3. CFormView类 CFormView类是一个非常有用的视图类,它具有许多无模式对话框的特点。像 CDialog的派生类一样,CFormView的派生类也和相应的对话框资源相联系,它 也支持对话框数据交换和对话框数据确认(DDX和DDV)。 CFormView是所有表单视图(如CRecordView、CDaoRecordView、CHtmlView 等)的基类;一个基于表单的应用程序能让用户在程序中创建和使用一个或多个 表单。 创建表单应用程序的基本方法除了在MFC AppWizard创建的第6步中选择 CFormView作为文档应用程序视图类的基类外,还可以通过选择“插入”→“新建 形式(New Form)”菜单命令在文档应用程序中自动插入一个表单。

63 6.4.1 一般视图类的使用 4. CHtmlView 类 CHtmlView 类是在文档视图结构中提供WebBrowser控件的功能。WebBrowser 控件可以浏览网址,也可以作为本地文件和网络文件系统的窗口,它支持超级链 接、统一资源定位(URL)导航器并维护历史列表等。 5. CScrollView类 CScrollView类不仅能直接支持视图的滚动操作,而且还能管理视口的大小和映 射模式,并能响应滚动条消息、键盘消息以及鼠标滚轮消息。

64 6.4.2 列表控件和列表视图 列表控件是一种极为有用的控件之一,它可以用 “大图标”、“小图标”、“列表视
图”或“报表视图”等四种不同的方式来显示一组信息,如图6.16所示。 图6.16 列表控件样式

65 6.4.2 列表控件和列表视图 1. 列表控件的风格及其修改
列表控件的风格有两类,一类是一般风格,如表6.11所示;另一类是Visual C++ 6.0在原有 的基础上添加的扩展风格,如LVS_EX_FULLROWSELECT,表示整行选择,但它仅用于 “报表视图”显示方式中。 表6.11 列表控件的一般风格

66 6.4.2 列表控件和列表视图 2. 列表项的基本操作 CListView按照MFC文档视图结构封装了列表控件CListCtrl类的功能。由于它又 是从CCtrlView中派生的,因此它既可以调用CCtrlView的基类CView类的成员函 数,又可以使用CListCtrl功能。当使用CListCtrl功能时,必需先要得到CListView 封装的内嵌可引用的CListCtrl对象,这时可调用CListView的成员函数GetListCtrl, 如下面的代码: CListCtrl& listCtrl = GetListCtrl(); // listCtrl必须定义成引用 列表控件类CListCtrl提供了许多用于列表项操作的成员函数,如列表项与列的添 加和删除等,下面分别介绍。 (1) 函数SetImageList用来为列表控件设置一个关联的图像列表,其原型如下: CImageList* SetImageList( CImageList* pImageList, int nImageList ); 其中,nImageList用来指定图像列表的类型,它可以是LVSIL_NORMAL(大图标)、 LVSIL_SMALL(小图标)和LVSIL_STATE(表示状态的图像列表)。

67 6.4.2 列表控件和列表视图 2. 列表项的基本操作 函数InsertItem用来向列表控件中插入一个列表项。该函数成功时返回新列表项
的索引号,否则返回-1。函数原型如下: int InsertItem( const LVITEM* pItem ); int InsertItem( int nItem, LPCTSTR lpszItem ); int InsertItem( int nItem, LPCTSTR lpszItem, int nImage ); 其中,nItem用来指定要插入的列表项的索引号,lpszItem表示列表项的文本标 签,nImage表示列表项图标在图像列表中的索引号;而pItem用来指定一个指向 LVITEM结构的指针,其结构描述如下:

68 typedef struct _LVITEM
{ UINT mask; // 指明哪些参数有效 int iItem; // 列表项索引 int iSubItem; // 子项索引 UINT state; // 列表项状态 UINT stateMask; // 指明state哪些位是有效的,-1全部有效 LPTSTR pszText; // 列表项文本标签 int cchTextMax; // 文本大小 int iImage; // 在图像列表中列表项图标的索引号。 LPARAM lParam; // 32位值 int iIndent; // 项目缩进数量,1个数量等于1个图标的像素宽度 } LVITEM, FAR *LPLVITEM; 结构中,mask最常用的值可以是: LVIF_TEXT pszText有效或必须赋值。 LVIF_IMAGE iImage有效或必须赋值。 LVIF_INDENT iIndent有效或必须赋值。

69 6.4.2 列表控件和列表视图 2. 列表项的基本操作 函数DeleteItem和DeleteAllItems分别用来删除指定的列表项和全部列表项,函数原型如下: BOOL DeleteItem( int nItem ); BOOL DeleteAllItems( ); 函数FindItem用来查寻列表项,函数成功查找时返回列表项的索引号,否则返回-1。其原型如下: int FindItem( LVFINDINFO* pFindInfo, int nStart = -1 ) const; 其中,nStart表示开始查找的索引号,-1表示从头开始。pFindInfo表示要查找的信息,其结构描述 如下: typedef struct tagLVFINDINFO { UINT flags; // 查找方式 LPCTSTR psz; // 匹配的文本 LPARAM lParam; // 匹配的值 POINT pt; // 查找开始的位置坐标。 UINT vkDirection; // 查找方向,用虚拟方向健值表示。 } LVFINDINFO, FAR* LPFINDINFO; 结构中,flags可以是下列值之一或组合: LVFI_PARAM 查找内容由lParam指定。 LVFI_PARTIAL 查找内容由psz指定,不精确查找。 LVFI_STRING 查找内容由psz指定,精确查找。 LVFI_WRAP 若没有匹配,再从头开始。 LVFI_NEARESTXY 靠近pt位置查找,查找方向由vkDirection 确定。

70 int nWidth = -1, int nSubItem = -1 );
6.4.2 列表控件和列表视图 2. 列表项的基本操作 (5) 函数Arrange用来按指定方式重新排列列表项,其原型如下: BOOL Arrange( UINT nCode ); 其中,nCode用来指定排列方式,它可以是下列值之一: LVA_ALIGNLEFT 左对齐 LVA_ALIGNTOP 上对齐 LVA_DEFAULT 默认方式 LVA_SNAPTOGRID 使所有的图标安排在最接近的网格位置处 (6) 函数InsertColumn用来向列表控件插入新的一列,函数成功调用后返回新的 列的索引,否则返回-1。其原型如下: int InsertColumn( int nCol, const LVCOLUMN* pColumn ); int InsertColumn( int nCol, LPCTSTR lpszColumnHeading, int nFormat = LVCFMT_LEFT, int nWidth = -1, int nSubItem = -1 ); 其中,nCol用来指定新列的索引,lpszColumnHeading用来指定列的标题文本, nFormat用来指定列排列的方式,它可以是LVCFMT_LEFT(左对齐)、LVCFMT_ RIGHT(右对齐)和LVCFMT_CENTER(居中对齐);nWidth用来指定列的像素宽 度,-1时表示宽度没有设置;nSubItem表示与列相关的子项索引,-1时表示没有子 项。pColumn表示包含新列信息的LVCOLUMN结构地址,其结构描述如下:

71 typedef struct _LVCOLUMN
{ UINT mask; // 指明哪些参数有效 int fmt; // 列的标题或子项文本格式 int cx; // 列的像素宽度 LPTSTR pszText; // 列的标题文本 int cchTextMax; // 列的标题文本大小 int iSubItem; // 和列相关的子项索引 int iImage; // 图像列表中的图像索引 int iOrder; // 列的序号,最左边的列为0 } LVCOLUMN, FAR *LPLVCOLUMN; 结构中,mask 可以是0或下列值之一或组合: LVCF_FMT fmt参数有效 LVCF_IMAGE iImage参数有效 LVCF_ORDER iOrder参数有效 LVCF_SUBITEM iSubItem参数有效 LVCF_TEXT pszText参数有效 LVCF_WIDTH cx参数有效 fmt可以是下列值之一: LVCFMT_BITMAP_ON_RIGHT 位图出现在文本的右边,对于从图像列表中选取的图像无效 LVCFMT_CENTER 文本居中 LVCFMT_COL_HAS_IMAGES 列表头的图像是在图像列表中 LVCFMT_IMAGE 从图像列表中显示一个图像 LVCFMT_LEFT 文本左对齐 LVCFMT_RIGHT 文本右对齐

72 6.4.2 列表控件和列表视图 2. 列表项的基本操作 (7) 函数DeleteColumn用来从列表控件中删除一个指定的列,其原型如下:
BOOL DeleteColumn( int nCol ); 除了上述操作外,还有一些函数是用来设置或获取列表控件的相关属性的。例如 SetColumnWidth用来设置指定列的像素宽度,GetItemCount用来返回列表控件 中的列表项个数等。它们的原型如下: BOOL SetColumnWidth( int nCol, int cx ); int GetItemCount( ); 其中,nCol用来指定要设置的列的索引号,cx用来指定列的像素宽度,它可以是 LVSCW_AUTOSIZE,表示自动调整宽度。

73 6.4.2 列表控件和列表视图 3. 列表控件的消息 在列表视图中,可以用MFC ClassWizard映射的控件消息有公共控件消息(如
NM_DBLCLK)、标题头控件消息以及列表控件消息。常用的列表控件消息有: LVN_BEGINDRAG 用户按左鼠拖动列表列表项 LVN_BEGINLABELEDIT 用户对某列表项标签进行编辑 LVN_COLUMNCLICK 某列被按击 LVN_ENDLABELEDIT 用户对某列表项标签结束编辑 LVN_ITEMACTIVATE 用户激活某列表项 LVN_ITEMCHANGED 当前列表项已被改变 LVN_ITEMCHANGING 当前列表项即将改变 LVN_KEYDOWN 某键被按下

74 6.4.2 列表控件和列表视图 4. 示例 [例Ex_List] 列表显示当前的文件
(1) 用MFC AppWizard创建一个默认的单文档应用程序Ex_List,在创建的第6步将视图的基类选 择为CListView。 (2) 为CEx_ListView类添加下列成员函数和成员函数: public: CImageList m_ImageList; CImageList m_ImageListSmall; CStringArray m_strArray; void SetCtrlStyle(HWND hWnd, DWORD dwNewStyle) { DWORD dwOldStyle; dwOldStyle = GetWindowLong(hWnd, GWL_STYLE); // 获取当前风格 if ((dwOldStyle&LVS_TYPEMASK) != dwNewStyle) dwOldStyle &= ~LVS_TYPEMASK; dwNewStyle |= dwOldStyle; SetWindowLong(hWnd, GWL_STYLE, dwNewStyle); // 设置新风格 } 其中,成员函数SetCtrlStyle用来设置列表控件的一般风格。

75 [例Ex_List] (3) 将项目工作区窗口切换到ResourceView页面,打开Accelerator节点下的
IDR_MAINFRAME,为其添加一个键盘加速键Ctrl+Shift+X,其ID号为ID_ VIEW_CHANGE。 (4) 用ClassWizard为CEx_ListView类添加ID_VIEW_CHANGE的COMMAND 消息映射函数,并增加下列代码: void CEx_ListView::OnViewChange() { static int nStyleIndex = 1; DWORD style[4] = {LVS_REPORT, LVS_ICON, LVS_SMALLICON, LVS_LIST }; CListCtrl& m_ListCtrl = GetListCtrl(); SetCtrlStyle(m_ListCtrl.GetSafeHwnd(), style[nStyleIndex]); nStyleIndex++; if (nStyleIndex>3) nStyleIndex = 0; } 这样,当程序运行后同时按下Ctrl、Shift和x键就会切换列表控件的显示方式。

76 [例Ex_List] (5) 用ClassWizard为CEx_ListView类添加NM_DBLCLK消息映射函数,并增加 下列代码:
void CEx_ListView::OnDblclk(NMHDR* pNMHDR, LRESULT* pResult) { LPNMITEMACTIVATE lpItem = (LPNMITEMACTIVATE)pNMHDR; int nIndex = lpItem->iItem; if (nIndex >= 0) CListCtrl& m_ListCtrl = GetListCtrl(); CString str = m_ListCtrl.GetItemText(nIndex, 0); MessageBox(str); } *pResult = 0; 这样,当双击某个列表项时,就是弹出一个消息对话框,显示该列表项的文本内 容。

77 [例Ex_List] (6) 在CEx_ListView::OnInitialUpdate中添加下列代码:
void CEx_ListView::OnInitialUpdate() { CListView::OnInitialUpdate(); // 创建图像列表 m_ImageList.Create(32,32,ILC_COLOR8|ILC_MASK,1,1); m_ImageListSmall.Create(16,16,ILC_COLOR8|ILC_MASK,1,1); CListCtrl& m_ListCtrl = GetListCtrl(); m_ListCtrl.SetImageList(&m_ImageList,LVSIL_NORMAL); m_ListCtrl.SetImageList(&m_ImageListSmall,LVSIL_SMALL); LV_COLUMN listCol; char* arCols[4]={"文件名", "大小", "类型", "修改日期"}; listCol.mask = LVCF_FMT|LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM;

78 // 添加列表头 for (int nCol=0; nCol<4; nCol++) { listCol.iSubItem = nCol; listCol.pszText = arCols[nCol]; if (nCol == 1) listCol.fmt = LVCFMT_RIGHT; else listCol.fmt = LVCFMT_LEFT; m_ListCtrl.InsertColumn(nCol,&listCol); }

79 // 查找当前目录下的文件 CFileFind finder; BOOL bWorking = finder.FindFile("*.*"); int nItem = 0, nIndex, nImage; CTime m_time; CString str, strTypeName; while (bWorking) { bWorking = finder.FindNextFile(); if (finder.IsArchived()) str = finder.GetFilePath(); SHFILEINFO fi;

80 // 获取文件关联的图标和文件类型名 SHGetFileInfo(str,0,&fi,sizeof(SHFILEINFO), SHGFI_ICON|SHGFI_LARGEICON|SHGFI_TYPENAME); strTypeName = fi.szTypeName; nImage = -1; for (int i=0; i<m_strArray.GetSize(); i++) { if (m_strArray[i] == strTypeName) { nImage = i; break; } } if (nImage<0) { // 添加图标 nImage = m_ImageList.Add(fi.hIcon); SHGFI_ICON|SHGFI_SMALLICON ); m_ImageListSmall.Add(fi.hIcon); m_strArray.Add(strTypeName);

81 // 添加列表项 nIndex = m_ListCtrl.InsertItem(nItem,finder.GetFileName(),nImage); DWORD dwSize = finder.GetLength(); if (dwSize> 1024) str.Format("%dK", dwSize/1024); else str.Format("%d", dwSize); m_ListCtrl.SetItemText(nIndex, 1, str); m_ListCtrl.SetItemText(nIndex, 2, strTypeName); finder.GetLastWriteTime(m_time) ; m_ListCtrl.SetItemText(nIndex, 3, m_time.Format("%Y-%m-%d")); nItem++; } SetCtrlStyle(m_ListCtrl.GetSafeHwnd(), LVS_REPORT); // 设置为报表方式 m_ListCtrl.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES);

82 // 设置扩展风格,使得列表项一行全项选择且显示出网格线
m_ListCtrl.SetColumnWidth(0, LVSCW_AUTOSIZE); // 设置列宽 m_ListCtrl.SetColumnWidth(1, 100); m_ListCtrl.SetColumnWidth(2, LVSCW_AUTOSIZE); m_ListCtrl.SetColumnWidth(3, 200); }

83 [例Ex_List] (7) 编译并运行,结果如图6.17所示。 图6.17 Ex_List运行结果

84 6.4.3 树控件和树视图 1. 树形视图的风格 常见的树控件风格如表6.12所示,其修改方法与列表控件同的一般风格修改方法 相同。
表6.12 树控件的一般风格

85 6.4.3 树控件和树视图 2. 树控件的常用操作 树控件类CTreeCtrl类提供了许多关于树控件操作的成员函数,如节点的添加和删除等。下
面分别说明。 (1) 函数InsertItem用来向树控件插入一个新节点,操作成功后,函数返回新节点的句柄, 否则返回NULL。函数原型如下: HTREEITEM InsertItem( UINT nMask, LPCTSTR lpszItem,int nImage, int nSelectedImage, UINT nState, UINT nStateMask, LPARAM lParam, HTREEITEM hParent, HTREEITEM hInsertAfter ); HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST ); HTREEITEM InsertItem( LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST ); 其中,nMask用来指定要设置的属性,lpszItem用来指定节点的文本标签内容,nImage用 来指定该节点图标在图像列表中的索引号,nSelectedImage表示该节点被选定时,其图标 图像列表中的索引号,nState表示该节点的当前状态,它可以是TVIS_BOLD(加粗)、TVIS_ EXPANDED(展开)和TVIS_SELECTED(选中)等,nStateMask用来指定哪些状态参数有效 或必须设置,lParam表示与该节点关联的一个32位值,hParent用来指定要插入节点的父节 点的句柄,hInsertAfter用来指定新节点添加的位置,它可以是TVI_FIRST(插到开始位置)、 TVI_LAST(插到最后)和TVI_SORT(插入后按字母重新排序)。

86 6.4.3 树控件和树视图 2. 树控件的常用操作 (2) 函数DeleteItem和DeleteAllItems分别用来删除指定的节点和全部的节点。它 们的原型如下: BOOL DeleteAllItems( ); BOOL DeleteItem( HTREEITEM hItem ); 其中,hItem用来指定要删除的节点的句柄。如果hItem的值是TVI_ROOT,则所 有的节点都被从此控件中删除。 (3) 函数Expand用来用来展开或收缩指定父节点的所有子节点,其原型如下: BOOL Expand( HTREEETEM hItem, UINT nCode ); 其中,hItem指定要被展开或收缩的节点的句柄,nCode用来指定动作标志,它 可以是: TVE_COLLAPSE 收缩所有子节点 TVE_COLLAPSERESET 收缩并删除所有子节点 TVE_EXPAND 展开所有子节点 TVE_TOGGLE 如果当前是展开的则收缩,反之则展开

87 6.4.3 树控件和树视图 2. 树控件的常用操作 (4) 函数GetNextItem用来获取下一个节点的句柄。它的原型如下:
HTREEITEM GetNextItem( HTREEITEM hItem, UINT nCode ); 其中,hItem指定参考节点的句柄,nCode用来指定与hItem的关系标志,常见的标志有: TVGN_CARET 返回当前选择节点的句柄 TVGN_CHILD 返回第一个子节点句柄,hItem必须为NULL TVGN_NEXT 返回下一个兄弟节点(同一个树支上的节点)句柄 TVGN_PARENT 返回指定节点的父节点句柄 TVGN_PREVIOUS 返回上一个兄弟节点句柄 TVGN_ROOT 返回hItem父节点的第一个子节点句柄 (5) 函数HitTest用来测试鼠标当前操作的位置位于哪一个节点中,并返回该节点句柄。它 的原型如下: HTREEITEM HitTest( CPoint pt, UINT* pFlags ); 其中pFlags包含当前鼠标所在的位置标志,如下列常用定义: TVHT_ONITEM 在节点上 TVHT_ONITEMBUTTON 在节点前面的按钮上 TVHT_ONITEMICON 在节点文本前面的图标上 TVHT_ONITEMLABEL 在节点文本上

88 6.4.3 树控件和树视图 除了上述操作外,还有其他常见操作,如表6.13所示。 表6.13 CTreeCtrl类其他常见操作

89 6.4.3 树控件和树视图 3. 树形视图控件的通知消息 同列表视图相类似,树视图也可以用ClassWizard映射公共控件消息和树控件消
息。其中,常用的树控件消息有: TVN_BEGINDRAG 开始拖放操作 TVN_BEGINLABELEDIT 开始编辑文本 TVN_BEGINRDRAG 鼠标右按钮开始拖放操作 TVN_ENDLABELEDIT 文本编辑结束 TVN_ITEMEXPANDED 含有子节点的父节点已展开或收缩 TVN_ITEMEXPANDING 含有子节点的父节点将要展开或收缩 TVN_SELCHANGED 当前选择节点发生改变 TVN_SELCHANGING 当前选择节点将要发生改变

90 6.4.3 树控件和树视图 4. 示例 [例Ex_Tree] 遍历本地磁盘所有的文件夹
(1) 用MFC AppWizard创建一个默认的单文档应用程序Ex_Tree,在创建的第6步将视图的 基类选择为CTreeView。 (2) 为CEx_TreeView类添加下列成员变量: public: CImageList m_ImageList; CString m_strPath; // 文件夹路径 (3) 为CEx_TreeView类添加成员函数InsertFoldItem,其代码如下: void CEx_TreeView::InsertFoldItem(HTREEITEM hItem, CString strPath) { CTreeCtrl& treeCtrl = GetTreeCtrl(); if (treeCtrl.ItemHasChildren(hItem)) return; CFileFind finder; BOOL bWorking = finder.FindFile(strPath); while (bWorking) bWorking = finder.FindNextFile(); if (finder.IsDirectory() && !finder.IsHidden() && !finder.IsDots()) treeCtrl.InsertItem(finder.GetFileTitle(), 0, 1, hItem, TVI_SORT); }

91 [例Ex_Tree] (4) 为CEx_TreeView类添加成员函数GetFoldItemPath,其代码如下:
CString CEx_TreeView::GetFoldItemPath(HTREEITEM hItem) { CString strPath, str; strPath.Empty(); CTreeCtrl& treeCtrl = GetTreeCtrl(); HTREEITEM folderItem = hItem; while (folderItem) int data = (int)treeCtrl.GetItemData( folderItem ); if (data == 0) str = treeCtrl.GetItemText( folderItem ); else str.Format( "%c:\\", data ); strPath = str + "\\" + strPath; folderItem = treeCtrl.GetParentItem( folderItem ); } strPath = strPath + "*.*"; return strPath;

92 [例Ex_Tree] (5) 用ClassWizard为CEx_TreeView类添加TVN_SELCHANGED消息处理,并增加下列代
码: void CEx_TreeView::OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult) { NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR; HTREEITEM hSelItem = pNMTreeView->itemNew.hItem; // 获取当前选择的节点 CTreeCtrl& treeCtrl = GetTreeCtrl(); CString strPath = GetFoldItemPath( hSelItem ); if (!strPath.IsEmpty()){ InsertFoldItem(hSelItem, strPath); treeCtrl.Expand(hSelItem,TVE_EXPAND); } *pResult = 0; (6) 在CEx_TreeView::PreCreateWindow函数中添加设置树控件风格代码: BOOL CEx_TreeView::PreCreateWindow(CREATESTRUCT& cs) cs.style |= TVS_HASLINES|TVS_LINESATROOT|TVS_HASBUTTONS; return CTreeView::PreCreateWindow(cs);

93 [例Ex_Tree] (7) 在CEx_TreeView::OnInitialUpdate函数中添加下列代码:
void CEx_TreeView::OnInitialUpdate() { CTreeView::OnInitialUpdate(); CTreeCtrl& treeCtrl = GetTreeCtrl(); m_ImageList.Create(16, 16, ILC_COLOR8|ILC_MASK, 2, 1); treeCtrl.SetImageList(&m_ImageList,TVSIL_NORMAL); // 获取Windows文件夹路径以便获取其文件夹图标 CString strPath; GetWindowsDirectory((LPTSTR)(LPCTSTR)strPath, MAX_PATH+1); // 获取文件夹及其打开时的图标,并添加到图像列表中 SHFILEINFO fi; SHGetFileInfo( strPath, 0, &fi, sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_SMALLICON ); m_ImageList.Add( fi.hIcon ); SHGFI_ICON | SHGFI_SMALLICON | SHGFI_OPENICON );

94 // 获取已有的驱动器图标和名称 CString str; for( int i = 0; i < 32; i++ ) { str.Format( "%c:\\", 'A'+i ); SHGetFileInfo( str, 0, &fi, sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_SMALLICON | SHGFI_DISPLAYNAME); if (fi.hIcon) int nImage = m_ImageList.Add( fi.hIcon ); HTREEITEM hItem = treeCtrl.InsertItem( fi.szDisplayName, nImage, nImage ); treeCtrl.SetItemData( hItem, (DWORD)('A'+i)); }

95 [例Ex_Tree] (8) 编译并运行,结果如图6.18所示。 图6.18 Ex_Tree运行结果

96 6.5 文档视图结构 文档和视是编程者最关心的,应用程序的大部分代码都会被添加在这两个类中。
文档和视紧密相联,是用户与文档之间的交互接口;用户通过文档视图结构可实 现数据的传输、编辑、读取和保存等。但文档、视图以及和应用程序框架的相关 部分之间还包含了一系列非常复杂的相互作用。切分窗口及一档多视是文档和视 图相互作用的典型实例。

97 6.5.1 文档与视图的相互作用 1. CView::GetDocument函数
当MFC AppWizard产生应用程序CView类时,它同时也创建一个安全类型的 GetDocument函数,它返回的是指向用户派生文档类的指针。该函数是一个内联 (inline)函数,如下面的代码: CEx_SDIDoc* CEx_SDIView::GetDocument() // non-debug version is inline { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CEx_SDIDoc))); // “断言”m_pDocument指针所指向的CEx_SDIDoc类是一个RUNTIME_CLASS类型 return (CEx_SDIDoc*)m_pDocument; }

98 6.5.1 文档与视图的相互作用 2. CDocument::UpdateAllViews函数
如果文档中的数据发生了改变,那么所有的视图都必须被通知到,以便它们能够 对所显示的数据进行相应的更新。UpdateAllViews函数就起到这样的作用,它的 原型如下。 void UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL ); 其中,参数pSender表示视图指针,若在应用程序文档类的成员函数中调用该函 数,则此参数应为NULL,若该函数被应用程序视图类中的成员函数调用,则此 参数应为this。lHint通常表示更新视图时发送信息的提示标识值,pHint表示存贮 信息的对象指针。 当UpdateAllViews函数被调用时,如果参数pSender指向某个特定的视图对象,那 么除了该指定的视图之外,文档的所有其他视图的OnUpdate函数都会被调用。

99 6.5.1 文档与视图的相互作用 3. CView::OnUpdate函数
这是一个虚函数。当应用程序调用了CDocument::UpdateAllViews函数时,应用 程序框架就会相应地调用该函数。 virtual void OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint ); 其中,参数pSender表示文档被更改的所在视图类指针,当为NULL时表示所有 的视图需要更新。 默认的OnUpdate函数(lHint = 0, pHint = NULL)使得整个窗口矩形无效。如果用 户想要视图的某部分无效,那么用户就要定义相关的提示(Hint)参数给出准确的 无效区域;lHint和pHint含义同UpdateAllViews。 事实上,hint机制主要用来在视图中根据提示标识值来获取文档或其他视图传递 来的数据,例如将文档的CPoint数据传给所有的视图类,则有下列语句: GetDocument()->UpdateAllViews(NULL, 1, (CObject *)&m_ptDraw);

100 6.5.1 文档与视图的相互作用 4. CView::OnInitialUpdate函数
当应用程序被启动时,或当用户从“文件”菜单中选择了“新建”或“打开”时,该 CView虚函数都会被自动调用。该函数除了调用无提示参数(lHint = 0, pHint = NULL)的OnUpdate函数之外,没做其他任何事情。 但用户可以重载此函数对文档所需信息进行初始化操作。例如,如果用户应用程 序中的文档大小是固定的,那么用户就可以在此重载函数中根据文档大小设置视 图滚动范围;如果应用程序中的文档大小是动态的,那么用户就可在文档每次改 变时调用OnUpdate来更新视图的滚动范围。

101 6.5.1 文档与视图的相互作用 5. CDocument::OnNewDocument函数
在文档应用程序中,当用户从“文件”菜单中选择“新建”命令时,框架将首先构造 一个文档对象,然后调用该虚函数。这里是设置文档数据成员初始值的好地方, 当然文档数据成员初始化处理还有其他的一些方法。例如,对于文档应用程序来 说,用户还可在文档构造函数中添加初始化代码。 MFC AppWizard为用户的派生文档类自动产生了重载的OnNewDocument函 数,如下面的代码: BOOL CMyDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) //注意一定要保证对基类函数的调用, return FALSE; // Do initialization of new document here. return TRUE; }

102 6.5.2 应用程序对象指针的互调 1. 从文档类中获取视图对象指针
在文档类中有一个与其关联的各视图对象的列表,并可通过CDocument类的成员函数 GetFirstViewPosition和GetNextView来定位相应的视图对象。 GetFirstViewPosition函数用来获得与文档类相关联的视图列表中第一个可见视图的位置, GetNextView函数用来获取指定视图位置的视图类指针,并将此视图位置移动到下一个位 置,若没有下一个视图,则视图位置为NULL。它们的原型如下: virtual POSITION GetFirstViewPosition( ) const; virtual CView* GetNextView( POSITION& rPosition ) const; 例如,下面代码是使用CDocument::GetFirstViewPosition和GetNextView重绘每个视图: void CMyDoc::OnRepaintAllViews() { POSITION pos = GetFirstViewPosition(); while (pos != NULL) CView* pView = GetNextView(pos); pView->UpdateWindow(); } } // 实现上述功能也可直接调用UpdateAllViews(NULL);

103 6.5.2 应用程序对象指针的互调 2. 从视图类中获取文档对象和主框架对象指针
(2) 在视图类中获取文档对象指针是很容易的,只需调用视图类中的成员函数 GetDocument即可。而函数CWnd::GetParentFrame可实现从视图类中获取主框 架指针,其原型如下: CFrameWnd* GetParentFrame( ) const; 该函数将获得父框架窗口指针,它在父窗口链中搜索,直到一个CFrameWnd(或 其派生类)被找到为止。成功时返回一个CFrameWnd指针,否则返回NULL。 3. 在主框架类中获取视图对象指针 对于单文档应用程序来说,只需调用CFrameWnd类的GetActiveView成员函数即 可,其原型如下: CView* GetActiveView( ) const; 函数返回当前CView类指针,若没有当前视图,则返回NULL。

104 6.5.3 切分窗口 1. 静态切分和动态切分 对于“静态切分”窗口来说,当窗口第一次被创建时,窗格就已经被切分好了,窗
格的次序和数目不能再被改变,但用户可以移动切分条来调整窗格的大小。每个 窗格通常是不同的视图类。 对于“动态切分”窗口来说,它允许用户在任何时候对窗口进行切分,用户既可以 通过选择菜单项来对窗口进行切分,也可以通过拖动滚动条中的切分块对窗口进 行切分。动态切分窗口中的窗格通常使用的是同一个视图类。当切分窗口被创建 时,左上窗格通常被初始化成一个特殊的视图。当视图沿着某个方向被切分时, 另一个新添加的视图对象被动态创建;当视图沿着两个方向被切分时,新添加的 三个视图对象则被动态创建。当用户取消切分时,所有新添加的视图对象被删 除,但最先的视图仍被保留,直到切分窗口本身消失为止。 无论是静态切分还是动态切分,在创建时都要指定切分窗口中行和列的窗格最大 数目。对于静态切分,窗格在初始时就按用户指定的最大数目划分好了;而对于 动态切分窗口,当窗口构造时,第一个窗格就被自动创建。动态切分窗口允许的 最大窗格数目是2 x 2,而静态切分允许的最大窗格数目为16 x 16。

105 6.5.3 切分窗口 2. 切分窗口的CSplitterWnd类操作
在MFC中,CSplitterWnd类封装了窗口切分过程中所需的功能函数,其中成员函 数Create和CreateStatic分别用来创建“动态切分”和“静态切分”的文档窗口,函数 原型如下: BOOL Create( CWnd* pParentWnd, int nMaxRows, int nMaxCols, SIZE sizeMin, CCreateContext* pContext, DWORD dwStyle = WS_CHILD | WS_VISIBLE |WS_HSCROLL| WS_VSCROLL | SPLS_DYNAMIC_SPLIT, UINT nID = AFX_IDW_PANE_FIRST ); BOOL CreateStatic( CWnd* pParentWnd, int nRows, int nCols, DWORD dwStyle = WS_CHILD | WS_VISIBLE, 其中,参数pParentWnd表示切分窗口的父框架窗口。nMaxRows表示窗口动态 切分的最大行数(不能超过2)。nMaxCols表示窗口动态切分的最大列数(不能超过 2)。nRows表示窗口静态切分的行数(不能超过16)。nCols表示窗口静态切分的 列数(不能超过16)。sizeMin表示动态切分时允许的窗格最小尺寸。

106 6.5.3 切分窗口 2. 切分窗口的CSplitterWnd类操作
CSplitterWnd类成员函数CreateView用来为静态窗格指定一个视图类,并创建视 图窗口,其函数原型如下: BOOL CreateView( int row, int col, CRuntimeClass* pViewClass, SIZE sizeInit, CCreateContext* pContext ); 其中,row和col用来指定具体的静态窗格,pViewClass用来指定与静态窗格相关 联的视图类,sizeInit表示视图窗口初始大小,pContext用来指定一个“创建上下 文”指针。“创建上下文”结构CCreateContext包含当前文档视图框架结构。

107 6.5.3 切分窗口 3. 静态切分窗口实现 利用CSplitterWnd成员函数,用户可以在文档应用程序的文档窗口中添加动态或
静态切分功能。 [例Ex_SplitSDI] 将单文档应用程序中的文档窗口静态分成3 x 2个窗格 (1) 用MFC AppWizard创建一个默认的单文档应用程序Ex_SplitSDI。 (2) 打开框架窗口类MainFrm.h头文件,为CMainFrame类添加一个保护型的切分 窗口的数据成员,如下面的定义: protected: // control bar embedded members CStatusBar m_wndStatusBar; CToolBar m_wndToolBar; CSplitterWnd m_wndSplitter; (3) 用MFC ClassWizard创建一个新的视图类CDemoView(基类为CView)用于与 静态切分的窗格相关联。

108 [例Ex_SplitSDI] (4) 用MFC ClassWizard为CMainFrame类添加OnCreateClient(当主框架窗口客户区创建的 时候自动调用该函数)函数重载,并添加下列代码: BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { CRect rc; GetClientRect(rc); // 获取客户区大小 CSize paneSize(rc.Width()/2-16,rc.Height()/3-16); // 计算每个窗格的平均尺寸 m_wndSplitter.CreateStatic(this,3,2); // 创建3 x 2个静态窗格 m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CDemoView), paneSize,pContext); // 为相应的窗格指定视图类 m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CDemoView), paneSize,pContext); m_wndSplitter.CreateView(1,0,RUNTIME_CLASS(CDemoView), m_wndSplitter.CreateView(1,1,RUNTIME_CLASS(CDemoView), m_wndSplitter.CreateView(2,0,RUNTIME_CLASS(CDemoView), m_wndSplitter.CreateView(2,1,RUNTIME_CLASS(CDemoView), return TRUE; }

109 [例Ex_SplitSDI] (5) 在MainFrm.cpp源文件的开始处,添加视图类CDemoView的包含文件:
#include "DemoView.h" (6) 编译并运行,结果如图6.19所示。 第0,0窗格 第0,1窗格 第1,1窗格 第1,0窗格 切分条 第2,0窗格 第2,1窗格

110 6.5.3 切分窗口 4. 动态切分窗口实现 [例Ex_DySplit] 通过添加切分窗口组件来创建动态切分
(1) 用MFC AppWizard创建一个默认的单文档应用程序Ex_DySplit。 (2) 选择“工程”→“添加工程”→“Components and Controls”,弹出如图6.20所示的 对话框。 图6.20 单文档应用程序的动态切分

111 [例Ex_DySplit] (3) 双击“Visual C++ Components”,出现Visual C++支持的组件,选中Splitter Bar,结果如图6.21所示。 图6.21 Visual C++支持的组件

112 [例Ex_DySplit] (4) 单击[Insert]按钮,出现一个消息对话框,询问是否要插入Splitter Bar组件,
单击[确定]按钮,弹出如图6.22所示的对话框。从中可选择切分类型:Horizontal (水平切分)、Vertical(垂直切分)和Both(水平垂直切分)。 (5) 选中Both选项,单击[OK]按钮,回到图6.21对话框,单击[结束]按钮,动态切 分就被添加到单文档应用程序的主框架窗口类CMainFrame中。 (6) 编译运行,结果如图6.23所示。 垂直切分块 水平切分块 图6.22 Splitter Bar组件选项对话框 图6.23 Ex_DySplit运行结果

113 6.5.4 一档多视 1. 一档多视模式 MFC对于“一档多视”提供下列3个模式:
(1) 在各自MDI文档窗口中包含同一个视图类的多个视图对象。用户有时需要应 用程序能为同一个文档打开另一个文档窗口,以便能同时使用两个文档窗口来查 看文档的不同部分内容。用MFC AppWizard创建的多文档应用程序支持这种模 式,当用户选择“窗口”菜单的“新建窗口”命令时,系统就会为第一个文档窗口创 建一个副本。 (2) 在同一个文档窗口中包含同一个视图类的多个视图对象。这种模式实际上是 使用“切分窗口”机制使SDI应用程序具有多视的特征。 (3) 在单独一个文档窗口中包含不同视图类的多个视图对象。在该模式下,多个 视图共享同一个文档窗口。它有点象“切分窗口”,但由于视图可由不同的视图类 构造,所以同一个文档可以有不同的显示方法。例如,同一个文档可同时有文字 显示方式及图形显示方式的视图。

114 6.5.4 一档多视 2. 示例 下面的示例是在一个多文档应用程序Ex_Rect中为同一个文档数据提供两种不同
的显示和编辑方式,如图6.24所示。在左边的窗格中,用户可以调整小方块在右 边窗格的坐标位置。而若在右边窗格中任意单击鼠标,相应的小方块会移动到当 前鼠标位置处,且左边窗格的编辑框内容也随之发生改变。 图6.24 Ex_Rect运行结果

115 6.5.4 一档多视 [例Ex_Rect] 一档多视示例 1) 创建表单应用程序,设计表单
(1) 用MFC AppWizard创建一个多文档应用程序Ex_Rect。在第6步中将视图的基 类选择为CFormView。 (2) 打开表单模板资源IDD_EX_RECT_FORM,调整表单模板大小,并依次添加 如表6.15所示的控件。 表6.15 在表单中添加的控件

116 [例Ex_Rect] 1) 创建表单应用程序,设计表单
(3) 打开MFC ClassWizard的Member Variables标签,在Class name中选择 CEx_RectView,选中所需的控件ID号,双击鼠标或单击Add Variables按钮。 依次为表6.16中的控件添加成员变量。 表6.16 添加的控件变量

117 [例Ex_Rect] 2) 添加CEx_RectDoc和CEx_RectView类代码
在CEx_RectDoc类中添加一个公有型的CPoint数据成员m_ptRect,用来记录小方块的位置。 在CEx_RectDoc类的构造函数处添加下列代码: CEx_RectDoc::CEx_RectDoc() { m_ptRect.x = m_ptRect.y = 0; // 或m_ptRect = CPoint(0,0) } 打开MFC ClassWizard的Messsage Maps标签页,为编辑框IDC_EDIT1和IDC_EDIT2添加 EN_CHANGE的消息映射,使它们的映射函数名都设为OnChangeEdit,并添加下列代码: void CEx_RectView::OnChangeEdit() UpdateData(TRUE); CEx_RectDoc* pDoc = (CEx_RectDoc*)GetDocument(); pDoc->m_ptRect.x = m_CoorX; pDoc->m_ptRect.y = m_CoorY; CPoint pt(m_CoorX, m_CoorY); pDoc->UpdateAllViews(NULL, 2, (CObject *)&pt);

118 [例Ex_Rect] 2) 添加CEx_RectDoc和CEx_RectView类代码
(4) 用MFC ClassWizard为CEx_RectView添加OnUpdate的消息函数,并添加下 列代码: void CEx_RectView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { if (lHint == 1) CPoint* pPoint = (CPoint *)pHint; m_CoorX = pPoint->x; m_CoorY = pPoint->y; UpdateData(FALSE); // 在控件中显示 CEx_RectDoc* pDoc = (CEx_RectDoc*)GetDocument(); pDoc->m_ptRect = *pPoint; // 保存在文档类中的m_ptRect }

119 [例Ex_Rect] 2) 添加CEx_RectDoc和CEx_RectView类代码
(5) 在CEx_RectView::OnInitialUpdate中添加一些初始化代码: void CEx_RectView::OnInitialUpdate() { CFormView::OnInitialUpdate(); ResizeParentToFit(); CEx_RectDoc* pDoc = (CEx_RectDoc*)GetDocument(); m_CoorX = pDoc->m_ptRect.x; m_CoorY = pDoc->m_ptRect.y; m_SpinX.SetRange(0, 1024); m_SpinY.SetRange(0, 768); UpdateData(FALSE); } (6) 这时编译并运行程序,程序会出现一个运行错误。造成这个错误的原因是因为旋转按钮控件在设置范围时,会自动对其伙伴窗口(编辑框控件)进行更新,而此时编辑框控件还没有完全创建好,因此需要进行一些处理。

120 [例Ex_Rect] 3) 处理旋转按钮控件的运行错误 (1) 为CEx_RectView添加一个BOOL型的成员变量m_bEditOK。
(2) 在CEx_RectView构造函数中将m_bEditOK的初值设为FALSE。 (3) 在CEx_RectView::OnInitialUpdate函数的最后将m_bEditOK置为TRUE,如 下面的代码: void CEx_RectView::OnInitialUpdate() { … UpdateData(FALSE); m_bEditOK = TRUE; } (4) 在CEx_RectView::OnChangeEdit函数的最前面添加下列语句: void CEx_RectView::OnChangeEdit() { if (!m_bEditOK) return;

121 [例Ex_Rect] 4) 新增CDrawView类,添加框架窗口切分功能
(1) 用MFC ClassWizard为添加一个新的CView的派生类CDrawView。 (2) 用MFC ClassWizard为CChildFrame类添加OnCreateClient函数的重载,并添加下列代 码: BOOL CChildFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { CRect rect; GetWindowRect( &rect ); BOOL bRes = m_wndSplitter.CreateStatic(this, 1, 2); // 创建2个水平静态窗格 m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CEx_RectView), CSize(0,0), pContext); m_wndSplitter.CreateView(0,1,RUNTIME_CLASS(CDrawView), CSize(0,0), pContext); m_wndSplitter.SetColumnInfo(0, rect.Width()/2, 10); // 设置列宽 m_wndSplitter.SetColumnInfo(1, rect.Width()/2, 10); m_wndSplitter.RecalcLayout(); // 重新布局 return bRes; //CMDIChildWnd::OnCreateClient(lpcs, pContext); }

122 [例Ex_Rect] 4) 新增CDrawView类,添加框架窗口切分功能 (3) 在ChildFrm.cpp的前面添加下列语句:
#include "ChildFrm.h" #include "Ex_RectView.h" #include "DrawView.h" (4) 打开ChildFrm.h文件,为CChildFrame类添加下列成员变量: public: CSplitterWnd m_wndSplitter;

123 [例Ex_Rect] 4) 新增CDrawView类,添加框架窗口切分功能
(5) 此时编译,程序会有一些错误。这些错误的出现是基于这样的一些事实:在 用标准C/C++设计程序时,有一个原则即两个代码文件不能相互包含,而且多次 包含还会造成重复定义的错误。 (6) 打开Ex_RectView.h文件,在class CEx_RectView : public CFormView语句 前面添加下列代码: class CEx_RectDoc; // 声明CEx_RectDoc类需要再次使用 class CEx_RectView : public CFormView {…}

124 [例Ex_Rect] 5) 添加CDrawView类代码
(1) 为CDrawView类添加一个公有型的CPoint数据成员m_ptDraw,用来记录绘 制小方块的位置。 (2) 在CDrawView::OnDraw函数中添加下列代码: void CDrawView::OnDraw(CDC* pDC) { CDocument* pDoc = GetDocument(); CRect rc(m_ptDraw.x-5, m_ptDraw.y-5, m_ptDraw.x+5, m_ptDraw.y+5); pDC->Rectangle(rc); // 绘制矩形,以后还会详细讨论 } (3) 用MFC ClassWizard为CDrawView类添加OnInitialUpdate的消息函数,并添 加下列代码: void CDrawView::OnInitialUpdate() CView::OnInitialUpdate(); CEx_RectDoc* pDoc = (CEx_RectDoc*)m_pDocument; m_ptDraw = pDoc->m_ptRect;

125 [例Ex_Rect] 5) 添加CDrawView类代码
(4) 在DrawView.cpp文件的前面添加CEx_RectDoc类的包含语句: #include "Ex_Rect.h" #include "DrawView.h" #include "Ex_RectDoc.h" (5) 用MFC ClassWizard为CDrawView类添加OnUpdate的消息函数,并添加下 列代码: void CDrawView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { if (lHint == 2) CPoint* pPoint = (CPoint *)pHint; m_ptDraw = *pPoint; Invalidate(); }

126 [例Ex_Rect] 5) 添加CDrawView类代码
(6) 用MFC ClassWizard为CDrawView类添加WM_LBUTTONDOWN的消息映 射,并添加下列代码: void CDrawView::OnLButtonDown(UINT nFlags, CPoint point) { m_ptDraw = point; GetDocument()->UpdateAllViews(NULL, 1, (CObject*)&m_ptDraw); Invalidate(); // 强迫调用CDrawView::OnDraw CView::OnLButtonDown(nFlags, point); } (7) 编译运行并测试,结果如前面图6.24所示。


Download ppt "第6 章框架窗口、文档和视图 尽管窗口、文档和视图是MFC的基础,但可能也是最不易理解的部分,因为其概"

Similar presentations


Ads by Google