Upload
grady-tyler
View
155
Download
31
Embed Size (px)
DESCRIPTION
第三章 MFC 程序设计. 本章的内容,简洁而不失完整性:由应用程序向导建立程序框架,由资源编辑器建立用户界面,由类向导建立成员变量、消息映射和处理函数,加上文档序列化和注册 MFC 包含了所有与系统相关的类 ( 包括图形用户界面相关的类 ) ,其中封装了大多数的 API 函数,提供了应用程序框架和开发应用程序的工具如应用程序向导、类向导、可视化资源设计等高效工具,用消息映射处理消息响应,大大简化了 Windows 应用程序的开发工作,使程序员可以从繁重的编程工作中解脱,提高工作效率。. 目录. 3.1. MFC 程序设计简介 3.2. MFC 程序设计 - PowerPoint PPT Presentation
Citation preview
第三章 MFC 程序设计本章的内容,简洁而不失完整性:由应用程序向导建立程序框架,由资源编辑器建立用户界面,由类向导建立成员变量、消息映射和处理函数,加上文档序列化和注册 MFC 包含了所有与系统相关的类 ( 包括图形用户界面相关的类 ) ,其中封装了大多数的 API 函数,提供了应用程序框架和开发应用程序的工具如应用程序向导、类向导、可视化资源设计等高效工具,用消息映射处理消息响应,大大简化了 Windows 应用程序的开发工作,使程序员可以从繁重的编程工作中解脱,提高工作效率。
3.1.3.1.MFC程序设计简介3.2.3.2.MFC 程序设计3.3.3.3.用户界面资源的应用3.4.3.4.对话框的应用3.5.3.5.基于对话框的应用程序设计实例3.6.3.6.文本和图形输出3.7.3.7.文档的保存3.8.3.8. MDI应用程序设计
目录
3.1 MFC 程序设计简介 MFC 类库 基础类 (已在第二章中介绍) 宏、全局变量及全局函数应用程序框架3.1.1 MFC 定义的宏、全局函数及全局变量
类库
1. MFC 提供的主要宏:消息映射宏:声明消息映射表宏 DECLARE_MESSAGE_MAP 、消息映射定义宏 BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 消息映射表入口宏 ON_ 消息名。 ( 本章只介绍消息映射宏 )
3.1.1 MFC 定义的宏、全局函数及全局变量 动态 MFC 对象宏 DECLARE_DYNAMIC 和 IMPLEMENT_DYNAMIC动态创建对象宏 DECLARE_DYNCREATE 和 IMPLEMENT_DYNCREATE运行时类宏: RUNTIME_CLASS序列化宏: DECLARE_SERIAL 和 IMPLEMENT_SERIAL诊断服务宏: ASSERT 、 VERIFY 跟踪服务宏: TRACE异常处理宏: THROW
3.1.1 MFC 定义的宏、全局函数及全局变量MFC 提供一些不属于任何类的全局函数,函数名以 Afx开头,可以被应用程序中的所有类和函数所调用。如:AfxAbort() ,无条件终止一个应用程序的执行; AfxGetAppName() ,返回指向应用程序名的字符串指针; AfxWinInit(), 由 WinMain() 调用,对 MFC 应用程序进行图形用户界面 (GUI) 的初始化等等。3. 全局变量 全局变量名以 afx 开头,如 afxTraceFlag 、afxDump等,主要与异常处理有关。
2. 全局函数
3.1.2 MFC 命名规则 匈牙利命名法规定 :• 变量名以一个或多个小写字母开头作为前缀,标识变量的数据类型;• 类型标识后是若干个单词,标识变量含义,每个单词以大写字母开头,其余字母小写;• MFC 中所有类及自定义类都以 C开头;• 类成员变量以 m_为前缀,下划线后是变量名等等。
前缀 数据类型 例B,b BOOL bIsValidCh char ChText,ChGradec class cStringCs CString CsNameDw DWORD DwFlagsfn function fnSort
handle hWnd,hPeni int iCountp pointer pFramesz ASCII string szNamem_ Data member m_szName
表 3.1 命名规则
h
MFC 提供了构造 Windows 应用程序的框架,它不仅为应用程序建立标准的结构,生成一系列起动文件,还提供标准的图形用户界面如菜单、工具栏、状态条等供开发人员在程序中补充完善,开发人员只需要完成针对特定应用的代码。使用应用程序框架的应用程序代码小,运行速度快,它提供的工具也降低了编码的难度。
3.1.3 应用程序框架介绍
程序的数据储存在文档类中(作为文档类的数据成员),是对数据的抽象表示。数据显示由视图负责,视图是程序窗口的客户区,框架窗口是客户区的框架,程序数据显示在窗口,用户通过视图与程序交互。一个文档可以对应多个不同视图;而一个视图一般只对应一个文档。当文档数据发生变化时,视图显示的状态也会随之变化;对于多个视图的情况,基于同一文档的多个视图的显示同步变化。
1. 文档 - 视图结构 文档视图结构提供一种连续处理数据的方式,使应用程序的数据与数据的显示分开处理。
文档、视图及框架的关系:
框架窗口对象文档与视图之间的双向数据交换
发送至视图对象的消息
文档对象( 保存程序数据 )
应用程序对象
视图对象
发送至框架窗口的消息
图 3.1
2. 单文档界面应用 单文档界面应用程序简称 SDI(Single Document Interface) ,只允许一个框架窗口存在,每次只能创建和拥有一个文档对象,也只允许打开一个文档。SDI各类 关系图可将文档比作一个文件袋,专门用于保存文件数据,需要看时需从文件袋 ( 文档 ) 中取出来,放到桌面 ( 视图 )上看。
表 3.2 基于文档 /视图关系的 SDI 应用中的类Document文档类 储存与应用程序相关的数据View 视图类 决定文档数据的显示方式,提供用户界面Frame window框架窗口
派生自 CFrameWnd,为 CWinApp类的m_pMainWnd 成员赋值
Document template文档模板CSingelDocTemplate类对象,用构造函数将文档、视图、框架窗口及用户界面资源联系在一起,创建并管理文档
Application应用程序类 代表应用程序,拥有指向文档管理类的指针,管理应用程序中所有文档模板,负责初始化和退出清理工作 返回
应用程序类
框架类 ( 窗口框架 )
资源 (决定用户界面的显示 )
视图类( 显示数据并与用户交互 )
文档类(保存数据 )
单文档模板返回
应用程序类CDocManager* m_pDocManager
框架类 ( 窗口框架 )
资源 (决定用户界面的显示 )
m_pDocument视图类 ( 显示数据并与用户交互 )
文档类 (保存数据 )m_pDocTemplatem_viewList
返回
单文档模板CRuntimeClass*m_pDocClassCRuntimeClass*m_pFrameClassCRuntimeClass*m_pViewClassUINT m_nIDResource
多文档界面应用简称 MDI(Multiple Document Interface) ,允许有多个文档类、视图类和多个文档模板 CMultiDocTemplate( 建立多种文档 -视图 - 子框架关系 )存在,运行时允许打开多个 MDI子框架窗口,拥有各自的文档和视图对象。示例 关系图在打开的多个子窗口中,只有一个活动窗口,应用程序菜单和工具栏的所有的命令都是针对活动窗口的。
3. 多文档界面应用
子框架
资源 2视图类 2
文档类 2
文档模板 2
子框架
资源 1视图类 1
文档类 1
文档模板 1
主框架…
其它文档模板
返回CDocManager应用程序类CDocManager*m_pDocManager
文档 / 视图结构可以充分发挥应用程序框架和 MFC的优势,是 MFC 应用程序的核心,但使用它也要付出运行效率和代码增大的代价。 文档 / 视图结构对应用程序来说并不是必不可少的,有两种常见的非文档 / 视图结构应用。
4. 非文档 / 视图结构应用
常见非文档 /视图结构应用:(1). 基于对话框的应用,它包含一个 CWinApp 对象和一个模态对话框,对话框负责数据的存储和显示 。关系图(2).SDI或 MDI应用,都可以不包含文档对象, SDI包含一个 CWinApp 对象,一个框架窗口对象和一个从 CWnd 派生的 CChildView视图对象,视图对象负责数据的显示, MDI 应用还包括一个子框架窗口。
对话框类作为应用程序主窗口负责保存和显示数据应用程序类
返回Dialog-based Application
主框架,应用程序主窗口CChildView 视图类负责保存和显示数据
应用程序类SDI Application, None Doc/View 返回
子框架,视图框架窗口CChildView视图类应用程序类
子框架,视图框架窗口CChildView视图类 …
主框架,应用程序主框架窗口
MDI Application, None Doc/View
返回
图 3.2 MFC 应用程序开发过程
类向导
集成开发环境应用程序向导
资源编辑器Create
.def .cpp.h
.dsw.dsp .ico .rc .bmp
源文件 资源文件编译 .obj Build 资源编译.res
应用程序
5. MFC 应用程序创建
6.最简单的 MFC 应用程序示例 对于一个 MFC 应用程序来说,只有 CWinApp 的派生对象是必不可少的,其它类均可视情况进行取舍。下例是一个最简单的 MFC 应用程序。例:显示一个消息框。 (1). 创建工程,工程类型选择 Win32 Application ,工程名为 Ex3_0; (2). 创建 C++ 源文件,文件名 Ex3_0.cpp ,内容如下 :
#include <afxwin.h>class CEx3_0App: public CWinApp{ //CWinApp派生类
public: virtual BOOL InitInstance() { MessageBox(NULL,”SimplistMFC Application.”, ”Ex3_0”,MB_OK); return TRUE; }}; CEx3_0App theApp; //全局变量,代表应用程序本身 CWinApp::InitInstance()虚函数完成应用程序的初始化工作,派生的应用程序类都要重载此函数进行初始化,如完成主框架窗口的构造,窗口定义显示等工作。
(3). 设置使用 MFC 。 选择 Project|Settings…菜单项,选择 General页面,在 Microsoft Foundation Classes 组合框中选择 Use MFC in a shared DLL 。(4).编译生成并运行此工程,结果显示一个消息框 ,如下图 :
图 3.3 最简单 MFC 应用示例
3.2.MFC 程序设计 MFC 应用程序的执行过程 (参见图 3.4):
1. 构造全局对象— CWinApp派生类对象; 2. 运行由应用程序框架提供的 WinMain 函数;
int AFXAPI AfxWinMain(...){ CWinApp *pApp = AfxGetApp();
AfxWinInit(...);
pApp->IninApplication(); pApp->InintInstance(); nReturnCode = pApp->Run();
AfxWinTerm();}
CMyWinApp theApp;
BOOL CMyWinApp::InitInstance(){ m_pMainWnd = new CMyFrameWnd(); m_pMainWnd ->ShowWindow(m_nCmdShow); m_pMainWnd UpdateWindow(); return TRUE;}
CMyFrameWnd::CMyFrameWnd(){
Create(NULL,…,…,…)}
CWinApp::Run();
CWinThread::Run(){ … do{ ::GetMessage(&msg,…); PreTranslateMessage(&msg); ::TranslateMessage(&msg); ::DispatchMessge(&msg); … }while(::PeakMessage()); …}
DefWindowProc()
AfxWndProc()
图 3.4 MFC 应用程序的执行过程
3. 在 WinMain 中,通过 afxGetApp()获得全局对象的指针 pApp ,调用全局函数 AfxWinInit() ,为 CWinApp的成员变量 m_hInstance, m_hPrevInstance, m_lpCmdLine,m_nCmdShow赋初值; 然后调用 pApp->InitApplication() ,这是 CWinApp 的虚函数,一般不需要改写; 调用 pApp->InitInstance() ,每个程序都必需改写这个函数,进行应用程序初始化; 4. 在 InitInstance() 函数中,先用 new 构造一个 CFrame
Wnd派生类对象,其构造函数又调用 Create() ,创建主窗口, MFC依此自动为应用程序注册窗口类;调用 ShowWindow() 显示窗口,调用 UpdateWindow() ,发出WM_PAINT 消息;
5. 回到 WinMain 中,调用 pApp->Run ,进入消息循环,通过API 函数 GetMessage()获得消息, TranslateMessage()处理消息, DispatchMessage()派送消息到 CWnd::DefWindowProc() , DefWindowProc()按照消息映射表的定义将消息分发到各相应消息处理函数;如消息队列为空,则 Run 调用 CWinApp::OnIdle() 进行空闲处理,重载 OnIdle() 可以去处理后台程序 (低优先级 );
6. 若用户选择菜单 File/Close ,则程序收到 WM_COLSE 消息,调用 ::DestroyWindow() 发出WM_DESTROY 消息,然后调 PostQuitMessage() ,发出WM_QUIT 消息,此时Run 会结束其内部消息循环,调用 ExitInstance() ;7.最后,返回 WinMain() ,执行 WinTerm() ,结束程序运行。
几点解释:(1). 每个 MFC 应用都有 stdafx.h头文件,由应用程序向导自动生成; (2). 每个 MFC 应用都应包含头文件 afxwin.h,其内部又包含了其他头文件。(3). 每一个 MFC 应用程序都包含唯一的 CWinApp派生类对象 ,代表应用程序本身。(4).MFC 应用程序中看不到 WinMain() 入口函数,它被封装在应用程序框架里,运行时自动调用;
3.2.2.MFC 消息映射 消息映射 (Message Map)机制 :消息映射机制规定每个要响应消息的类定义自己的消息映射表,将消息与处理消息的函数联系在一起;当事件发生产生消息时,在程序中各个类的消息映射表中查找,当找到消息处理函数时,便将消息送至对应的消息处理函数进行处理。每个从 CCmdTarget 所派生的类都可以接收消息,都可以定义自己的消息映射表 Message Map 。
消息映射表:1. 消息 消息是 Windows用来通知 MFC 应用程序事件发生的主要方式,应用程序的行为取决于对消息的响应方式。 MFC把消息分为三大类 : (1). 标准 Windows消息 (WM_XXX) , (2). 命令消息 (WM_COMMAND), (3).控件通知消息。
2. 消息映射 消息映射将消息与应用程序提供的处理该消息的成员函数联系在一起,一组消息映射构成消息映射表。消息映射表由 BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 这一对宏作开始和结束,中间是若干消息映射入口每一个入口由特定的消息宏 ON_XXX 组成,将一个消息与与之对应的处理函数联系在一起。
定义和使用消息映射表的步骤: ① 在头文件的类声明中添加 DECLARE_MESSAGE_MAP 宏声明消息映射 ; 例如 ② 在类的定义文件 (.cpp) 定义消息映射表,方法是在 文件的开始处添加 BEIGN_MESSAGE_MAP 和END_MESSAGE_MAP 这一对宏,在它们之间放置消息映射宏,定义并初始化消息映射表,在消息与处理消息的函数之间建立联系;例如 ③ 在类的定义文件 (.cpp) 中添加消息处理函数。例如
消息宏:(1). 对标准 Windows 消息的响应
消息宏 ON_WM_XXX 定义对标准Windows 消息 WM_XXX 的响应,所有派生自 CWnd 的类均可响应这类消息,系统默认的响应函数为 OnXxx ,对应关系见下面的表 3.3 :宏名 消息 默认处理函数
ON_WM_CHAR WM_CHAR OnCharON_WM_ CLOSE WM_ CLOSE OnCloseON_WM_CREATE WM_ CREATE OnCreate
ON_WM_LBUTTONDOWN
WM_ LBUTTONDOWN
OnLButtonDownON_WM_LBUTTONUP WM_ LBUTTONUP OnLButtonUp
ON_ WM_MOUSEMOVE
WM_ MOUSEMOVE OnMouseMove由于有系统默认的消息和处理函数,在消息映射表中只写宏名,如:ON_WM_CREATE()
(2). 对命令消息的的响应 。 ON_COMMAND 宏定义对命令消息的响应,格式为ON_COMMAND( 命令 ID,响应函数 )举例如下:ON_COMMAND ( IDM_ABOUT, OnAbout )// 表示 IDM_ABOUT 消息由 OnAbout()响应ON_COMMAND ( IDM_FILENEW, OnFileNew)ON_COMMAND ( IDM_FILEOPEN, OnFileOpen)所有由用户定义的命令消息也由 ON_COMMAND 定义消息映射关系。
(3). 对控件消息的响应 控件消息由按钮 (BN_) 、编辑框 (EN_) 、组合框(CBN_) 、列表框 (LBN_) 等产生,在消息名前加上 ON_即构成宏名,举例如下: ON_BN_CLICKED (按钮 ID,响应函数 ) ON_CBN_DBCLK (组合框 ID,响应函数 ) ON_EN_SETFOCUS (组合框 ID,响应函数 ) ON_LBN_DBCLK ( 列表框 ID,响应函数 ) 分别表示选择各个控件后,产生的消息由其后面定义的函数进行处理。
3. 消息处理消息一般由应用程序中对象的成员函数来响应。当某个事件发生后,系统向应用程序消息队列放入一个消息, CWinApp 对象取得消息并按照消息映射表的定义将消息派送到某个类的相应的消息处理函数进行处理。使用 ClassWizard(见第五章菜单中 View 菜单介绍 )定义消息映射关系,添加消息处理函数,系统会自动生成函数原型和函数框架,开发人员只要添加函数体代码,可以避免一些常见错误的产生。
程序中类响应 windows消息的优先顺序:取决于消息传递的顺序。对于一般的 Windows 消息,由产生这些消息的对象响应,如窗口的创建 WM_CREATE, 窗口的移动 WM_MOVE ,由产生消息的窗口类响应,如果找不到相应的处理函数,则将消息传递到其父类,一般情况都有缺省的响应函数;对于命令消息 WM_COMMAND ,可由拥有菜单资源的所有类来处理,传递的顺序是视图类、文档类、文档模板类、框架类、应用程序类,即这些类处理命令消息的优先顺序。详见第 2 章。
视图类的定义文件 (.cpp) 中必须有消息映射定义:BEIGN_MESSAGE_MAP(CMyView,CView) // 消息映射表定义
ON_WM_LBUTTONDOWN() // 函数名为 OnLButtonDown … // 其它消息映射入口END_MESSAGE_MAP()以及对应的消息响应函数定义:void CMyView::OnLButtonDown(UINT nFlags,CPoint point){ … // 处理消息的操作代码,程序中具体实现} 返回
将 CMyView 的基类 CView 也包含进去,当在派生类中没找到处理函数时,允许在基类中继续查找。
注
例如,当用户在视图窗口中按下鼠标左键时, Windows 发送 WM_LBUTTONDOWN 消息到应用程序,假设视图类需要响应这些消息,在类的头文件 (.h) 中有函数声明和消息映射表的声明:Class CMyView: public CView{ …afx_msg void OnLButtonDown(UINT nFlags,CPoint point); // 函数原型声明DECLARE_MESSAGE_MAP() // 消息映射表声明…} 返回
afx_msg 标识消息映射函数,所有由 ClassWizard 生成的消息处理函数原型说明前都有这一标识。
注
3.2.3. 文档 /视图结构 1. 文档类 在基于文档 /视图结构的应用程序中, CDocument 的派生对象负责读入、保存和管理应用程序的数据。每个文档类可能与多个视图类关联,而每个视图包含一个指向与其相关的文档的指针。 文档类允许向其中增加数据成员,应用程序的数据可以是任何类型的,或者是任何自定义的数据结构,如结构体,类或链表等。
3.2.3. 文档 /视图结构 文档类的常用成员函数 (带√的表示是可重定义函数 )
成员函数 说明GetFirstViewPosition 返回一个第一个视图的位置值GetNextView 返回指向相关视图队列中下一个视图的指针GetPathName 取得文档的文件名和路径,若文档未命名则返回空串GetTitle 取得文档的标题,若文档未命名则返回空串IsModified 若文档包含未保存数据,则返回非 0,否则返回 0SetModifiedFlag 设置或清除文档的修改标志
表 3.4
成员函数 说明UpdateAllViews 文档对应多个视图时,更新所有视图,它是通过调用每个视图类的 OnUpdate 函数来更新所有与本文档关联的视图√OnNewDocument 当创建一个新文档时由应用程序框架调用。重定义这个函数实现在新文档创建之前初始化文档对象,如为指针申请内存空间√OnOpenDocument 当从磁盘读入文档时由应用程序框架调用。重定义这个函数实现在新文档读入之前初始化文档的其它非序列化数据成员√DeleteContents 由应用程序框架调用,以删除文档内容。重定义这个函数在文档关闭之前释放文档所占用的内存和资源√Serialize 由应用程序框架调用对文档进行序列化,重定义这个函数实现特定文档的序列化,以保存或读入文档数据
2. 视图类 视图类对象代表了应用程序的窗口客户区,是文档信息的一个显示窗口 ( 视口 ) ,它同时也是用户与程序之间信息交互的桥梁,用户对数据的编辑和修改需要通过键盘或鼠标进行,这些消息都由视图类接受后再反映到文档类。
视图类的派生关系: CWnd
CViewCCtrlView
CListViewCEditViewCRichEditViewCTreeView
CScrollViewCFormView
CDaoRecordView
CDaoRecordView
图 3.5
CView类提供了向视图或打印机进行输出的基本框架,决定了视图的基本特性,派生的视图类可以重载基类的函数,或增加新的函数以决定特定视图的特性。CView的派生类可以多种方式显示文档类的信息,每个视图类根据显示的方式各自定义其显示代码。 MFC 提供了一系列这样的类,如CTreeView 树型视图CListView 列表型视图CFormView 显示基于对话框模板的视图 CScrollView 带滚动条的视图。
1) 视图类实现特定视图的特性的虚函数√GetDocument 返回指向相关联文档对象的指针√OnDraw 支持打印、打印预览和在屏幕上显示,由 OnPaint(重画 ), OnPrint(打印 )调用√OnInitialUpdate 当视图第一次与文档关联时被调用,重定义这个函数对重新读入或新创建文档的视图进行初始化√OnUpdate 当文档内容发生变化,视图需要更新时被调用,更新整个视图。重定义这个函数可以实现只更新无效区域
表 3.5
CEx3_1Doc* pDoc = GetDocument();CEx3_1Doc* pDoc = GetDocument();通过 GetDocument获得与视图关联的文档类指针,通过指针 pDoc访问文档类的数据成员和函数成员,在视图(窗口)中显示文档对象,当用户通过视图改变文档数据时,可设置自动更新文档内容。文档 /视图结构已经把应用程序所需要的数据处理与显示(虚函数 )的框架构造好,程序设计人员只需要将具体应用的数据添加到文档类,并根据数据类型在视图类的虚函数 OnDraw() 中完成数据的显示。
2) 视图类的成员函数访问与其关联的文档类的方式
3. 文档模板类 CDocTemplate 类将框架、视图、文档对象及应用 程序的资源捆绑在一起。应用程序每打开一个文件,至少创建一个文档对象、一个视图对象和一个框架窗口,而文档模板对象负责管理所有这些对象,并将各种相关资源与之联系在一起。在大多数情况下,程序不需要对这个类进行修改。对于 SDI 应用,使用 CSingleDocTemplate 文档模板。对于 MDI 应用,使用 CMultiDocTemplate 文档模板。 MDI 允许有多个文档模板,将不同文档 -视图类联系在一起,实现不同文档视图显示关系。
4. 应用程序创建过程1) 使用 AppWzard ,根据实际需要,选择创建合适的应用程序框架 (是否使用文档 / 视图结构,使用
SDI或MDI 等 )2) 在文档类中添加程序所处理的数据,作为文档类数据成员;3) 在视图类中取得文档数据,以合适的方式在窗口中显示出来
5. 应用实例 例 3_1: 创建一个包含文档 /视图结构单文档界面应用的实例,为文档类添加一个字符串数据,在视图窗口显示出来。步骤如下:(1). 在 File菜单中选择 New,如图 3.6,在 Project页面选择 MFC AppWizard(exe), 在 Project name 中输入 Ex3_1,并选择合适的路径按 OK 按钮。
(2). 在 AppWizard Step 1 中,如图 3.7,选择 Single Document 单 选按钮,并选中 Document/ View architechure Support复选框, 表示使用文档 /视图结构,按 Finish 按钮。显示 New Project Information信息框,显示新创建的工程的有关信息按 OK 按钮,这样工程创建完毕。
图3.6 New Project对话框
图3.7 AppWizard Step1
在 VC集成开发环境中看到,新工程生成了五个类,如图3.8在 class view 页面上可看到这些类。 CAboutDlg 派生自 CDialog ,一个 About 对话框。 CEx3_1App 派生自 CWinApp ,其全局对象 theApp 代表应用程序本身。 CEx3_1Doc 派生自 CDocument ,文档类。 CEx3_1View 派生自 CView,视图类。 CMainFrame 派生自 CFrameWnd,程序框架类。
文档、视图及应用程序类名都以工程名开头,后接对应的类型名,这有助于在程序中有多个文档视图类时区分主视图与其它视图,区分主文档与其它文档类。应用程序向导 AppWizard为每个类生成了一个头文件和一个 .cpp 文件,还自动生成了 stdafx.h和 stdafx.cpp ,图3.9,AppWizard 还生成了缺省的用户界面资源文件,在 Resource view 页面显示。
图3.8 ClassView 图3.9 FileView
(3). 为文档类增加数据成员。在编辑窗口打开文档类的头文件 Ex3_1Doc.h,手工添加一个字符数组成员 m_StrName ;或在 Workspace 的 ClassView 页面,右击 CEx3_1Doc 类,在弹出式菜单中选择 Add Member Variables,在对话框中输入变量类型和名称。在文档类构造函数中对新增加的变量进行初始化。(4). 显示字符串 .在 CEx3_1View的 OnDraw() 函数显示字符串。
图 3.10 例 3.1第一步主界面
3.3. 用户界面资源的应用 资源是 windows应用程序的一种特殊数据,包括菜单、加速键、光标、位图、图标、对话框、字符串、 工具栏、状态栏等,资源一般放在 exe 或 DLL 文件中。当 windows 将程序装入内存时,一般不将资源同时装入,而是当需要某个资源时才将相应资源装入。 资源定义在资源脚本( resource scirpt )文件中。 以 .rc为文件扩展名,文件名与工程名相同。资源脚本文件中定义了各种资源的相关数据, Windows通过使用不同的资源标识符前缀来区分不同资源类型 ( 如表3.6所示),这样做虽然不是必须的,但遵循这样的规则 有利于提高程序的通用性和可读性。
表 3.6 资源类型及其标示 资源标识前缀IDM_ID_IDI_IDB_IDD_IDR_IDC_IDS_
资源类型菜单项 ID菜单命令 ID图标 ID位图 ID对话框 ID菜单栏、工具栏、加速键控件 ID字符串
返回
Visual C++ 6.0 中提供了资源编辑器,对资源进行可视化编辑,并自动生成资源脚本文件;脚本文件可用文本编辑器进行编辑,但其可读性较差,容易出错,一般情况下尽量不要进行编辑。用户定义的资源标识符号常量定义在 resource.
h 文件中。
3.3. 用户界面资源的应用
3.3.1.菜单 分类 : 顶层菜单 : 一般位于应用程序主窗口或顶层窗口 弹出式菜单 : 选择某个菜单项后弹出的子菜单
1.菜单的编辑 如果是 AppWizard 产生的程序,系统已为框架创建了一个标准的主菜单,包括文件、编辑、视图、窗口帮助等标准菜单,定义了各菜单项的 ID及相应的命令处理函数。在 Resource View 页面,打开工程资源,选择 Menu ,然后双击主菜单资源 IDR_MAINFRAME 标示,可打开菜单编辑器。
添加新的菜单资源选择 Insert Resource菜单项,在 Insert Resource 对话框中,选择 Menu资源类型,单击 New 按钮,缺省新菜单名为 IDR_MENU1 。a) 增加一个顶层菜单 在菜单属性对话框中,选中 Pop-up选项,可以定义一个顶层菜单,在 Caption编辑框中输入菜单名,菜单名即显示在菜单上。若在菜单名的某个字符前加一个 &符号,则菜单名该字符下显示一个下画线,可用 Alt加该字符作为选择菜单的快捷方式。对中文菜单“文件”,如希望用 Alt-F 作为键盘操作方式,可写成“文件 (&F)”。 在定义好的菜单后面,仍预留了一个空白菜单,可拖动该空白菜单到任意位置增加新的菜单。
b) 增加一个子菜单 若在定义菜单项时选中 Pop-up选项,则在该菜单项右边出现一个三角形符号,同时右边出现一个新的菜单,再定义这个菜单。c) 定义菜单项 双击空白菜单项,打开菜单项属性对话框 ,如图 3.11,在 General页面上输入 Caption菜单项名称及其 ID值,在 Prompt编辑框中的字符串是选择该菜单项时状态栏上显示的内容,在 \n 后面的内容为当光标移至工具栏上与其 ID值相同的图标上时状态栏显示的内容。
在空白菜单项被定义后,该菜单的最下面又出现一个空白菜单项,拖动该空白菜单项可在已定义的菜单内任何位置插入新菜单项或新子菜单。
d) 创建菜单加速键a. 在 Caption编辑框中,在菜单项名后,在字符 \t 后,加上快捷键组合,如图 3.11所示, Ctrl+O 表示打开文件操,快捷键组合会出现在菜单项名右端,目的是给用户一个提示;
b. 在 Workspace 的 ResourceView 页面上,找到加速键 Accelerator资源, AppWizard生成的缺省加速键资源名为 IDR_MAINFRAME 。双击它打开加速键表,在已有加速键表后面,有一个空白行,双击它打开加速键属性对话框,如图3.12,选择某个菜单项 ID ,在 Key列表框选择某个虚拟键作为菜单命令的快捷方式,或选择 Next Key Typed 按钮,此后键盘输入的按键组合即为菜单命令加速键。这一步才是快捷键的定义过程。
图 3.11 菜单项设置
返回 定义菜单项返回创建菜单加速键
选项 含义ID 表示该菜单项的 ID值
Caption 表示该菜单项显示的文本Separator 表示该菜单项是一条分隔线Checked 表示该菜单项被选中显示一个标志Pop-up 选择该菜单项显示一个子菜单
选项 含义Grayed 该菜单项变灰显示,被禁止Inactive 该菜单项不被激活
Help Help菜单,通常放在菜单的最右端Break 选中该菜单项后就退出其所在菜单
Prompt 光标移至该菜单项状态栏显示的文本
定义 Ctrl+O 为 ID_FILE_OPEN 的快捷方式,在 ID编辑框中输入 ID_FILE_OPEN ,在 Key中键入 O, 然后选中 Ctrl复选框即可定义了菜单项的快捷方式。
图3.12 快捷键设置 注意 :(1). 不要对不同菜单命令使用相同的加速键组合,以免产生二意性;(2). 加速键命令必须有菜单命令与之对应,加速键不应该是某个命令唯一启动方式。
MFC 程序可以处理两种菜单消息2.菜单消息
WM_COMMAND 为菜单命令消息,当选择菜单项、工具栏按钮或加速键之一会发出菜单命令消息, MFC依据一定的消息传递途径将消息传递给相应的处理函数进行处理,如果在消息传递途径的消息映射表中找不到相应的处理函数,该菜单项或按钮会被禁止使用。
UPDATE_COMMAND_UI是菜单更新消息,处理菜单状态的动态变化。如希望根据程序的执行状态,激活或禁用某些菜单项,设置选中标记,或更改菜单项文字等,就要定义这一消息处理函数。这一消息在 Message Map 中定义如下:ON_UPDATE_COMMAND_UI(<命令 ID>,<响应函数 >)例如:ON_UPDATE_COMMAND_UI(ID_EDIT_COPY,OnUpdateEditCopy)在某个菜单显示前,框架将发送菜单内所有菜单项的更新命令,有更新处理函数的菜单项,调用其处理函数更新菜单显示方式;没有更新处理函数的菜单项,查找其命令处理函数,若仍然没有,则将菜单项禁用。
3. 菜单消息的处理 定义好菜单项及其 ID后,为菜单命令添加响应函数,实现方法是借助 ClassWizard,在需要响应菜单命令的类的消息映射表中增加一个入口,即定义一个命令消息 ON_COMMAND 宏。 选择 View|ClassWizard ,打开 ClassWizard 对话框,在 Message Map页面,图3.14。在 Class Name 列表框中选择要响应菜单命令的类名,在 Object IDs 列表框中选择菜单 ID ,在 Message 列表框中选择 COMMAND 或 UPDATE_COMMAND_UI ,然后按 Add Function 按钮,弹出 AddMember Function 对话框,可以修改函数名或接受缺省函数名,按 OK 结束这个对话框,然后在 ClassWizard 对话框中按 Edit Code按钮,直接跳至新增函数的定义处。
图3.14 ClassWizard 添加消息映射关系
3.3.2 工具栏 工具栏是一个包含一个或多个命令按钮的窗口,一般情况下附着在窗口客户区上方菜单栏下面,也可作为一个浮动的小窗口。工具栏为菜单命令提供可视化的快捷操作方式,模拟大部分的菜单行为。
1.编辑工具栏在 Workspace 的 ResourceView 页面,点击 Toolbar 前的+号可以看到缺省的工具栏资源IDR_MAINFRAME ,双击它打开可视化工具栏编辑器。工具栏资源由一组按钮组成,每个按钮是一个 16色位图图标,工具栏编辑器提供一个简单的位图编辑器 ,点击某个图标即可编辑它。用鼠标拖动图标:改变它在工具栏中的位置用鼠标拖出工具栏外:删除图标
双击位图打开 Toolbar Button Properties对话框 ,图 3.15, 在 ID 列表框中输入命令 ID 。列表框中列出了当前工程中已存在的 ID,可为已有命令提供可视化的快捷操作方式。
图 3.15 工具栏图标编辑
2. 工具栏命令处理 如果工具栏按钮与某个菜单项有相同的 ID ,则它们共享一个命令响应函数,不需要另外定义命令响应函数。具有相同 ID 的菜单项和工具栏按钮还共享提示字符串,它们的显示状态也同步变化。如果与工具栏按钮对应的菜单项定义了更新函数,工具栏按钮也会同步实现状态的动态变化。
3.3.3 状态栏 状态栏是出现在应用程序窗口底部的显示程序运行状况的窗口,当用户选择了菜单项或某个工具栏按钮时,状态栏显示它们的提示字符串。AppWizard为应用程序添加缺省的状态栏,为 CMainFrame类增加 CStatusBar 类的数据成员 m_wndStatusBar ,在 MainFrame.cpp 文件中添加提示符数组,作为状态栏创建时的参数。static UINT indicators[] = { ID_SEPARATOR, // 状态栏指示器 ID_INDICATOR_CAPS, //显示 Caps Lock键状态 ID_INDICATOR_NUM, //显示 Num Lock键状态ID_INDICATOR_SCRL, //显示 Scroll Lock 键状态}; // 可以向数组中增加 ID 以增加状态栏上显示栏。
3.3.3 状态栏
图 3.16 例 3.1主界面
3.4. 对话框的应用 3.4.1 对话框的工作方式、种类和创建方法 1. 对话框在应用程序中的工作方式
对话框是一个读入或写出数据的图形界面对象,由集成开发环境中的对话框模板提供,可以向模板添加各种控件 (如按钮,编辑框,单选复选按钮等 ),构成程序的对话框资源; 应用程序要使用对话框还需定义一个 CDialog派生类的对话框类与资源相连接,并在这个对话框类中定义一些成员变量与对话框中的控件相对应,提供对话框控件的缺省数据值;
程序运行时需要用到对话框时依据对话框资源激活对话框,接收用户输入后通过成员函数更新文档类数据成员,如图 3.17 示。
图 3.17 对话框与文档的数据交换
构造对话框并更新文档数据
数据交换对话框模板 controlspublic:data members
文档类:application datas
对话框类对象
2. 对话框的种类 Windows有两种对话框,模态对话框和非模态对话框,构造两种对话框的大多数工作都相同,只是两者的显示和数据处理有些不同。模态对话框 (Modal Dialog Boxes)工作时使其父类窗口无效,直到对话框结束。也就是说,该对话框打开后 , 程序等待用户输入,在关闭对话框后才执行其它任务。 非模态对话框 ( Modeless Dialog Boxes) 在打开期间,允许用户切换到程序其它部分,不一定要关闭对话框。 WinWord中的 Find and Replace 对话框是一个典型的非模态对话框。
3. 对话框的创建与编辑 a) 对话框的创建 选择 Insert|Resource... ,在 Insert Resource 对话框中选择 Dialog 类型,按 New 按钮,打开对话框编辑器,缺省情况下,对话框模板提供了 OK和 Cancel两个按钮,同时打开了对话框工具栏和控件工具栏,如下图。
图 3.18 对话框模板及编辑工具
在对话框模板内右击鼠标,在弹出的菜单中选择 Properties,打开 Dialog Property对话框。
图 3.19 对话框属性对话框
b)控件属性 将控件工具栏中的任一种控件拖至对话框模板中,在控件上右击鼠标,选择 Properties打开控件属性对话框,如图3.20。各种控件属性对话框基本相似,在 ID框中输入标识控件的唯一 ID值,在 Caption 中输入控件上显示的字符 (Edit控件没有这一属性 ) , Visible 表示该控件在对话框创建时是否显示, Group 用来控制控件分组, Disabled使控件不可用, Tab stop 表示是否用 Tab键选择控件。
图3.20 控件属性设置对话框
c)控制 Tab顺序 当打开对话框编辑器后,集成开发环境的菜单上多了一个 Layout菜单,其中主要是对话框工具栏上显示的一些命令,其中有一个命令 Tab Order ,定义当用户用 Tab键移动输入焦点时各控件接受焦点的顺序。如图3.21所示。d) 分组控制 VC 提供了根据控件功能将控件进行分组的工具,使用户更易于理解对话框的结构。在分组框中使用最多的是单选按钮 (radio buttons) ,一组单选按钮允许用户在组内多个相互排他的选项内选择一项。
图3.21 设置Tab Order
e)组合框 Combo Box 将组合框拖至对话框中后,按向下箭头,显示运行时下拉组合框的大小,调整到合适大小。组合框的属性对话框比其它控件多一个 Data页面,用于输入组合框中的数据项,每一行是一项,输完一项按 Ctrl+Enter键换行。 f) 列表框 List Box
在对话框编辑器中只能设置列表框大小、位置和 ID,数据的初始化需要通过编程实现。一般在拥有列表框控件的对话框的 OnInitDialog 函数中,调用 CListBox::AddString( 无序 )或 CListBox::InsertString( 有序 )插入字符串。
g)测试对话框 当所有控件都布置好并定义属性后,可以选择对话框工具栏上的测试按钮,测试对话框工作情况,必要的话做适当调整。
3.4.2 将对话框与程序连结 1. 对话框类及数据成员的定义
a) 创建对话框类 对话框资源建立好后,在对话框中右击鼠标,选择 ClassWizard 菜单项,会出现提示框,说明新建的对话框资源是新资源,是否需要建立一个新类或选择一个已存在的类。 若选择新类,出现图 3.23示的 New class 对话框 。 选定后,在 WorkSpace 中可看到新增加的类,自动生成的 "StudentDlg.h" 头文件和 StudentDlg.cpp 源文件。
图 3.23 新建类对话框
b)增加数据成员 要使对话框能与程序通讯,还需给对话框类增加数据成员,以保存各控件的初始值,并从控件读取数据。与控件对应的数据成员可以是变量或控件类型,如表 3.9所示。
分类 变量成员 控件成员用途 用于控件初始化或保存控件值,成员变量可以是任何数据类型,如编辑框的值可以是整型或字符串,复选框的值是 BOOL 类型
控件成员变量是相应控件类的对象,可以调用控件类的成员函数,如列表框,定义一个 CListBox类型变量,可以通过控件使用类的成员函数以初始化列表框说明 一个控件可以定义其中一种也可定义两种表 3.9 对话框与控件对应的变量或控件数据成员
在 ClassWizard 的 Member Variables 页面,选择对话框类,在 Control IDs中列出了对话框中所有控件 ID,如图 3.24 ,选择某个 ID ,按 Add Variable…按钮,弹出 Add Member Variable 对话框,可输入成员名,在 Category 中选择值 (value) 变量或控件 (controls) 类型,在 Variable type 列表框中,若是 Value 类,选择变量数据类型,若是 Controls,选择控件类,按 OK,成员添加成功。设计不同的菜单命令响应函数,可以用对话框成员变量更新文档数据;也可以用文档数据为对话框数据成员赋值,再显示对话框
图 3.24 为对话框添加数据成员
2. 对话框的显示 模态对话框 调用其 DoModal() 函数显示, 按 OK或 Cancel按钮关闭对话框非模态对话框 调用 Create() 函数创建并显示对话框 用 DestroyWindow关闭对话框。
3. 为控件添加或修改消息响应函数 打开 ClassWizard,在 Message Map页面,可以为控件添加响应函数。如图 3.26 。
图 3.26 为控件添加响应函数
图 3.27 例 3.1 新主界面
3.5. 基于对话框的应用程序设计 AppWizard 提供了基于对话框 (Dialog based) 的应用程序框架 ,程序可以简单到只有 CWinApp派生类和一个对话框类,对话框就是程序框架,负责数据输入输出和存储,以及消息处理;对话框可以有最小化按钮,甚至可以连接一个菜单。 基于对话框的应用程序结构简单,只要正确选择控件及定义成员变量,设计正确的算法。适合开发数据不多,功能简单的程序。
3.6. 文本和图形输出 Windows设备描述表 DC(device contexts) ,是对各种不同设备的抽象,应用程序只要向 DC输出,由 DC负责处理不同设备的差异。 DC 是一个数据结构,描述了图形设备接口 GDI 进行输出所需要的信息,包括颜色,填充方式,画笔宽度等。在 Windows中, DC允许应用程序使用输出设备, DC在应用程序、设备驱动程序、与输出设备之间建立联系,并提供绘图信息。 在 MFC 中, CDC 类封装了设备描述表 DC,该类的核心是 m_hDC ,代表一个指向窗口 DC的句柄。
1.CDC 类及其派生类 CDC 类派生自 CObject ,它具有CObject 类的共同特性,如可以动态创建,可进行序列化操作。 CDC派生类 说明CClientDC 代表窗口客户区CPaintDC 代表客户区的无效区域 (需要重画的区域 ),如被对话框遮挡的部分CWindowsDC 窗口区 (包括客户区和非客户区 )
CMetafileDC 面向备忘文件的 DC,为重绘图记录一系列绘图命令
(a). 设置颜色 windows使用一个 32位无符号整型值来表示图形和文本的颜色,其数据类型为 COLORREF ,它包含 4个字节,第一字节未使用,不要改变其值;后面三个字节分别表示红、绿、蓝三种基本色的成分,每一种颜色用一个 0-255 之间的数字表示。COLORREF
unused Blue Green Red
设备描述表最常见的使用是在视图类的 OnDraw 函数中进行图形和文本输出 2.CDC 类的使用
宏 RGB 定义颜色值宏 RGB 定义一个颜色的三种基本色成分,返回 COLORR
EF 值GetRValue 返回一个 COLORREF 中红色分量GetGValue 返回一个 COLORREF 中绿色分量GetBValue 返回一个 COLORREF 中蓝色分量例:COLORREF color = RGB(125,125,125);CBrush brush(color);TRACE(“ The Red component of the brush is %d\n”, G
etRValue(color));……
CDC 的像素操作函数 SetPixel() 和 GetPixel(),使图形设置精确到像素(b). 像素操作
void CExView::OnDraw (CDC *pDC){ pDC->SetPixel( CPoint (100,100), RGB(255,0,0));}
typedef struct tagPOINT { LONG x; LONG y;} POINT;CPoint是MFC 中表示点的类,派生自 POINT 结构,封装了对 CPo
int 和 POINT进行操作的函数,主要是一些运算符函数。
(c). 绘制直线及其它图形CDC 用于图形操作的成员函数
MoveTo 从当前点移至参数点LineTo 从当前点画直线至参数点Polyline 用 CPoint 对象的数组作参数, 将数组中各点依次连直线,Rectangle 画长方形Ellipse 以长方形为界,画一个椭圆Polygon 以数组中点为顶点,画一个 多边形
(d). 文本输出 文本输出包含两部分工作,分别用两组函数实现。设置文本特性 如字体,颜色,对齐方式等输出文本
SetTextColor GetTextColor
控制输出文本的颜色SetBkMode GetBkMo
de控制文本是否透明显示,如果是不透明方式,文本周围用背景色填充
SetBkColor GetBkColor
控制文本背景颜色,当文本是不透明显示时才填充背景SetTextAlign GetText
Align 控制对齐方式
TextOut 用当前选定的字体、颜色和对齐方式,在参数指定位置显示字符串DrawText 在一个指定矩形区域内显示格式化字符串
3. 使用 GDI 对象 GDI 对象类 CGdiObject派生 CObject ,是画笔、画刷、字体、位图等 GDI 对象的基类,其派生类封装了各种绘图工具。GDI对象 用途 默认值
Cpen 画笔类,画直线、曲线和各种图形边框,可改变颜色,线型,线宽 黑实线,宽一个像素CBrush 画刷类,填充各种图形的内部,可改变颜色和填充图案 白色画刷CFont 字体类,改变字体的属性 系统字体CBitmap 位图类,嵌入或操作位图 无CRgn 区域类,操作由椭圆或矩形组合的各种区域,常与 CDC的剪切函数配合使用
无
CPalette 调色板类,封装了Windows调色板,允许创建调色板对象,修改其属性 无
a)GDI 对象的构造调用 GDI 类的构造函数产生一个 GDI 对象 CPen pen1(PS_SOLID,0,RGB(0,255,0)); 或: CPen pen1; pen1.CreatePen(PS_SOLID,0,RGB(255,0,0));
b)CDC 与 GDI 对象应用程序本身不需要构造 GDI 对象,程序中构造一个 CDC 对象后,系统为其提供了默认的属性和默认的 GDI 对象,但 CDC 封装了对 GDI操作的成员函数,可以修改相关属性,如 SetTextColor() ;也可选择自定义的 GDI 对象。
Windows预定义了标准 GDI 对象,供系统和程序使用,称为备用 GDI 对象 , 可为多个程序同时使用。备用对象包括最常用的字体、画笔、画刷等 , 用函数 CDC:: SelectStockObject选择备用对象,并返回原值。
CPen pen1; //自定义画笔pen1.CreatePen(PS_SOLID,0,RGB(255,0,0));CPen *poldPen = pDC->SelectObject(&pen1);
//选择新画笔,返回并保存原值 CBrush*poldBrush = pDC-> SelectStockObject (NULL_BRUS
H); //选择备用空画刷CRect rect1(0,0,400,400); // 定义矩形pDC->Rectangle(&rect1); //画出空心矩形pDC->SelectObject(poldBrush); //恢复原值
表 3.15 备用 GDI 对象GDI 对象 预定义值字体 ANSI_FIXED_FONT ANSI_VAR_FONT
DEVICE_DEFAULT_FONT
OEM_FIXED_FONT SYSTEM_FONT
画笔 BLACK_PEN WHITE_PEN NULL_PEN
画刷 BLACK_BRUSH DKGRAY_BRUSH GRAY_BRUSHHOLLOW_BRUSH LTGRAY_BRUSH WHITE_BRUSHNULL_BRUSH(透明画刷 )
c) 画笔 CPen 使用 画笔有线型、线宽、颜色三种属性,在构造画笔时需 提供它们的信息。 线型有七种,分别为:实线 PS_SOLID点线 PS_DOT虚线 PS_DASH点画线 PS_DASHDOT双点画线 PS_DASHDOTDOT空画笔 PS_NULL画图形内框 PS_INSIDEFRAME
d) 画刷 CBrush 使用 画刷用于填充区域,有三类画刷。根据画刷类型不同, CBrush有多个构造函数。画刷 参数 创建画刷方法实心画刷 COLORREF值 CBrush brush;
Brush.CreateSolidBrush(RGB(dd,dd,dd))阴影填充画刷
HS_HORIZONTALHS_VERTICALHS_FDIAGONALHS_BDIAGONALHS_CROSSHS_DIAGCROSS
CBrush brush;Brush.CreateHatchBrush(HS_CROSS,RGB(dd,dd,dd))
位图画刷 CBitmap指针 CBitmap bitmap;bitmap.LoadBitmap(IDB_BITMAP1);CBrush brush;Brush.CreatePatternBrush(&bitmap)
e) 区域使用 CRgn 类封装了窗口的 GDI 区域,该区域可以是由一个或多个椭圆形或多边形合成的,通常将 CRgn 的成员函数与 CDC 的剪切成员函数结合使用,实现多种图形显示方式。
面向对象技术有一个术语――永久保存 (Persistence),即保存应用程序的各种信息。 MFC支持两种永久保存:序列化和应用程序状态保存。 序列化 (Serialization) 是指程序的数据保存到文件或从文件读出。应用程序框架提供了完成序列化的大多数结构,程序开发人员只需要完成针对特定应用的数据序列化代码。 应用程序运行状态保存通过注册表完成。
3.7. 文档的保存
3.7.1. 文档序列化 1. 序列化
序列化是将数据从应用程序写入数据文件或从数据文件读入应用程序的过程。序列化在文档 / 视图结构中完成,当用户要保存或读出数据时发生序列化过程,当文档数据发生变化时,序列化会提示用户保存数据。序列化过程从文档类对象序列化开始,文档类可直接序列化普通数据,或序列化对象成员。成员对象各自的类完成自身数据序列化操作。
(3)CArchive 为序列化提供一个上下文。
(1)CObject 类是所有可序列化类的基类,它提供了序列化的协议和功能,只有派生自 CObject 的类才能序列化。 (2)CDocument 类存储应用程序的数据 (数据成员和对象成员 )
MFC 中与序列化相关的类
(4)CFile代表存储在磁盘介质上的文件,直接支持非缓冲文件和二进制文件读写,其派生类支持文本文件和内存文件。
磁盘
Loading Storing
CDocument …..
CFile 1 CFile 2
CArchive 1
CArchive 2
读写过程如下图 :
Serialize
2. 运行时类型信息 从文件读入对象时,程序根据运行时类型信息 (Run-time class information 即 RTTI) 来判断要读入的对象类型,从而动态地构造 (重构 ) 恰当的对象。每创建一个新的对象,都记录必要的类型信息,产生一个类型信息结构,构成运行时类型信息表 (链表 ) ,以便运行时进行类型检查。类型信息由 CRuntimeClass 结构记录。
struct CRuntimeClass {LPCSTR m_lpszClassName; // 类名int m_nObjectSize;UINT m_wSchema; //版本号CObject * (PASCAL* m_pfnCreateObject) ( ); // 函数指针,指向该类的缺省构造函数CRuntimeClass *m_pBaseClass; //指向基类的运行时信息结构static CRuntimeClass *pFirstClass; //链表头指针,静态成员,只有一个 CRuntimeClass *m_pNextClass; //指向链表下一对象 };
CRuntimeClass
作为可序列化类的公共基类, CObject 定义中包含下面的代码: class CObject{public:virtual CRuntimeClass* GetRuntimeClass() const; … //返回当前类的运行时信息结构指针static CRuntimeClass classObject;//静态数据成员,新创建类的信息存储在此};CRuntimeClass* CObject::GetRuntimeClass() const { return &CObject::classObject; }
CRuntimeClass* CRuntimeCalss::pFirstClass = NULL; //初始化存放运行时信息的链表为空
说明: CObject派生类在构造对象时,类型信息被记录在其 classobject 结构成员中,程序中构造的全部对象通过结构体的 m_pNextClass指针构成一个类型信息表链表相互关联。将程序 ( 文档 ) 数据保存至文件时,运行时类型信息也被写入文件;从文件读入对象时,程序根据运行时类型信息表中的记录判断要读入的对象类型,调用其缺省构造函数,动态地重构对象。
3. 与序列化相关的命令选项 响应方式 调用的虚函数
SDI MDI
File|New 1.检查当前文档是否被修改,若是,提示用户保存当前文档2.清空旧文档3.创建新文档
CDocument::OnNewDocument重定义这个函数进行文档初始化,如动态申请对象CDocument::DeleteContents重定义这个函数删除动态分配的对象
创建新文档及视图CDocument::OnNewDocumentCDocument::DeleteContents
File|Open
1.检查当前文档是否被修改,若是,提示用户保存当前文档2.显示打开文件对话框,接受用户输入3. 清空旧文档,反序列化读入新文档
CDocument::OnOpenDocument在这个函数中进行文档初始化CDocument::DeleteContents重定义这个函数删除动态分配的对象CDocument::Serialize读入文档数据
创建新文档及视图CDocument::OnNewDocumentCDocument::OnOpenDocumentCDocument::Serial-ize读入文档数据
File|Save 保存文档数据和对象 CDocument::Serialize保存数据 CDocument::Serialize
File|Colse
1.检查当前文档是否被修改,若是,提示用户保存当前文档2.清空文档内容
因为 SDI有且只有一个文档,无此命令 CDocument::IsModifiedCDocument::DeleteContents文档类的析构函数清除文档对象
4. 文档类的序列化 如果创建的应用程序不使用数据库, AppWizard为文档类自动生成 Serialize 函数的框架,由开发人员完成具体的序列化代码。 void CMyDoc::Serialize ( CArchive & ar) {if(ar.IsStoring( )) {//TODO: add storing code here }else {//TODO: add loading code here }}
a)将类的基类定义为 CObject 或其派生类; b) 在类的声明中定义一个缺省构造函数 ( 不带任何参数 );
5. 创建一个可序列化类
d) 在类的定义文件 (cpp 文件 ) 中,添加 IMPLEMENT_SERIAL 宏,它需要三个参数,分别是需要序列化的类名,其基类名及版本号 ;
e)重定义 Serialize 函数,其参数是一个 CArchive 对象的引用。
c) 在类的声明中,添加 DECLARE_SERIAL 宏,以类名为 唯一参数;
只有派生自 CObject且重载了 Serialize 函数的类才能实现序列化, MFC 定义了两个宏支持序列化,它们是 DECLARE_SERIAL 和 IMPLEMENT_SERIAL
6. 类的序列化当数据为文档类的对象成员时// Ex3_1Doc.hclass CEx3_1Doc : public CDocument {
CStudent m_Student; // 对象成员 … };// Ex3_1Doc.cppvoid CEx3_1Doc::Serialize(CArchive& ar) {
m_Student.Serialize(ar); // 调用对象的序列化函数}
有了可序列化类,就可作为文档的数据参与序列化 .
若数据是指向对象的指针,参看下例。
class CEx3_1Doc : public CDocument { CStudent *m_pStudent;…};
CEx3_1Doc::CEx3_1Doc():m_pStudent(0) { } // 对象指针初始化为 0
void CEx3_1Doc::Serialize(CArchive& ar) { if (ar.IsStoring()) { ar<< m_pStudent; } else { ar>> m_pStudent; }} 当然还要修改文档类的 OnNewDocment 和
DeleteContents 函数。
BOOL CEx3_1Doc::OnNewDocument( ) { if (!CDocument::OnNewDocument())
return FALSE; m_pStudent = new CStudent; // 构造对象return TRUE;}void CEx3_1Doc::DeleteContents() {delete m_pStudent; //删除对象m_pStudent = 0;CDocument::DeleteContents( );}
调用基类的同名函数完成其它功能
返回注
7. 文件对话框中文件类型确定 在 Word字处理软件中,打开文件对话框,会显示缺省的文件类型 .doc 。使用 MFC 开发的应用程序,可以确定其缺省文件类型,通过编辑字符串资源实现。 在 WorkSpace 中选择 Resource View ,打开 String Table资源,选择 ID 为 IDR_MAINFRAME 的字符串,双击它,打开 String Properties 对话框,在 Caption编辑框中显示下列信息:Ex3_1\n\nEx3_1\n\n\nEx31.Document\nEx3_1 Document每个字符串由 \n 分隔, \n 后无字符表示字符串为空。 七个字符串的含义…
七个字符串含义顺序如下:Ex3_1 应用程序主窗口标题栏显示内容,仅 SDI 需要\n NULL 文档名,作为新建文档的缺省名,如 Ex3_11 ,后一个 1 为产生的序列号。此串为空,使新文档窗口标题栏显示”无标题”\nEx3_1 只与 MDI 有关,有多个文档模板时的文档模板名\n NULL 显示在文件对话框文件类型列表框中的信息如” 学生信息文件 (*.stu)”\n NULL 缺省的文件后缀 , 如“ *.stu”,上两项现在未定义\nEx31.Document 注册表中使用的文件类型子项名\nEx3_1 Document 注册表中使用的文件类型名 其中第四、五个字符串与文件类型有关。例 Ex3_1 中对字符串资源修改为行尾的字符串后,打开文件对话框会自动显示文件类型 *.stu 。 返回
3.7.2.注册表 1. 注册表数据库的组织 注册表数据库是一个有关配置信息的层状数据库,层状结构的根部是预定义的原始项 (Predefined Key) ,提供注册表的入口 (Entry),这些原始项包含项 (Key)和子项 (Subkeys) ,子项还可包含子项或值 (Value) 。
程序的运行状态永久保存在注册表中。(这部分内容作为自学,不作为课堂内容。)
2. 注册表编辑器 注册表编辑器提供一个查看和编辑注册表的可视化界面。运行 RegEdit.exe, 打开注册表编辑器。警告 :错误地编辑注册表可能会严重损坏系统 ,备份数据 .
3. 注册表入口文件 注册表入口文件是包含注册表数据库数据的文本文件,当编辑注册表时,注册表编辑器使用注册表入口文件中的信息增加或更新注册表,但不能用来从数据库删除信息。
4. 在程序中更新注册表 在程序中更新注册表可以记录诸如窗口大小和位置及用户选项等信息,一般通过 CWinApp 的成员函数完成。在 CWinApp派生类的 InitInstance 函数中包含对 SetRegistryKey函数的调用。
3.8. MDI 应用程序开发 MDI允许在程序框架窗口内打开多个文档子窗口,拥有一个Window菜单,提供子窗口切换、tile、 cascade等功能。
MDI 应用包含下列类CMainFrame MDI 应用的主框架窗口类 CChildFrame MDI子窗口的框架CExp3_3App 派生自 CWinApp ,代表应用程序本身CExp3_3Doc 派生自 CDocumant ,文档类CExp3_3View 派生自 CScrollView ,视图类
CFrameWnd
CMainFrameCMDIChildWndCMDIFrameWndCChildFrame
1. 窗口拆分 窗口可以实现动态拆分和静态拆分动态拆分 :是对一个视图进行分隔,各个分隔窗口内容相同,但各自独立控制视野范围。 AppWizard支持动态拆分。参看例 Ex3_3动态部分.静态拆分 : 窗口个数是固定的,各窗口可以显示不同的视图对象,每个窗口的活动完全独立,有属于自己的水平和垂直滚动条 .静态拆分可修改动态拆分代码实现。参看例 Ex3_3 静态部分
2. 多文档模板应用 a)在 New Project页面上,选择 MFC AppWizard(exe)类型,工程名为Ex3_3; b) 在 AppWizard-Step 1 ,选择 Multiple Documents应用类型,并选中 Document/View architecture support 复选框; Step 2, 3接受缺省值,在 Step 4 中,按 Advanced 按钮,打开 Advanced Options对话框,填写如图3.34示的Document template strings 页面,并在 Window Styles 页面图3.35,选中 Use Split Window 复选框,关闭 Advanced对话框;
例 Ex3_3 MDI 应用,使其支持窗口动态拆分。
图3.34 决定文件类型窗口
图3.35 使用拆分窗口选项
c) Step 5接受缺省值, Step 6 中,确定各类基类时将 CEx3_3View的基类改为 CScrollView,当文档内容超出视图范围时自动实现滚动;按 Finish 按钮,结束 AppWizard。MDI 中每个子窗口都应是可拆分的,子框架包含拆分窗口类对象成员及拆分窗口的操作。为了在视图窗口显示内容,在视图的 OnDraw 函数中添加一个画图操作:void CEx3_3View::OnDraw(CDC* pDC) {CEx3_3Doc* pDoc = GetDocument();ASSERT_VALID(pDoc);pDC->Ellipse(0,0,200,100);}
class CChildFrame : public CMDIChildWnd {….
protected:CSplitterWnd m_wndSplitter; //拆分窗口类对象
public:virtual BOOL OnCreateClient(LPCREATESTRUCT lp
cs, CCreateContext* pContext);}; BOOL CChildFrame::OnCreateClient( LPCREATESTRUCT /*lpcs*/,CCreateContext* pCont
ext) { // 将窗口拆分为两行两列的子窗口return m_wndSplitter.Create( this, 2, 2,
CSize( 10, 10 ), pContext );} 返回
3. 多个文档模板应用 MDI 允许多个文档模板存在,在不同窗口显示不同文档内容。例 Ex3_4 在文档类存储一个数组,三个视图分别以文本、条形图和曲线三种形式显示文档内容。 a) 与例 Ex3_3相同的方式创建工程,在 Step4 的 Advan
ced 对话框中,不选中 Use Split Window ,不使用拆分窗口,文件类型相应修改为 Ex3_4 。b) 为工程增加两个视图类, CBarView和 CCurveView,对应条形图和曲线,它们的基类都是 CView;修改它们的 OnDraw函数。这两个函数代码见实践教材。
工程主视图显示文本,修改其 OnDraw 函数,输出文本:void CEx3_4View::OnDraw(CDC* pDC){ CEx3_4Doc* pDoc = GetDocument ( );// 取文档指针 ASSERT_VALID(pDoc); TEXTMETRIC tm; // 存储字体信息的结构体 int x,y, cy, i; char sz[20]; pDC->GetTextMetrics(&tm); // 获取当前字体信息 cy = tm.tmHeight; pDC->SetTextColor(RGB(255, 0, 0)); // red text for (x=5,y=5,i=0; i<DATANUM; i++,y+=cy) {
wsprintf (sz, “%d”, pDoc->arr[i]); //格式化输出 pDC->TextOut (x,y, sz, lstrlen(sz));}
}
d) 为新文档模板创建资源,可以为新窗口的定义菜单和字符串资源,其中字符串资源尤为重要,因为在新建文档时要选择文档类型,必须为新文档命名。 详细信息…
c) 添加文档模板。在 theApp.InitInstance()中 AppWizard产生主视图文档模板,再添加两个新文档模板,在 Ex3_3.cpp 中包含两个新增视图类的头文件。代码
BOOL CEx3_4App::InitInstance() {… CMultiDocTemplate* pDocTemplate;pDocTemplate = new CMultiDocTemplate(IDR_EX3_4TYPE,RUNTIME_CLASS(CEx3_4Doc),RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CEx3_4View));AddDocTemplate( pDocTemplate);pDocTemplate = new CMultiDocTemplate(IDR_BAR_TYPE, // 要创建新资源RUNTIME_CLASS(CEx3_4Doc),RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CBarView));AddDocTemplate( pDocTemplate);…} 返回
打开字符串编辑器,在字符串表中右击鼠标,在弹出式菜单中选择 New String 命令,编辑如下两个字符串资源:IDR_BAR_TYPE\nBar\nBar\nEx3_4 文件 (*.ex4)\n.ex4\nEx34.Document\nEx3_4 DocumentIDR_CURVE_TYPE\nCurve\nCurve\nEx3_4 文件 (*.ex4)\n.ex4\nEx34.Document\nEx3_4 Document
文档模板名
文件后缀返回注
4. 多文档模板应用分类 例 Ex3_3 只有一个文档模板,但在静态拆分窗口中可显示不同视图,图示。在动态拆分窗口中可显示同一文档的不同部分,图示例 Ex3_4 中多个文档模板使用的同一文档类,以不同方式显示文档内容;图示程序中也可有多个文档类,建立多个文档模板。图示总之, MDI 应用的方式非常灵活,可根据需要选择合适的应用程序框架。对比 SDI 则相对单一:图示
e) 运行程序,由于 CWinApp::OnFileNew 首先被调用,因此出现新建对话框,由用户选择文档类型,如图3.38,以后每次新建文档都会出现此对话框。三种文档各打开一个的运行情况如图3.39,每种文档打开新文档时自动生成序列号,如 Bar1,Bar2, 等。
Summary我们已经学习了 MFC 程序设计的最基本概念,包括: MFC主要类 应用程序框架及文档视图结构 用户界面及对话框编程 图形和文本输出 文档序列化这是MFC 的主要内容,但仅仅是一个起步, MF
C 还有很多重要内容无法在短时间内涉及,有待今后进一步的学习。有了核心内容,今后的学习就是作锦上添花的工作了。
图 3.38 多文档模板应用新建文档提示窗口 返回
图3.39 返回3.1节
在子框架内的拆分窗口中显示不同视图 返回
在子框架内的拆分窗口中显示不同部分 返回
子框架
文档类文档模板 1 文档模板 3视图 1 视图 2 视图 3
资源 3资源 2资源 1
文档模板 2
MDI一个文档对多 视图应用说明
子框架
文档 2视图 1 视图 2 视图 3
资源 3资源 2资源 1
文档模板 2 文档模板 3 文档模板 1
文档 3文档 1
MDI 多文档多视图
单文档应用 SDI
框架( 窗口框架 ) 资源 (决定用户界面的显示 )
文档类(保存数据 )视图类( 显示数据并与用户交流 )
单文档模板 返回