尽管窗口、文档和视图是MFC的基础,但可能也是最不容易理解的部分,因为其概念比传统编程所需要的Windows函数更强一些,因此,须在本章做进一步详细讨论框架窗口、文档和视图的方法和技巧。
6.1框架窗口
分两类:一是应用程序主窗口,另一类是文档窗口。
6.1.1主窗口和文档窗口
主窗口(或称主框架窗口)是应用程序直接放在桌面(DeskTop)上的那个窗
口,每个应用程序只能有一个窗口,主窗口的标题栏上往往显示应用程序的名称。
主窗口类的源文件名是MainFrm.h和MainFrm.cpp,其类名是CMainFrame。
单文档SDI程序主窗口类是从CFrameWnd派生来的。
多文档MDI程序主窗口类是从CMDIFrameWnd派生的。
如果应用程序中还有工具栏(CToolBar)状态栏(CStatusBar),那么在CMainFrame类还含有表示工具栏和状态栏的成员变量m_wndToolBar和m_wndStatusBar,并在CMainFrame的OnCreate函数中进行初始化。
文档窗口对于SDI程序来说,它和主窗口是一致的,即主窗口就是文档窗口;对于MDI程序,文档窗口是主窗口的子窗口。见书244页图6.1所示。文档窗口一般都有相应的可见边框,他的客户区(初了窗口标题栏、边框外的区域)是由相应的视图来构成的,可以说视图是文档窗口内的子窗口。文档窗口时刻跟踪当前处于活动状态的视图的变化,并将用户或系统产生的命令消息传递给当前活动视图。而主窗口负责管理各个用户交互对象(包括菜单、工具栏、状态栏以及加速键)并根据用户操作相应地创建或更新文档窗口及其视图。在MDI应用程序中,MFC
AppWizard创建的文档子窗口类的源代码文件是ChildFrm.h和ChildFrm.cpp,其类名是CChildFrame,它是从CMDIChildWnd派生的。
6.1.2窗口风格的设置
窗口的风格决定了窗口的外观及功能,用户通过风格的设置增加或减少窗口中所包含的功能,这些功能一般都是由系统内部定义的,不需要用户去编程实现。
窗口风格可以通过MFC AppWizard来设置,也可以在主窗口或文档窗口类的
PreCreateWindow函数中修改CREATESTRUCT结构,或是可以调用CWnd类的
成员函数ModifyStyle和ModifyStyleEx来更改。
1、窗口风格
通常以WS_为前缀和扩展以WS_EX_为前缀两种形式;这两种形式的窗口风格可在函数CWnd::Create(只能指定窗口的一般风格)或CWnd::CreateEx(可同时支持以上两种风格),对于控件和对话框这样的窗口来说,它们的窗口风格可直接通过其属性对话框来设置。常见的一般窗口风格如下所示:(书245页表6.1)
WS_BORDER 窗口含有边框
WS_CAPTION 窗口含有标题栏(它意味着还具有WS_BORDER风格)
但它不能和WS_DLGFRAME组合
WS_CHILD 创建子窗口,它不能和WS_POPUP组合
WS_CLIPCHILDREN 在父窗口范围内裁剪子窗口,它通常在父窗口创建时指定
WS_CLIPSIBLINGS 裁剪相邻子窗口,也就是说,具有此风格的子窗口和其他
子窗口重叠的部分被裁剪,它只和WS_CHILD组合
WS_DISABLED 窗口最初时是禁用的
WS_DLGFRAME 窗口含有双边框,但没有标题
WS_GROUP 此风格被控件组中第1个控件窗口指定。用户可在控件组
的第1个和最后1个控件中用方向键来选择
WS_HSCROLL 窗口最初时处于最大化
WS_MAXIMIZEBOX 在窗口的标题栏上含有”最大化”按钮
WS_MINIMIZE 窗口最初处于最小化,他只和WS_OVERLAPPED组和
WS_MINIMIZEBOX 在窗口的标题栏上含有”最小化”按钮
WS_OVERLAPPED 创建覆盖窗口,一个覆盖窗口通常有一个标题和边框
WS_OVERLAPPEDWINDOW 创建一含有WS_OVERLAPPED、WS_CAPTION、
WS_SYSMENU、WS_THICKFRAME、
WS_MINIMIZEBOX和WS_MAXIMIZEBOX
风格的覆盖窗口
WS_POPUP 创建一弹出窗口,它不能和WS_CHILD组合,只能用
CreateWx函数指定
WS_POPUPWINDOW 创建一含有WS_BORDER、WS_POPUP和WS_SYSMENU
风格的弹出窗口。当WS_CAPTION和WS_POPUPWINDOW
风格组合时才能使系统菜单可见。
WS_SYSMENU 窗口的标题栏上含有系统菜单框,它仅用于含有标题的窗口
WS_TABSTOP 用户可以用于TAB键 选择控件组中的下一个控件
WS_THICKFRAME 窗口含有边框,并可调整窗口的大小
WS_VISIBLE 窗口最初是可见的
WS_VSCROLL 窗口含有垂直滚动条
除了这些风格外,框架窗口还有以下3个自己的风格。他们都可以在PreCreateWindow重载函数中指定。
(1)FWS_ADDTOTITLE风格:指定一个文档名添加到框架窗口标题中,如书244页图6.1的“Ex_MDI---Ex_MDI1”的Ex_MDI1是文档名。若单文档应用程序,默认的文档名是”无标题”。
(2)FWS_PREFIXTITLE风格:使得框架窗口标题中的文档名显示在应用程序名之前。例如,若未指定该风格时的窗口标题为”Ex_MDI_Ex_MDI1”,当指定该风格后就变成了”Ex_MDI1_Ex_MDI”。
(3)FWS_SNAPTOBARS风格:用于调整窗口的大小,使它刚好包含了框架窗口中的控制栏(如工具栏)
2、用MFC AppWizard设置
MFC AppWizard有一个Advanced(高级)按钮(在创建单或多文档程序时的第4步中),允许用户指定有关SDI和MDI框架窗口的属性。见书246页图6.2所示为”Advanced Options”对话框的Window Styles页面,其中的含义如下:(246页)
(但在该对话框中,用户只能设定少数几种窗口风格)
Use split window(应用拆分窗口) 选中时,将程序的文档窗口创建成”切分”*(或称
拆分)窗口
Thick frame(厚框) 选中时,设置窗口风格WS_THICKFRAME
Minimize box(最小化框符) 选中时,设置窗口风格WS_MINIMIZEBOX
Maximize box(最大化框符) 选中时,设置窗口风格WS_MAXIMIZEBOX
System menu(系统菜单) 选中时,设置窗口风格WS_SYSMENU
Minimized(最小化) 选中时,设置窗口风格WS_MINIMIZE
Maximized(最大化) 选中时,设置窗口风格WS_MAXIMIZE
3、修改CREATESTRUCT结构
在窗口创建之前,系统自动调用PreCreateWindow虚函数。在MFC AppWizard
创建SDI/MDI应用程序结构时,MFC已为主窗口或文档窗口类自动重载了该虚函数。我们可以在此函数中通过修改CREATESTRUCT结构来设置窗口的绝大多数风格。
例:在SDI程序中,框架窗口默认的风格是WS_OVERLAPPEDWINDOW和FWS_ADDTOTITLE的组合,更改其风格。
(1)建一个单文档的应用程序
(2)在主框架程序MainFrm.cpp中找到PreCreateWindow虚函数,并加代码:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
cs.style&=~WS_MAXIMIZEBOX;//新窗口不带有[最大化]按钮
cs.cy=::GetSystemMetrics(SM_CYSCREEN)/3;
cs.cx=::GetSystemMetrics(SM_CYSCREEN)/3;//将窗口大小设置为1/3屏幕大
//小,并居中。
cs.y=((cs.cy*3)-cs.cy)/2;
cs.x=((cs.cx*3)-cs.cx)/2;
//调用基类的PreCreateWindow(cs)函数
return CFrameWnd::PreCreateWindow(cs);
// if( !CFrameWnd::PreCreateWindow(cs) )
// return FALSE;
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
// return TRUE;
}
(3)编译运行,见出现的窗口是原来的1/3。
(4)建一个多文档MDI应用程序(先运行一下试一试窗口最大化按钮)
(5)在子文档窗口ChildFrm程序中找到PreCreateWindow虚函数,并加代码:
(创建不含有[最大化]按钮的子窗口)
BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
cs.style&=~WS_MAXIMIZEBOX;//创建不含有[最大化]按钮的子窗口
return CFrameWnd::PreCreateWindow(cs);
// if( !CMDIChildWnd::PreCreateWindow(cs) )
// return FALSE;
// return TRUE;
}
结果是:不含有[最大化]按钮的子窗口
代码中,前面有“::”作用域符号的函数是指全局函数。
代码“cs.style &=~WS_MAXIMIZEBOX;”中的“~”是按位取“反”运算符,它将WS_MAXIMIZEBOX的值按位取反后,再和cs.style值按位“与”,其结果是将cs.style值中的WS_MAXIMIZEBOX标志位清零。
4、使用ModifyStyle和ModifyStyleEx
CWnd类中的成员函数ModifyStyle和ModifyStyleEx也可用来更改窗口的风格,其中ModifyStyleEx还可更改窗口的扩展风格。这两个函数具有相同的参数:
BOOL ModifyXXXX(DWORD dwRemove,DWORD dwAdd,UINT nFlags=0);
参数:dwRemove用来指定需要删除的风格
dwAdd 用来指定需要增加的风格
nFlags 表示SetWindowPos的标志,0(默认)表示更改风格的同时不
调用SetWindowPos函数
由于框架窗口在创建时不能直接设定其扩展风格,因此只能通过调用ModifyStyle函数来进行。
例:在多文档(MDI)的子文档窗口增加水平和垂直滚动条(书247页例,248
页图6.3)
(1)用MFC AppWizard创建一个多文档应用程序(或用上个多文档应用程序),
名为:“多文档”
(2)用ClassWizard为子文档窗口类CChildFrame添加OnCreateClient处理消息:
ViewàClassWizardàCChildFrameàOnCreateClientàAdd FunctionàEdit Code
并加如下代码:
BOOL CChildFrame::OnCreateClient(LPCREATESTRUCT lpcs,,CCreateContext* pContext)
{
// TODO: Add your specialized code here and/or call the base class
ModifyStyle(0,WS_VSCROLL,0);//垂直滚动轴
ModifyStyle(0,WS_HSCROLL,0);//水平滚动轴
return CMDIChildWnd::OnCreateClient(lpcs, pContext);
}
(3)编译并运行(书上只有一个滚动轴)
6.1.3窗口状态的改变
MFC AppWizard为每个窗口设置了相应的大小和位置,但总有的默认窗口
不另人满意,这时就需要进行适当的调整。
1、用ShowWindow改变窗口的显示状态
当应用程序运行时,Windows会自动调用应用程序框架内部的WinMain函数,并自动查找该应用程序类的全局变量theApp,然后自动调用用户应用程序类的虚函数InitInstance,该函数会进一步调用相应的函数来完成主窗口的构造和显示工作,如一个单文档应用程序的应用程序文件中的程序名.cpp中的InitInstance()函数中的代码:
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
参数:SW_SHOW是用当前的大小和位置激活并显示窗口。
m_pMainWnd是主框架窗口指针变量,ShowWindow是CWnd类的成员函
数,用于按指定的参数显示窗口,该参数的值如下:
SW_HIDE 隐藏此窗口并将激活状态移交给其他窗口
SW_MINIMIZE 将窗口最小化并激活系统中的顶层窗口
SW_RESTORE 激活并显示窗口。若窗口是最小或最大状态时,则恢复到原来的
大小和位置
SW_SHOW 用当前的大小和位置激活并显示窗口
SW_SHOWMAXIMIZED 激活窗口并使之最大化
SW_SHOWMINIMIZED 激活窗口并使之最小化
SW_SHOWMINNOACTIVE窗口显示成为一个图标并保留其激活状态(即原来是
激活的,仍然是激活)
SW_SHOWNA 用当前状态显示窗口
SW_SHOWNOACTIVATE 用最近的大小和位置状态显示窗口并保留其激活状态
SW_SHOWNORMAL 激活并显示窗口
通过指定ShowWindow函数的参数值可以改变窗口显示状态,例如:将窗口的初始状态设置为”最小化”,可以这样写:
m_pMainWnd->ShowWindow(SW_SHOWMINIMIZED);
m_pMainWnd->UpdateWindow();
由于用户应用程序类继承了基类CWinApp的特性,因此也可在用户应用程序类
CWinApp中使用公有型(public)成员变量m_nCmdShow,通过对其进行赋值,同样能达到效果。
例如:在上个“多文档”项目的应用程序的InitInstance()函数中这样写:
m_nCmdShow=SW_SHOWMAXIMIZED;//激活窗口并使之最大化,书是最小
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
return TRUE;
2、用SetWindowPos或MoveWindow改变窗口的大小和位置
CWnd中的SetWindowPos是一个非常有用的函数,它不仅可以改变窗口的大小、位置,而且还可以改变所有窗口在堆栈排列的次序(Z次序),这个次序是根据它们在屏幕出现的先后来确定的。
BOOL SetWindowPos(const CWnd *pWndInsertAfter,int x,int y,int cx,int cy,UINT
nFlags);
参数:pWndInsertAfter表示窗口对象指针,它可以预定义下列窗口对象的地址:
wndBottom将窗口放置在Z次序中的底层
wndTop将窗口放置在Z次序中的顶层
wndTopMost设置最顶层窗口
wndNoTopMost将窗口放置在所有最顶层的后面,若此窗口不是最顶窗
口,则此标志无效。
x和y表示窗口新的左上角坐标,cx和cy分别表示窗口的宽度和高度,
nFlags表示窗口新的大小和位置方式,如下说明(书250页表6.4)
常用nFlags值及其含义:
SWP_HIDEWINDOW隐藏窗口
SWP_NOACTIVATE 不激活窗口。如该标志没有被指定,则依赖pWndInsertAfter参数
SWP_NOMOVE 不改变当前的窗口位置(忽略x和y参数)
SWP_NOOWNERZORDER不改变父窗口的Z次序
SWP_NOREDRAW 不重新绘制窗口
SWP_NOSIZE 不改变当前的窗口大小(忽略cx和cy参数)
SWP_NOZORDER 不改变当前的窗口Z次序(忽略pWndInsertAfter参数)
SWP_SHOWWINDOW 显示窗口
函数CWnd::MoveWindow也可用来改变窗口的大小和位置,与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表示窗口新的大小和位置
例:用上面两个函数,把主窗口移到屏幕的(100,100)处。
(1)建一个单文档的应用程序
(2)打开:文件名.cpp应用程序,找到BOOL CMyApp::InitInstance()函数,并添下列代码:
m_pMainWnd->SetWindowPos(NULL,100,100,0,0,SWP_NOSIZE|
SWP_NOZORDER);//不改变当前的窗口大小和窗口Z次序,
CRect rcWindow; //见上面说明(书250页表6.4)
m_pMainWnd->GetWindowRect(rcWindow);
m_pMainWnd->MoveWindow(100,100,rcWindow.Width(),
rcWindow.Height(),TRUE);//改变窗口位置
//AfxGetMainWnd()->CenterWindow();//将主框架窗口居中.
//AfxGetMainWnd()->CenterWindow(CWnd::GetDesktopWindow());
//将窗口置于屏幕中央
//return TRUE;
当然,改变窗口的大小和位置的CWnd成员函数还不止以上两个,例如:
CenterWindow(CWND::GetDesktopWindow())函数是使窗口居于父窗口(屏幕)中央。
AfxGetMainWnd()->CenterWindow();//将主框架窗口居中。见上面已加上的两个函数。
6. 2文档模板
用MFC AppWizard创建的单文档(SDI)或多文档(MDI)应用程序均包含应用程序类、文档类、视图类和框架窗口类,这些类是通过文档模板有机地联系在一起的。
6.2.1文档模板类
文档应用程序框架结构是在程序运行一开始构造的。
1、在单文档应用程序的应用程序类InitInstance()函数中,可以看到这样的代码:
BOOL CMyApp::InitInstance()
{
CSingleDocTemplate *pDocTemplate;
pDocTemplate = new CSingleDocTemplate
(
IDR_MAINFRAME, //资源ID
RUNTIME_CLASS(CMyDoc), //(以中文名字命名的项目名的项目)文档类
RUNTIME_CLASS(CMainFrame),// 主框架窗口类
RUNTIME_CLASS(CMyView) //(以中文名字命名的项目名的项目)视图类
);
AddDocTemplate(pDocTemplate);
…..
return TRUE;
}
代码中:
pDocTemplate 是类CSingleDocTemplate的指针对象。
CSingleDocTemplate是一个单文档模板类,他的构造函数中有4个参数,分别表示
菜单和加速键等的资源ID号以及3个由宏RUNTIME_CLASS
指定的运行时类对象。
AddDocTemplate 是类CWinApp的一个成员函数,当调用了该函数后,就建立
了应用程序类、文档类、视图类和主框架类之间的相互联系。
2、在多文档应用程序的应用程序类InitInstance()函数中,同样可以看到这样的代码:
BOOL CMyApp::InitInstance()
{ CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate
(
IDR_MYTYPE, //(以中文名字命名的项目名的项目)资源ID号
RUNTIME_CLASS(CMyDoc),//(以中文名字命名的项目名的项目)文档类
RUNTIME_CLASS(CChildFrame),// MDI文档窗口类
RUNTIME_CLASS(CMyView)//(以中文名字命名的项目名的项目)视图类
);
AddDocTemplate(pDocTemplate);
// create main MDI Frame window创建主框架窗口
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
……
Return TRUE;
}
由于多文档模板是用于建立资源、文档类、视图类和子框架窗口(文档窗口)类之间的联系的,因而多文档的主框架窗口需要额外的代码来创建。代码中:
LoadFrame()是CFrameWnd类成员函数,用于加载与主框架窗口相关的菜单、加速键、图标等资源。
说明:多文档主框架窗口的创建应在多文档模板创建后进行,以便MFC程序框架将多文档模板和多文档主框架窗口建立联系。
应用程序类对象在模板被创建之前就已经存在,但此时文档、视图及框架对象
还没有被创建。程序运行后,程序框架会动态地创建这些对象。这一动态创建的
过程包含了对C++语言的非常复杂的运用。
通过在用户文档类、视图类以及主框架类的定义(.h)及实现(.cpp)过程中
使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏,MFC库可以动
态地创建相应的类对象。
这种称为运行时类的动态创建机制,简化了程序员编程工作,拓展了“类”的
功能,是MFC程序框架结构的重要特性。
6.2.2 文档模板字串资源
在MFC AppWizard创建的应用程序资源中,许多资源标识符都是IDR_MAINFRAME
意味着这些具有同名标识的资源将被框架自动加载到应用程序中。其中String Table
(字符串)资源列表中也有一个IDR_MAINFRAME项,他是用于标识文档类型、标题等内容的,称为“文档模板字串资源”。其内容如下(以单文档“模板”为例)
模板\n\nMy\n\n\nMy.Document\nMy Document
可以看出,IDR_MAINFRAME所标识的字符串被分成了一些以“\n”结尾的子串,这些子串共有7段,每段都有特定的用途,如:(252页表6.5)
文档模板字符串的含义:
IDR_MAINFRAME的子串 串号 用途
模板\n 0 应用程序窗口标题即窗口标题栏:无标题--模板
\n 1 文档根名。对多文档应用程序来说,若在文档
窗口标题上显示“Sheet1”,则其中的Sheet就
是文档根名,若该子串为空,则文档名为默认
的“无标题”
My\n 2 新建文档的类型名。若有多个文档类型,则这
个名称将出现在“新建”对话框中
\n 3 通用对话框的文件过滤器正文
\n 4 通用对话框的文件扩展名
My.Document\n 5 在注册表中登记的文档类型标识
My Document 6 在注册表中登记的文档类型名称
但对于MDI(多文档)来说,上述的字串分别由IDR_MAINFRAME和IDR_MYTYPE
(项目名为:中文名:“字串资源“)2项组成;其中,IDR_MAINFRAME表示窗口标题,而IDR_MYTYPE表示后6项内容。它们的内容如下:
IDR_MAINFRAME: My(中文项目名)
IDR_MYTYPE: \nMy\n\n\nMy.Document\nMy Document
实际上,文档模板字串资源内容既可直接通过字串资源编辑器进行修改,也可以在文档应用程序中创建向导的第4步中,通过“Advanced Options”对话框中的
“Document Template String”页面来指定,如书235页图6.4所示(名字是Ex_SDI)。
图中的数字表示该项的含义与上表中对应串号的含义相同。
6.2.3使用多个文档类型
在MFC AppWizard创建的应用程序中,通常只有一种文档类型。但有时,用户也需要另一种文档类型。例如Visual C++6.0本身既要处理文本文件,也要处理MFC资源,至少要有2种文档类型。
多种文档类型的应用程序中一般有多个文档模板、多个文档类以及与之紧密相联的多个视图类。
当用户选择“文件”菜单的“新建”命令时,应用框架将弹出含有文档类型列表的对话框,允许用户自己选择所需的文档类型。下面是实现在多文档应用应用程序中使用多种文档类型。
例:使用多个文档
(1)用MFC AppWizard创建一个多文档应用程序(Step1第一步就单击[Finish]
即可)
(2)(3)“多个文档类型实例”
(4)\nPicture\nMyDenmo图片\n图片文件(*.bmp)\n.bmp\nMyDemo.Document\n
My Document
(5)在“字符串属性”对话框中将ID设为IDR_OTHERTYPE=130,标题内容设
为:\nTxt\nMyDemo 文本\n 文本文件(*.txt,*.cpp,*.h)\n.txt;*.cpp,*.h\n
MyDemo.Document\n MyDocument
(6)COtherDoc
(7)COtherDoc
(8)pDocTemplate=new CMultiDocTemplate(
IDR_OTHERTYPE,
RUNTIME_CLASS(COtherDoc),//指定新的文档类
RUNTIME_CLASS(CChildFrame),
RUNTIME_CLASS(COtherView));//指定新的视图类
AddDocTemplate(pDocTemplate);
(9) #include "OtherDoc.h"
#include "OtherView.h"
(10)运行结果见书
说明:A、如果在该例程序中再添加图标和菜单资源,并使资源标识设为IDR_OTHERTYPE
则当创建“MyDemo文本”文档类型后,程序会自动使用新的图标和菜单资源。
B、单文档应用程序也可以有多个文档类型,它的实现方法与多文档类似,也是通过添
加文档模板来实现的,只不过每次只能在文档窗口(视图)中显示一个文档。
6.3文档序列化
用户处理的数据往往需要存盘作为永久备份。将文档类中的数据成员变量的值保存在磁盘文件中,或者将存储的文档文件中的数据读取到相应的成员变量中,这个过程称为序列化(Serialize)。
6.3.1文档序列化过程
在使用MFC程序结构进行文档序列化之前,我们先了解对文档不同操作的具体程序运行过程。
1创建空文档(256页有说明)
应用程序类的InitInstance函数在调用了AddDocTemplate函数之后,会通
过CWinApp::ProcessShellCommand间接调用CWinApp的另一个非常有用的成员函数OnFileNew,并依次完成文档各个步骤的创建。…..
2)打开文档
当MFC AppWizard创建应用程序时,它会自动将“文件(File)”选单中的“打开(Open)”命令(ID号为ID_FILE_OPEN)映射到CWinApp的OnFileOpen
成员函数。这一结果可以从应用类(.cpp)的消息入口处得到验证:见书257页
除了使用“文件(File)”à“打开(Open)”选项外,用户也可以选择最近使用过的文件列表来打开相应的文档。在应用程序的运行过程中,系统会记录下4个(默认)最近使用过的文件,并将文件名保存在Windows的注册表中。当每次启动应用程序时,应用程序都会在“文件(File)”选单中显示最近使用过的文件名称。
需要说明的是:MFC为我们重载了Serialize函数,不必使用CFile类就可以完成相应的文档操作。(写磁盘和串口访问这样这样的任务)
例:使用Serialize函数,读取文档中的数据(附加)
(1)打开前面的“多文档”应用程序或重新建一个单或多文档应用程序。
(2)加成员变量
工作区ClassViewà右键对准CMyDocà单击àAdd Member Variableà右键
单击à出现对话框àVariable Type处写:char àVariable Name处写:m_ch[300]
在文档类中加了一个成员变量。
(3)在文档类即多文档Doc.cpp中找到:void CMyDoc::Serialize(CArchive& ar)
函数并加代码:
void CMyDoc::Serialize(CArchive& ar)//CArchive的对象是ar(引用)
{
if (ar.IsStoring())
{
// TODO: add storing code here
}
else
{
// TODO: add loading code here
for(int i=0;i<sizeof(m_ch);i++)
ar>>m_ch[i];
CString str;
str.Format(_T("%s"),m_ch);
AfxMessageBox(str);
}
}
这样,当我们通过选单的“打开(Open)”成功打开(用*.*)一个文件时,将弹出一个对话框,显示出该文件的前300个字符的内容。
3)保存文档(257页)
当MFC AppWizard创建应用程序时,它会自动将“文件(File)”选单中的“保存(Save)”命令与文档类CDocument的OnFileSave函数在内部关联起来,但用户在程序框架中看不到相应的代码。OnFileSave函数还会进一步完成下列工作。
(1) 弹出通用文件“保存”对话框,让用户提供一个文件名
(2) 调用文档对象的CDocument::OnSaveDocument虚函数,接着又自动调用Serialize函数,将CArchive对象的内容保存在文档中。
说明:
A、只有在保存文档之前还没有存过盘(即没有文件名)或读取的文档是“只读”
的,OnFileSave函数才会弹出通用“保存”对话框,否则只执行第2步。
B、“文件(File)”菜单中还有一个“另存为(Save As)”命令,它是与文档类
CDocument的OnFileSaveAs函数相关联。不管文档有没有保存过,OnFileSaveAs
都会执行上述2个步骤。
C、上述文档存盘的必要操作都是由系统自动完成的。
除了系统自动将文档存盘外,用户可以用ClassWizard来重载CDocument::OnSaveDocument函数,并可在Serialize()函数体的ar.IsStorinr()为“真”的条件语句处添加代码,从而在文档中保存用户自己的数据。
例:使用Serialize函数,保存文档中的数据(附加)
(1)打开前面的“多文档”应用程序或重新建一个单或多文档应用程序。
(2)如果是重建的应用程序,也按前面第二步在文档类CMyDoc中加一个成员变量char m_ch[300]。
(3)在文档类即多文档Doc.cpp中找到:void CMyDoc::Serialize(CArchive& ar)
函数并加代码:
void CMyDoc::Serialize(CArchive& ar)// CArchive的对象是ar(引用)
{
if (ar.IsStoring())
{
// TODO: add storing code here
for(int i=0;i<sizeof(m_ch);i++)
ar<<m_ch[i];
}
else
{
// TODO: add loading code here
for(int i=0;i<sizeof(m_ch);i++) ar>>m_ch[i];
CString str;
str.Format(_T("%s"),m_ch);
AfxMessageBox(str);
}
}
上述代码的结果是当保存的文件名成功指定后,该文件保存m_ch中的300字符。若文件中有其它数据,则数据被自动清除。
(可用Word随便建个文件名“小山”,先打开一个文件,成功后,再用保存,将刚才打开的文件另存为“小山”,退出再运行之后打开小山,就是刚才打开的文件内容)
4、关闭文档
当用户试图关闭文档(或退出应用程序)时,应用程序会根据用户对文档的修改与否来进一步完成下列任务。
(1)若文档内容已被修改,则弹出一个“消息”对话框,询问用户是否需要将文档保存。当用户选择“是”,则应用程序执行OnFileSave过程。
(2)调用CDocument::OnCloseDocument虚函数,关闭所有与该文档相关联的文档窗口及相应的试图,调用文档类CDocument的DeleteContents清除文档数据。
MFC应用程序通过CDocument的protected类型成员变量m_bModified的逻辑值来判断用户是否对文档进行修改,如果m_bModified为“真”,则表示文档被修改。对于用户来说,可以通过CDocument的SetModifiedFlag成员函数来设置或通过IsModified成员函数来访问m_bModified的逻辑值。当文档创建、从磁盘中读出以及文档存盘时,文档的这个标记就被置为FLASE(假);而当文档数据被修改时,用户必须使用SetModifiedFlag函数将该标记置为TRUE。这样,当用户关闭文档时,应用程序才会显示询问消息对话框。(多文档与单文档类似)。
6.3.2文档序列化操作
打开和保存文档,系统都会自动调用Serialize函数。而MFC AppWizard在创建文档应用程序框架时已在文档类中重载了Serialize函数,通过在该函数中加代码可达到实现数据序列化的目的。例如:上面的“打开”“保存”都在Serialize里加了代码。
void CMyDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: add storing code here
}
else
{
// TODO: add loading code here
}
}
代码中,Serialize函数的参数ar是一个CArchive类引用变量。通过判断ar.IsStoring
的结果是“真”还是“假”就可决定向文档写或读数据。
CArchive(归档)类提供对文件数据进行缓存,同时还保存一个内部标记,用于标识文档是存入(写盘)还是载入(读盘)。每次只能有一个活动的存档与ar相连。通过CArchive类可以简化文件操作,提供“<<”和“>>”运算符,用于向文件写入简单的数据类型以及从文件中读取它们。下面是CArchive支持的常用数据类型:
类 型 描 述 类 型 描 述
BYTE 8位无符号整型 WORD 16位无符号整型
LONG 32位带符号整型 DWORD 32位无符号整型
float 单精度浮点 double 双精度浮点
int 带符号整型 short 带符号短整型
char 字符型 unsigned 无符号整型
除了“<<”和“>>”运算符外,CArchive类还提供成员函数ReadString和
WriteString用于从一个文件对象中读、写一行文本,它们的原型如下:
BOOL ReadString(CString &rString);
LPTSTR ReadString(LPTSTR lpsz,UINT nMax);
void WriteString(LPCTSTR lpsz);
其中,lpsz用于指定读或写的文本内容,nMax用于指定可以读出的最大字符个数。
注意:当向一个文件写一行字符串时,字符’\0’和’\n’都不会写到文件中。
CArchive(归档)类:CArchive类没有基类,它提供了串行化对象从文件中读
写的类型安全缓冲机制,可以把CArchive对象想象成一种二进制流,就象输入/输出流一样可以顺序高效的处理二进制对象数据。使用CArchive对象之前,必须先创建一个CFile对象,同时保证CArchive对象的读写标志设置和文件打开方式相一致。对于一个CArchive对象,可以进行存储操作,也可以读取,但不能2者同时进行。
(1)CArchive类操作符”<<”和”>>”
CArchive对象的引用,类似于流的输入/输出(cin/cout),可以使用一系列的
操作符以简化程序,例如:
int x;
CString y;
……
ar<<x<<y;
ar是CArchive类的对象,见Serialize(CArchive &ar)函数
2、CArchive类的成员函数:
Read()、Write() 读写指定字节数的缓冲区内容
ReadString()、WriteString()读写一行文本
ReadObject()、WriteObject()调用一个对象的Serialize函数来读或写
ReadClass()、WriteClass()读写一个CRuntimeClass指明对象
IsLoading()、IsStoring()判断当前读写状态
3、Serialize()函数
在”MFC AppWizard”自动生成的程序框架中,文件的串行化操作都是由CDocument派生类成员函数Serialize()完成的。它的结构如下:
void CMySdiDoc::Serialize(CArchive &ar)
{ if(ar.IsStoring())
{
//TODO:….
}
else
{
//TODO……
}
}
在这个成员函数中,使用CArchive对象完成具体的操作。参数中传递进来的CArchive引用对象(ar),是由MFC程序框架根据用户输入需要执行串行化操作时创建的,它包含了所需要的文件的信息,使用它可以对各种CArchive类支持的数据格式进行读写、调用其它CObject派生类对象的串行化函数等。
“MFC AppWizard”自动生成的代码已经完成的工作是:用户选择”打开”、”保存”或”另存为”等命令时,程序框架创建这个文件的CFile对象,将它关联到新创建的CArchive对象上,并设置CArchive对象的”Store”或”Load”标志,用这个对象来调用CDocument派生类的Serialize()成员函数。在Serialize()函数完成读写操作返回后,自动删除Serialize()函数和CFile对象。祥见最后的例题《学生档案管理程序》
例:一个简单的文档序列化示例(259页)
(1)(2)
(3)
char m_chArchive[100];//读、写数据时使用
CString m_strArchive; //读、写数据时使用
BOOL m_bIsMyDoc; //用于判断文档
(4)m_bIsMyDoc=FALSE;
(5)
strcpy(m_chArchive,"&这是一个用于测试文档的内容!");
m_strArchive="这是一行文本!";
m_bIsMyDoc=TRUE;
(6)
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("打开的文档无效!");
}
}//原有
}//原有
(7)不做,与第二步重复了
(8)结果见260页图6.12
6.3.3使用简单数据组集合类
上述文档的读、写是通过变量来存取文档数据的,实际上还可以使用MFC提供的集合类来进行操作。这样不仅可以有利于优化数据结构,简化数据的序列化,而且保证数据类型的安全性。
MFC提供的集合类可分为3类:
(1)链表集合类(List) (2)数组集合类(Array) (3)映射集合类(Map)
在这讨论的是简单数组类,它包括:
CObArray(对象数组集合类)
CByteArray(BYTE数组集合类)(8位无符号整型)
CDWordArray(DWORD数组集合类)(32位无符号整型)
CPtrArray(指针数组集合类)
CStringArray(字符串数组集合类)
CUIntArray(UINT数组集合类)(Windows所用的数据类型,unsigned int 32位无符号整数)
CWordArray(WORD数组集合类)(16位无符号整型)
简单数组集合类是一个大小动态可变的数组,数组中的元素可用下标运算符“[]”
来访问(从0开始),设置或获取元素数据。若要设置超过数组当前个数的元素的值,可以指定是否使数组自动扩展。当数组不需扩展时,访问数组集合类的速度与访问标准C++中的数组的速度同样快,以下的基本操作对所有的简单数组集合类都适用。
1、简单数组集合类的构造及元素的添加
对简单数组集合类构造的方法都是一样的,均是使用各自的构造函数,它们的原型如下:
CByteArray CByteArray() // BYTE数组集合类
CDWordArray CDWordArray() // DWORD数组集合类
CObArray CObArray() // 对象数组集合类
CPtrArray CPtrArray() // 指针数组集合类
CStringArray CStringArray() // 字符串数组集合类
CUIntArray CUIntArray() // UINT数组集合类
CWordArray CWordArray() // WORD数组集合类
下面的代码说明了简单数组集合类的2种构造方法:
CObArray array; //使用默认的内存块大小
CObArray *pArray=new CObArray; //使用堆内存中的默认的内存块大小
为了有效使用内存,在使用简单数组集合类之前最好调用成员函数SetSize设置此数组的大小,与其对应的函数是GetSize,用于返回数组的大小。原型如下:
void SetSize(int nNewSize,int nGrowBy=-1);
int GetSize()const;
其中,参数nNewSize用于指定新的元素的数目(必须大小或等于0)。nGrowBy表示当数组需要扩展时允许可添加的最少元素数目,默认时为自动扩展。
向简单数组集合类添加一个元素,可使用成员函数Add和Append,它们的原型如下:
int Add(CObject *newElement);
int Append(const CObArray&src);
其中,Add函数是向数组的末尾添加一个新元素,且数组自动增1。如果调用的函数SetSize的参数nGrowBy的值大于1,那么扩展内存将被分配。此函数返回被添加的元素序号,元素序号就是数组下标。参数newElement表示要添加的相应类型的数组元素。而Append函数是向数组的末尾添加由src指定的另一个数组的内容。函数返回加入的第1个元素的序号。
2、访问简单数组集合类的元素
在MFC中,一个简单数组集合类元素的访问既可以使用GetAt函数,也可以使用“[]”运算符,例如:
//CObArray::operator[]示例
CObArray array; //CObArray是对象数组集合类
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示例
CObArray array;
array.Add(new CAge(21));//元素0
array.Add(new CAge(40));//元素1
3、删除简单数组集合类的元素
(1)使用函数GetSize和整数下标值访问简单数组集合类中的元素
(2)若对象元素是在堆内存中创建的,则使用delete操作符删除每一个对象元素
(3)调用函数RrmoveAll删除简单数组集合类中的所有元素
例如:以下代码是一个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();
函数RemoveAll是删除数组中的所有元素,而函数RemoveAt(int nIndex,int nCount=1)
则表示要删除数组中从序号为nIndex元素开始的,数目为nCount的元素。
6.3.4文档序列化示例(262页)
见书263页图6.13示例:首先通过对话框来输入一个学生记录,记录包括学生的姓名、学号和3门成绩。然后将记录内容保存到一个对象数组集合类对象中,最后通过文档序列化将记录保存到一个文件中。当添加记录或打开一个记录时,还会将数据显示在文档窗口(视图)中。
1、添加用于学生记录输入的对话框
(1)创建一个单文档的应用程序名为:学生记录
(2)(3)(4)(5)
2、添加一个CStudent类并使该类可序列化(在CMyDoc.h和CMyDoc.cpp中)
//一个可序列化的类必须是CObject的一个派生类,且在类声明中需要包含:
//264页说明
//在CMyDoc.h中加:
#endif // _MSC_VER > 1000
class CStudent:public CObject
{
CString strName; //姓名
CString strID; //学号
float fScore1,fScore2,fScore3;//3门课程
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)处显示数据
};
//在CMyDoc.cpp中加:
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) //在坐标为(0,y)处显示数据
{
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;//从文件读取
}
3、添加并处理菜单
(1)
(2)
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);//更新视图
}
(3)#include "序列化Doc.h"
#include "AddDlg.h"
4、完善代码
(1)在CMyDoc.h中添加下列成员函数和变量(用右键加)
CStudent * GetStudentAt(int nIndex);
int GetAllRecNum(void);
CObArray m_stuObArray;//对象数组集合类CObArray的对象,
(2)在CMyDoc.cpp中,添加函数实现代码
CStudent * CMyDoc::GetStudentAt(int nIndex)
{
if((nIndex<0)||nIndex>m_stuObArray.GetUpperBound())
return 0; //超界处理
return(CStudent *)m_stuObArray.GetAt(nIndex);
}
int CMyDoc::GetAllRecNum()
{
return m_stuObArray.GetSize();//返回数组的大小
}
(3)在CMyDoc.cpp的析构函数里加
CMyDoc::~CMyDoc()
{
int nIndex=GetAllRecNum();
while(nIndex--)
delete m_stuObArray.GetAt(nIndex);
m_stuObArray.RemoveAll();
}
(4)在Serialize函数中添加下列代码
if(ar.IsStoring())
{m_stuObArray.Serialize(ar);}
else
{m_stuObArray.Serialize(ar);}
这里:m_stuObArray是一个对象数组集合类CObArray的对象,当读取数据、调用Serialize成员函数时,它实际上是调用集合类对象中的元素的Serialize成员函数,并将对象添加到m_stuObArray中。那么它又是怎么知道元素是调用CStudent类的Serialize成员函数呢?这是因为当添加学生成绩记录后,一旦保存到文件中,就会将CStudent类名同时存到文件中,当读取时,就会自动使用CStudent类。这是CObArray序列化的一个内部机制。
(5)在CMyView.cpp的OnDrawe函数中加:
int y=0;
for(int nIndex=0;nIndex<pDoc->GetAllRecNum();nIndex++)
{
pDoc->GetStudentAt(nIndex)->Display(y,pDC);
y+=16;
}
(6)打开文档的字串资源IDR_MAINFRAME,将其内容修改为:
文件名\nStudentRec\nEx_Stu\n记录文件(.rec)\n.rec\n
文件名.Document\nEx_Stu Document
(7)运行结果见263页图6.13
6.3.5使用CFile类
在MFC中,CFile类是一个文件I/O的基类,它直接支持非缓冲、二进制的磁盘文件的输入、输出,也可以使用其派生类处理文本文件(CStdioFile)和内存文件(CMemFile)。CFile类的读、写功能类似于C语言中的fread和fwrite,而CStdioFile类的读、写功能类似于C语言中的fgets和fputs。
使用CFile类可以打开或关闭一个磁盘文件、向一个文件读或写数据等。
1、文件的打开和关闭
在MFC中,使用CFile打开一个文件通常使用以下2个步骤:
(1)构造一个不带任何参数的CFile对象
(2)调用成员函数Open并指定文件路径以及文件标志
CFile类的Open函数原型如下:
BOOL Open(LPCTSTR lpszFileName,UINT nOpenFlags,CFileException *pError= NULL);
其中,lpszFileName用于指定一个要打开的文件路径,该路径可以是相对的、绝对的或是一个网络文件名(UNC)。
nOpenFlags用于指定文件打开的标志,它的值如下表(或267页表6.9)所示:
pError用于表示操作失败产生的CFileException指针。
CFileException是一个与文件操作有关的异常处理类。
函数Open操作成功时返回TRUE,否则为FALSE
CFile类的文件访问方式如下:
CFile::modeCreate表示创建一个新文件,若该文件已存在,则将文件原有内容清除
CFile::modeNoTruncate 与CFile::modeCreate组合。若文件已存在,不会将文件原
有内容清除
CFile::modeRead 打开文件只读
CFile::modeReadWrite打开文件读与写
CFile::modeWrite打开文件只写
CFile::modeNoInherit防止子线程继承该文件
CFile::shareDenyNone共享文件的读和写,若其他线程用相关方式打开过此文件,
则创建失败
CFile::shareDenyRead禁止其他线程读此共享文件,若其他线程用相关方式打开过
此文件,则创建失败
CFile::shareDenyWrite禁止其他线程写此共享文件,若其他线程用相关方式打开过
此文件,则创建失败
CFile::shareExclusive禁止其他线程读、写此共享文件,若其他线程用相关方式打
开过此文件,即使是当前线程也会使创建失败
例如:以下代码将显示如何用读、写方式创建一个新文件:
Char *pszFileName = “c:\\test\\myfile.dat”;
CFile myFile;
CFileException fileException;
if(!myFile.Open(pszFileName,CFile::modeCreate|CFile::modeReadWrite),&fileException)
{
TRACE(“Cant open file %s,error=%u\n”,pszFileName,fileException.m_cause);
}
代码中:若文件创建打开有任何问题,Open函数将在他的最后一个参数中返回
CFileException(文件异常类)对象,TRACE宏将显示出文件名和表示失败原因的代码。使用AfxThrowFileException函数将获得更详细的有关错误的报告。
文件“关闭”使用Close函数,若该对象是在堆内存中创建的,还需要调用delete来删除它(不是删除物理文件)。
例:使用CFile类,完成从磁盘中读取数据的应用程序(为267页附加举例)
(1)用AppWizard创建一个单文档应用程序
(2)将ID_FILE_OPEN的消息映射加到View中。
ViewàClassWizardàClass name处置CMyView视图类里à在Object IDs
里找到ID_FILE_OPEN点黑àCOMMANDàAdd FunctionàEdit Code
(3)添加代码:
void CMyView::OnFileOpen()
{
// TODO: Add your command handler code here
CMyDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CString FilePathname;
CString FileName;
CDC *pDC=GetDC();
CFile MyFile;//使用不带参数的CFile对象
CFileDialog dlg(TRUE,_T("TXT"),_T("*.TXT"),OFN_HIDEREADONLY|
OFN_OVERWRITEPROMPT,_T("文本文件(*.TXT)|*.TXT|"));
//利用CFileDialog类创建"打开"对话框,设置打开文件的类型
if(IDOK==dlg.DoModal())
{
FilePathname.Format("%s %s","filepath:",dlg.GetPathName());
FileName.Format("%s %s","Old file name:",dlg.GetFileName());
MyFile.Open(dlg.GetFileName(),CFile::modeRead);
}
pDC->TextOut(0,0,FileName);
pDC->TextOut(0,20,FilePathname);
pDC->TextOut(0,40,"文件已被打开");
}
(4)编译运行出现空白对话框
文件à打开à在一个一般的单文档应用程序中找到如:ReadMe文件,点黑(是.TXT类型的)à打开à就见有一些文字说明了。
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必须为负值才能在文件中定位,否则将超出文件)等。
文件刚刚打开时,默认的文件指针位置为0,即文件的开始位置。
void SeekToBegin() 将文件指针移到文件开始位置
DWORD SeekToEnd() 将文件指针移到文件结尾位置,并将返回文件大小
上述文件操作与C++的fstream操作相类似。
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 文件大小的字节数(32位带符号整数)
BYTE m_attribute文件属性(8位无符号整数)
Char m_szFullName[_MAX_PATH]文件名
static形式的GetStatus函数将获得指定文件名的文件状态,并将文件名复制至m_szFullName中。该函数仅获取文件状态,并没有真正打开文件,这对于测试一个文件的存在性是非常有用的。例如:
CFile theFile;
char *szFileName=”c::\\test\\myfile.dat”;
BOOL bOpenOK;
CFileStatus status;
if(CFile::GetStatus(szFileName,status))//该文件已存在,直接打开
{ bOpenOK=theFile.Open(szFileName,CFile::modeWrite);}
else
{bOpenOK=theFile.Open(szFileName,CFile::modeCreate|CFile::modeWrite);}
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视图及视图类
视图是框架窗口的子窗口,它与文档紧密相联,是用户与文档之间的交互接口。视图不仅可以响应各种类型的输入,例如键盘输入、鼠标输入或拖放输入、菜单、工具栏和滚动条产生的命令输入等,而且能实现文档的打印和打印预览。
6.4.1一般视图类的使用
MFC中的CView类及其他的派生类封装了视图的各种不同的功能,他们为用户实现最新的Windows特性提供了很大的便利。这些视图类如下所示,他们都可以作为文档应用程序中视图类的基类,其设置方法是在MFC AppWizard创建SDI/MDI的第六步中进行基类的选择。
CView的派生类及其功能描述
CScrollView 具有滚动或缩放功能
CFormView 提供可滚动的视图,它由对话框模板创建,并具有和对话框一
样的设计方法
CRecordView 提供表单视图直接与ODBC记录集对象关联;和所有的表单视
图一样,CRecoraView也是基于对话框模板设计的
CDaoRecordView 提供表单视图直接与DAO记录集对象关联,其他同CRecordView
CCtrlView 是CEditView、CListView、CTreeView和CRichEditView的基类,他们提供的文档/视图结构,也适用于Windows98(NT)中的新控件
CEditView 提供包含编辑控件的视图;支持文本的编辑、查找、替换以及滚动功能
CRichEditView 提供包含复合编辑控件的视图;它除了CEditView功能外还支持字体、颜色、图表及OLE对象的嵌入等。
CLisView 提供包含列表控件的视图;他类似于Windows98资源管理器的右侧窗口
CTreeView 提供包含树状控件的视图;他类似于Windows98资源管理的左侧窗口
1、CEditView类
CEditView类对象是一种视图,像CEdit类一样,他是提供窗口编辑控制功能,可以用于执行简单文本操作,如打印、查找、替换、剪贴板的剪切、复制和粘贴等。由于CEditView类自动封装上述功能的映射函数,因此只要在文档模板中使用CEditView类,那么应用程序的“编辑”菜单和“文件”菜单里的菜单项都可自动激活。
例如:创建一个基于CEditView类的单文档应用程序
(1)建一个单文档基于CEditView的应用程序
(2)编译