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

Slides:



Advertisements
Similar presentations
软件开发技术基础 第 3 章 操作系统及程序设计 讲授教师:卫颜俊. 主 要 内 容 主 要 内 容 操作系统及其功能 进程管理应用程序设计 内存管理应用程序设计 设备与文件管理应用程序设计 人机接口管理应用程序设计.
Advertisements

第8章 数据库编程 Visual C++ 6.0为用户提供了ODBC、DAO及OLE DB三种数据库方式。这三种
过程性保存文本格式的实现 计算概论小课题 王元康.
第三章 鏈結串列 Linked List.
计算机高级程序设计 第五章.
雷 霆 战 机 By—谷恩轩&余万全.
複習 struct score_Type{ int chinese,english; }; struct my_Type{
第7章 框架窗口、文档和视图 7.1框架窗口 7.2文档及其序列化 7.3视图及视图类 7.4文档视图结构.
第四章 在 C# 中实现 OOP 概念.
计算概论 第二十一讲 文件操作 北京大学信息学院.
4.1 概述 4.2 类与对象的实现 4.3 对象的初始化和析构 4.4 类的包含 4.5 类模板
第5章 图形和文本输出.
单片机原理与应用 C/C++在现代数字计算机上的实现.
Visual C++ Windows Programming
第4讲 Windows编程基础 此为封面页,需列出课程编码、课程名称和课程开发室名称。
·线性表的定义及ADT ·线性表的顺序存储结构 ·线性表的链接存储结构 · 单向循环链表 · 双链表、双向循环链表 · 一元多项式的加法
本單元介紹何謂變數,及說明變數的宣告方式。
第六章 继承性和派生类 胡昊 南京大学计算机系软件所.
第8章视图应用框架 8.1文档与视图的相互作用 8.2一般视图框架 8.3列表视图框架 8.4树视图框架 8.5切分视图框架 8.6综合应用.
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
程序设计期末复习 黎金宁
第三章 C++中的C 面向对象程序设计(C++).
2 C++ 的基本語法和使用環境 親自撰寫和執行程式是學好程式語言的不二法門。本章藉由兩個簡單的程式,介紹C++ 程式的基本結構和開發環境,讓初學者能逐漸建立使用C++ 的信心。
第5章 文本与字体 2018/12/3 面向对象与可视化 程序设计 --Visual C++ 编程 主讲教师: 唐 龙教授 (计算机科学与技术系) 黄维通博士 (计算机与信息管理中心) 清 华 大 学 2001年2月 2018/12/3 Huang Weitong.
简要回顾 了解课程体系、课程目的和主要内容 掌握Windows编程涉及的一些主要概念 DOS程序和Windows程序的主要区别 窗口和程序
第1章 Windows应用程序框架的 创建与消息处理
第3章 Windows 应用程序基础 2018/12/6 第2讲 Windows 应用程序基础 VC++面向对象与可视化程序设计.
第十一讲 MFC常用控件的使用(3) 严宣辉 数学与计算机科学学院
MFC WinSock类的编程 为简化套接字网络编程,更方便地利用Windows的消息驱动机制,微软的基础类库(Microsoft Foundation Class Libary,简称MFC),提供了两个套接字类,在不同的层次上对Windows Socket API函数进行了封装,为编写Windows.
第9章数据库应用 9.1数据库和ODBC操作 9.2MFC ODBC应用编程 9.3ADO数据库编程 9.4数据库相关的ActiveX控件
网络游戏开发语言基础 ——Windows程序设计
VC++开发实例 张荣梅 2007年2月.
第5章 文本与字体 2018/12/31 面向对象与可视化 程序设计 --Visual C++ 编程 主讲教师: 唐 龙教授 (计算机科学与技术系) 黄维通博士 (计算机与信息管理中心) 清 华 大 学 2001年2月 2018/12/31 Huang Weitong.
程式設計 博碩文化出版發行.
第4章 MFC编程 4.1 MFC概述 4.2 MFC和Win CObject类 4.4 消息映射的实现
第 10 章 Windows用户界面 10.1 Windows消息与命令 10.2 单文档与多文档程序 10.3 对话框与常用组件
第7章图形、文本和位图 7.1概述 7.2图形设备接口 7.3图形绘制 7.4字体与文字处理 7.5在对话框及控件中绘图 7.6综合应用.
THE C PROGRAMMING LANGUAGE
Visual C++ Windows Programming
Ch02-基礎語法.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C#程序设计基础 第二章 数据类型.
第八章 文档/视图结构应用程序 8.1 文档/视图结构 8.2 图形与文字输出 8.3 定时器 8.4 鼠标和键盘消息处理 8.5 对话框
Animation(動畫) 靜宜大學資工系 蔡奇偉 副教授
第6章数据和文档 6.1CString类 6.2使用简单数组集合类 6.3使用CFile类 6.4文档序列化 6.5综合应用.
第1章 概述 本章要点: C语言程序结构和特点 C语言程序的基本符号与关键字 C语言程序的编辑及运行 学习方法建议:
第十五讲 MFC与消息处理 MFC简介 Windows编程机制 MFC应用程序框架原理 创建应用程序框架 消息及其分类 消息映射机制
版权所有 复制必究 第 6 章 MFC原理与方法.
第三章 链表 单链表 循环链表 多项式及其相加 双向链表 稀疏矩阵.
第五章 递归与广义表 递归的概念 递归过程与递归工作栈 递归与回溯 广义表.
第十二讲 菜单、工具栏和状态栏 严宣辉 数学与计算机科学学院
《面向对象程序设计与Visual C++6.0教程》
Visual C++ Windows Programming
_05MessageMap的原理 本节课讲师——void* 视频提供:昆山爱达人信息技术有限公司 官网地址:
MFC及其应用.
MFC控件 本节内容 视频提供:昆山爱达人信息技术有限公司 视频录制:yang 官网地址:
第5章 文本与字体 2019/4/25 面向对象与可视化 程序设计 --Visual C++ 编程 主讲教师: 唐 龙教授 (计算机科学与技术系) 黄维通博士 (计算机与信息管理中心) 清 华 大 学 2001年2月 2019/4/25 Huang Weitong.
C++语言程序设计教程 第2章 数据类型与表达式 第2章 数据类型与表达式 制作人:杨进才 沈显君.
功能表的建立 製作.
<编程达人入门课程> 本节内容 为什么要使用变量? 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ:
Visual C++ Windows Programming
第二章 Java语法基础.
第10章媒体控制接口 10.1 MCI设备类型 10.2 MCI编程步骤 10.3使用MCIWnd窗口类.
#include <iostream.h>
第二章 Java基本语法 讲师:复凡.
Go 语言编程 —— 平台研发部 吴植民.
第2章 Java语言基础.
MFC的六大核心 机制3 动态创建 本节内容 视频提供:昆山爱达人信息技术有限公司 视频录制:yang
熟悉VC++开发环境.
手工编写第一个 MFC程序 本节内容 视频提供:昆山爱达人信息技术有限公司 视频录制:yang 官网地址:
Presentation transcript:

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

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

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

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

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

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标志位清零。

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所示。

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

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所示。

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

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

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

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

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(); // 将主框架窗口居中

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

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; }

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

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

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

[例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 添加新的字符串项

[例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对话框。

[例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; }

[例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所示的文件打开对话框。

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

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

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虚函数,以便显示出带有菜单、工 具栏、状态栏以及视图窗口的主框架窗口。

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)”菜单中。

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

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

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 的结果是“真”还是“假”就可决定向文档写或读数据。

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

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类的文档序列化操作方 法。

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; }

[例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; }

[例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] == '&') // 是自己的文档

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("打开的文档无效!"); }

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

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; // 使用堆内存中的默认的内存块大小

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指定的另一个数组 的内容。函数返回加入的第一个元素的序号。

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

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();

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

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 添加的控件

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

[例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)

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)

{ 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;

[例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); // 更新视图 }

[例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);

[例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();

[例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

[例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所示。

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并指定文件路径以及文件标志。

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

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必须为负值才能在文件中定位,否则将超 出文件)等。

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] 文件名

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();

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

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

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

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运行结果

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)”菜单命令在文档应用程序中自动插入一个表单。

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

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

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

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(表示状态的图像列表)。

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结构的指针,其结构描述如下:

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有效或必须赋值。

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 确定。

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结构地址,其结构描述如下:

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 文本右对齐

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

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

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用来设置列表控件的一般风格。

[例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键就会切换列表控件的显示方式。

[例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; 这样,当双击某个列表项时,就是弹出一个消息对话框,显示该列表项的文本内 容。

[例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;

// 添加列表头 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); }

// 查找当前目录下的文件 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;

// 获取文件关联的图标和文件类型名 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);

// 添加列表项 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);

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

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

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

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(插入后按字母重新排序)。

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 如果当前是展开的则收缩,反之则展开

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 在节点文本上

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

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

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); }

[例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;

[例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);

[例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 );

// 获取已有的驱动器图标和名称 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)); }

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

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

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; }

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函数都会被调用。

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);

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

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; }

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);

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

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

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表示动态切分时允许的窗格最小尺寸。

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包含当前文档视图框架结构。

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)用于与 静态切分的窗格相关联。

[例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; }

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

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

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

[例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运行结果

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

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

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

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

[例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);

[例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 }

[例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) 这时编译并运行程序,程序会出现一个运行错误。造成这个错误的原因是因为旋转按钮控件在设置范围时,会自动对其伙伴窗口(编辑框控件)进行更新,而此时编辑框控件还没有完全创建好,因此需要进行一些处理。

[例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; …

[例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); }

[例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;

[例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 {…}

[例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;

[例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(); }

[例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所示。