详解MFC框架窗口、文档和视图

尽管窗口文档视图是MFC的基础,但可能也是最不容易理解的部分,因为其概念比传统编程所需要的Windows函数更强一些,因此,须在本章做进一步详细讨论框架窗口文档视图的方法和技巧。

6.1框架窗口

分两类:一是应用程序主窗口,另一类是文档窗口

6.1.1主窗口文档窗口

窗口(或称主框架窗口)是应用程序直接放在桌面(DeskTop)上的那个窗

口,每个应用程序只能有一个窗口,主窗口的标题栏上往往显示应用程序的名称。

窗口类的源文件名是MainFrm.hMainFrm.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)编译运行,打开一个文档,见书271页图6.15

需要说明的是尽管CEditView类具有编辑框控件的功能,但他却不具有所见即所得编辑的功能,而且只能将文本单一字体的显示,不支持特殊格式的字符。

2、CRichEditView类

CRichEditView类使用了复合文本编辑控件,他支持混合字体格式和更大数据量的文本。CRichEditView类被设计成与CRichEditDoc、CRichEditCntrItem类一起使用,他们可实现一个完整的ActiveX包容器应用程序。

3、使用CFormView类(有用的位图显示)

CFormView类是一个非常有用的视图类,它具有许多无模式对话框的特点。像CDiolog的派生类一样,CFormView的派生类也和相应的对话框资源相联系,

它也支持对话框数据交换和对话框数据确认(CDX和DDV)

CFormView是所有表单视图(如CRecordView,CDaoRecordView,CHtmlView

等)的基类;一个基于表单的应用程序能让用户在程序中创建和使用一个或多个表单。CFormView类的基类是CView类,使用CFormView类后,可以使对话框显示在窗口中,这大大地方便了程序开发者。

创建表单应用程序的基本方法除了在MFC AppWizard创建的第六步中选择

CFormView作为文档应用程序视图类的基类外,还可以通过选择“插入”à

“新建形式(New Form)”菜单命令在文档应用程序中自动插入一个表单。

例如:制作一个使用CFormView类的应用程序,当点击窗口中名为“显示背景图”

的按钮时,窗口会显示一幅位图。(为271页附加)

(1)建一个单文档应用程序“背景图形”,在Step6第6步将视图的基类选择为

CFormView,对程序进行编译并运行,可以看到一个对话框和窗口结合的

应用程序(在对话框上添加各种控件,和普通对话框模板完全一样)。

(2)将对话框上原有东西删掉,加一个按钮,ID等不动,名字为:显示背景图

(3)在视图的头文件的public中添加如下代码:

CBitmap m_bitmap;

(4)将按钮消息IDC_BUTTON1映射到View中去。

(5)在如上按钮消息函数中加代码:

void CMyView::OnButton1()

{

// TODO: Add your control notification handler code here

CDC *pDC=GetDC();

HBITMAP hBitmap=(HBITMAP)LoadImage(NULL,_T("Bkground.bmp"),

IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_DEFAULTSIZE|

LR_LOADFROMFILE);

m_bitmap.Attach(hBitmap);

BITMAP bm;

m_bitmap.GetBitmap(&bm);//注意此处好出错,与你加的图形有关。

CDC dcImage;

if(!dcImage.CreateCompatibleDC(pDC))

return;

CBitmap *pOldBitmap=dcImage.SelectObject(&m_bitmap);

pDC->BitBlt(0,0,bm.bmWidth,bm.bmHeight,&dcImage,0,0,SRCCOPY);

//调用BitBlt()函数填充窗口背景

dcImage.SelectObject(pOldBitmap);

DeleteObject(m_bitmap.Detach());//释放位图存储空间

}

注意:一定将准备好的.bmp图形拷贝到此程序的文件夹中,并重新命名为:

Bkground.bmp,在我的文档里有此图形(玩具2)

4、CHtmlView类的应用

CHtmlView类具有在文档视图结构中提供WebBrowser控件的功能。WebBrowser控件可以浏览网址,也可以作为本地文件和网络文件系统的窗口,它支持超级连接、统一资源定位(URL)导航器并维护历史列表等。

例1:将我们经常浏览的网站分成6类:新闻、聊天、影视、体育、音乐、购物。

点击到哪个,浏览器会自动连接到相应的网站。此处是使用CHtmlView类

创建一个浏览器,而窗口上的工具条是通过DialogBar类来实现的。(为271页附加)

1)运行AppWizard创建一单文档应用程序,第6步将视图基类置为:CHtmlView

2)创建工具条

* 拷贝6个.bmp图形即:新闻、聊天、影视、购物、体育、音乐到本程序的文

件夹中(U盘有这6个图形)

* 将如上6个.bmp图形加到项目中

InsertàResourceà点黑BitmapàImportà出现对话框à选文件类型为*.*à

逐个或一起把这6个图形加进去。并将这6个.bmp图形的IDB改成:

“IDB_BITMAP1U”

“IDB_BITMAP2U”

“IDB_BITMAP3U”

“IDB_BITMAP4U”

“IDB_BITMAP5U”

“IDB_BITMAP6U”

1)建个对话框 ID改为:IDD_MYDIALOG

InsertàResourceà点黑DialogàNewà出现对话框,将ok和cancel删去à把

按钮Button拖过来,做适当大小(比原来稍大些,能放下图形),这样再拷贝同样大小5个按钮,并在属性栏里写:

ID处写:IDC_BUTTON1 Caption处写:IDB_BITMAP1

ID处写:IDC_BUTTON2 Caption处写:IDB_BITMAP2

ID处写:IDC_BUTTON3 Caption处写:IDB_BITMAP3

ID处写:IDC_BUTTON4 Caption处写:IDB_BITMAP4

ID处写:IDC_BUTTON5 Caption处写:IDB_BITMAP5

ID处写:IDC_BUTTON6 Caption处写:IDB_BITMAP6

在Styles风格下置好Owner draw和Bitmap(右键对准按钮击出)

在此对话框本身IDD_MYDIALOG的Styles风格里选:Child 及 Dialog Frame

一定把Title bar的对号去掉,所有都没对号。(右键对准这个对话框击出)

2)手工添加工具条按钮的消息映射

(1)在view.h里加:

//{{AFX_MSG(CMyView)

// NOTE - the ClassWizard will add and remove member functions here.

// DO NOT EDIT what you see in these blocks of generated code !

afx_msg void OnMyAddress(UINT nID);

//}}AFX_MSG

(2)在view.cpp里加:

BEGIN_MESSAGE_MAP(CMyView, CHtmlView)

//{{AFX_MSG_MAP(CMyView)

// NOTE - the ClassWizard will add and remove mapping macros here.

// DO NOT EDIT what you see in these blocks of generated code!

ON_COMMAND_RANGE(IDC_BUTTON1,IDC_BUTTON6,OnMyAddress) //}}AFX_MSG_MAP

// Standard printing commands

3)添加代码:

(1) 在MainFrame.h的public里加:(用CDialogBar类完成工具条显示)

CDialogBar m_WndDlgBar;

CBitmapButton w_MyBitmapButton1,

w_MyBitmapButton2,

w_MyBitmapButton3,

w_MyBitmapButton4,

w_MyBitmapButton5,

w_MyBitmapButton6;

(2)在MainFrame.cpp里加:(在原有函数OnCreate()里加)

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

return -1;

m_WndDlgBar.Create(this,IDD_MYDIALOG,CBRS_TOP,

AFX_IDW_DIALOGBAR);

w_MyBitmapButton1.AutoLoad(IDC_BUTTON1,&m_WndDlgBar);

w_MyBitmapButton2.AutoLoad(IDC_BUTTON2,&m_WndDlgBar);

w_MyBitmapButton3.AutoLoad(IDC_BUTTON3,&m_WndDlgBar);

w_MyBitmapButton4.AutoLoad(IDC_BUTTON4,&m_WndDlgBar);

w_MyBitmapButton5.AutoLoad(IDC_BUTTON5,&m_WndDlgBar);

w_MyBitmapButton6.AutoLoad(IDC_BUTTON6,&m_WndDlgBar);

---------------

return 0;

}

这里可将原有其它的全注释掉。

(3)在View.h的public里加:

CString MyAddressStr;//用于记录URL地址

(4)在View.cpp里加:(全用手工写,连同函数本身)

void CMyView::OnMyAddress(UINT nID)

{

switch(nID)

{

case IDC_BUTTON1:MyAddressStr="http://www.sohu.com";break;

case IDC_BUTTON2:MyAddressStr="http://www.sina.com";break;

case IDC_BUTTON3:MyAddressStr="http://www.nanshan.edu.cn";break;

case IDC_BUTTON4:MyAddressStr="http://www.163.com";break;

case IDC_BUTTON5:MyAddressStr="http://www. badu.com";break;

case IDC_BUTTON6:MyAddressStr="http://www.yahoo.com";break;

}

Navigate2(_T(MyAddressStr),NULL,NULL);

Refresh();

}

4)加映射消息

ViewàClassWizardàClass name里置为视图viewà在右边Messgas表里找到:

OnInitialUpdate加到视图View.cpp里:

void CMyView::OnInitialUpdate()

{

CHtmlView::OnInitialUpdate();

Navigate2(_T(MyAddressStr),NULL,NULL);

// TODO: This code navigates to a popular spot on the web.

// change the code to go where you'd like.

Navigate2(_T("http://www.microsoft.com/visualc/"),NULL,NULL);

}

5)在View.cpp的构造函数里加:

MyAddressStr="http://www.nashan.edu.com.cn";

8)运行,能直接上网,后你再选你定义的6个网址,点哪个就可到哪个网站上。

例2:制作网页

利用Visual C++6.0中的Web技术的新类型和控件,连接涉及HTML和JavaScript的新特性,可以通过CHtmlView类和浏览器控件,随意加载HTML文档,本例中,将利用Visual C++上的开发工具制作2个Web页,并且在下一个实例中(例3)加载该页。

1)创建Web网页

桌面状态双击Visual C++系统à文件àNewàFileàHTML Pageà右面路径写:e:\vcppà文件名字处写:The first pageàOK

2)在已有的程序语句<BODY>和</BODY>间加代码:

<H1>

This is my first Web Page!

</H1>

<H1>

This is my first Web Page!

</H1>

<H1>

This is my first Web Page!

</H1>

<a href="The%20Second%20page.htm">Hyperlink! The next Page!</a>//通过这条

//语句实现网页的超级连接。

注意:写完后存盘退出:FileàSave..-->Exit

3)再在桌面状态双击Visual C++系统à文件àNewàFileàHTML Pageà右面路径写:e:\vcppà文件名字处写:The Second pageàOK

4)添代码:

<HTML>

<HEAD>

<META NAME="GENERATOR" Content="Microsoft Visual Studio 6.0">

<TITLE></TITLE>

</HEAD>

<BODY bgColor=blanchedalmond background="Balloon.jpg">

<CENTER >

<H1>Our first JavaScript Example </H1><BR><BR><INPUT id=button1 name=关闭 type=button value=关闭窗口 onClick="CloseWindow()">;

</CENTER>

<P>

<PRE>

<SCRIPT language=JavaScript>

document.writeln("Hello and welcome to JavaScript")

var date="Hello JavaScript";

document.writeln(date);

document.write(date);

function CloseWindow()

{

window.close()

}

</SCRIPT>

<PRE><P></P></PRE></PRE>

</BODY>

</HTML>

象上面一样存盘退出后,你在e:\vcpp中找到The first page和The Second page这2个刚刚建好的文件,双击The first page出现一个页,你再单击此页上的:

Hyperlink! The next Page!就可浏览第二个网页。

注意:事先要将实例 59中的图形拷到和你的文件在一起的目录vcpp下,名字叫:Balloon.jpg

注意:要修改你写的HTML文件:要双击Visual C++系统àFileàOpenà*.*à找到你刚才建的文件名(The first page和The Second)单击The first pageà打开à就可以修改了。
例3:加载网页

由于有了CHtmlView类和浏览器控件,我们可任意在自己开发的应用程序中加载HTML文档。先通过一个“打开”对话框获取HTML文件的路径和名称,然后通过CHtnlView类的成员函数Navigate2()打开该网页。

1)建一个单文档应用程序,名为:加载网页,第6步将基类置:CHtmlView

2)将系统原有的OPEN(打开),映射到View中

ViewàClassWizardàClass name处置:Viewà左面找到ID_FILE_OPEN点黑

àCOMMANDàAdd FunctionàEdit Code

3)添代码:

void CMyView::OnFileOpen()

{

// TODO: Add your command handler code here

CFileDialog dlg( TRUE,_T("htm"),_T("*.htm"),

OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

_T("网页 (*.htm)|*.htm|"));

if(IDOK==dlg.DoModal())

{

Navigate2(dlg.GetPathName(),NULL,NULL);

}

}

运行后,在出现的空白窗口上à点文件à打开à在vcpp里找到The first page点黑à打开à出现第1个网页àHyperlink! The next page点黑à便出现第2个网页。

5、CScrollView类

CScrollView类不仅能直接支持视图的滚动操作,而且还能管理视图的大小和映射模式,能响应滚动条消息、键盘消息以及鼠标消息。

需要说明的是,当滚动视图应用程序创建后,MFC AppWizard会自动重载CView::OnInitialUpdate,并在该函数调用CScrollView成员函数SetScrollSizes来设置相关参数,如映射模式、滚动逻辑窗口的大小、水平或垂直方向的滚动量等。如果仅需要视图具有自动缩放功能(而不具有滚动特性),则调用:

CScrollView::SetScaleToFitSize函数代替MFC AppWizard添加的SetScrollSizes函数调用代码。

6.4.2列表控件和列表视图

列表控件是一种极为有用的控件之一,它可以用“大图标”、“小图标”、“列表视图”或“报表视图”等4种不同的方式来显示一组信息(见272页图6.16)。

1)大图标方式:是指列表中的所有项的上方均以大图标(32*32)形式出现,用

户可将其拖动到列表视图窗口的任意位置。

2)小图标方式:是指列表中的所有项的左方均以小图标(16*16)形式出现,用

户可将其拖动到列表视图窗口的任意位置。

3)列表视图方式:与图标方式不同,列表项被安排在某一列中,用户不能拖动他们

4)报表视图方式:是指列表项出现在各自的行上,而相关的信息出现在右边,最左边的列可以是标签或图标,接下来的列则是程序指定的列表项内容。其最引人注目的是他可以有标题头。

1、列表控件的风格及其修改

列表控件的风格有2类,一类是一般风格,如下面所示;另一类是Visual C++6.0在原有的基础上添加的扩展风格,如LVS_EX_FULLROWSELECT,表示整行选择,但它仅用于“报表视图”显示方式。

对于列表控件的一般风格的修改,可先调用GetWindowLong来获取当前风格,然后调用SetWindowLong重新设置新的风格。对于列表控件的扩展风格,可直接调用成员函数CListCtrl::SetExtendedStyle加以设置。

列表控件的一般风格

LVS_ALIGNLEFT 在“大图标”或“小图标”显示方式中,所有列表项左对齐

LVS_ALIGNTOP 在“大图标”或“小图标”显示方式中,所有列标项被安排在控件的顶部

LVS_AUTOARRANGE在“大图标”或“小图标”显示方式中,图标自动排列

LVS_EDITLABELS允许用户编辑项目文本,但父窗口必须处理LVN_ENDLABELEDIT通知消息

LVS_ICON“大图标”显示方式

LVS_LIST“列表视图”显示方式

LVS_NOCOLUMNHEADER在“报表视图”显示方式中,不显示其标题头

LVS_NOLABELWRAP在“大图标”显示方式中,项目文本占满一行

LVS_NOSCROLL禁用滚动条

LVS_NOSORTHEADER当用户单击标题头时,不产生任何操作

LVS_OWNERDRAWFIXED指明控件的拥有者,而不是Windows负责绘制控件

LVS_REPORT“报表视图”显示方式

LVS_SHAREIMAGELISTS共享图象列表

LVS_SHOWSELALWAYS一直显示被选择的部分

LVS_SINGLESEL只允许单项选择,默认时是多项选择

LVS_SMALLICON“小图标”显示方式

LVS_SORTASCENDING按升序排列

LVS_SORTDESCENDING按降序排列

2、列表项的基本操作

CListView按照MFC文档视图结构封装了列表控件CListCtrl类的功能。由于他又是从CCtrlView中派生的,因此他既可以调用CCtrlView的基类CView类的成员函数,又可以使用CListCtrl功能。当使用CListCtrl功能时,必须先要得到CListView封装的内嵌可引用的CListCtrl对象,这时可调用CListView的成员函数GetListCtrl,例如以下代码:

CListCtrl &listCtrl=GetListCtrl();//listCtrl必须定义成引用

列表控件类CListCtrl提供了许多用于列表项操作的成员函数,如列表项与列的添加和删除等:

(1)函数SetImageList *SetImageList(CImageList *pImageList,int nImageList);

其中,nImageList用于指定图象列表的类型,他可以是LVSIL_NORMAL(大图标)、

LVSIL_SMALL(小图标)和LVSIL_STATE(表示状态的图象列表)。

需要说明的是,CImageList类用于创建、显示或管理图象的最常见的操作,有

创建和添加,相应的函数原型如下:

BOOL CImageList::Create(int cx,int cy,UINT nFlags,int nInitial,int nGrow);

其中,cx和cy用于指定图象的像素大小;nFlags表示要创建的图象类型,一般取其ILC_COLOR和ILC_MASK(指定屏蔽图象)的组合,默认的ILC_COLOR为

ILC_COLOR4(16色),当然也可以是ILC_COLOR8(256色)、ILC_COLOR16(16位色)等;nInitial用于指定图象列表中最初的图象数目;nGrow表示当图象列表的大小发生改变时图象可以增加的数目。

int CImageList::Add(CBitmap *pbmImage,CBitmap *pbmMask);

int CImageList::Add(CBitmap *pbmImage,COLORREF crMask);

int CImageList::Add(HICON hIcon);

此函数用于向一个图象列表添加一个图标或多个位图。成功时返回第1个新图象的索引号,否则返回-1。pbmMask表示包含屏蔽的位图指针,pbmImage表示包含图象的位图指针,crMask表示屏蔽色,hIcon表示图标句柄。

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

(3)函数DeleteItem和DeleteAllItems分别用于删除指定的列表项和全部列表项,函数原型如下:

BOOL DeleteItem(int nItem);

BOOL DeleteAllItems();

(4)函数FindItem用于查询列表项,函数成功查找时返回列表项的索引号,否则返回-1,其原型如下:

int FindItem(LVFINDINFO *pFindInfo,int nStart=-1)const;

其中:nStart表示开始查找的索引号,-1表示从头开始。pFindInfo表示要查找的信息,其结构描述如下:

Typedef struct tagLVFINDINFO

{ UINT flags //查找方式 (32位无符号整数)

LPCSTR psz; //匹配的文本 (指向字符串常量的32位指针)

LPARAM lparam; //匹配的值

POINT pt; //查找开始的位置坐标

UINT vkDirection; //查找方向,用虚拟方向键值表示

}LVFINDINFO,LPFINDINFO;

结构中flags可以是下列值之一或组合:

LVFI_PARAM 查找内容由lParam指定

LVFI_PARTIAL 查找内容由psz指定,不精确查找

LVFI_STRING 查找内容由psz指定,精确查找

LVFI_WRAP 若没有匹配,再从头开始

LVFI_NEARESTXY靠近pt位置查找,查找方向由vkDirection确定

(5)函数Arrange用于按指定方式重新排列列表项,其原型如下:

BOOL Arrange(UINT nCode);

其中,nCode用于指定排列方式,它可以是下列值之一:

LVA_ALIHNLEFT 左对齐

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 frnt; //列的标题或子项文本格式

int cx; //列的像素宽度

LPTSTR pszText; //列的标题文本

int cchTextMax;//列的标题文本大小

int iSubItem; //和列相关的子项索引

int iImage; //图象列表中的图象索引

int iOrder; //列的序号,最左边的列为0

}LVCOLUMN,FAR *LPLVCOLUMN;

结构中,mask可以是0或下列值之一或组合:

LVCF_FMT fint参数有效

LVCF_IMAGE iImage参数有效

LVCF_ORDER iOrder参数有效

LVCF_SUBITEM iSubItem参数有效

LVCF_TEXT pszText参数有效

LVCF_WIDTH cx参数有效

结构中,frnt可以是下列值之一;

LVCFMT_BITMAP_ON_RIGHT位图出现在文本的右边,对于从图象列表中选取的图象无效

LVCFMT_CENTER文本居中

LVCFMT_COL_HAS_IMAGES列表头的图象是在图象列表中

LVCFMT_IMAGE从图象列表中显示一个图象

LVCFMT_LEFT文本左对齐

LVCFMT_RIGHT文本右对齐

(7)函数DeleteColumn用于从列表控件中删除一个指定的列,其原型如下:

BOOL DeleteColumn(int nCol);

除了上述操作外,还有一些函数是用于设置或获取列表控件的相关属性的。例如SetColumnWidth用于设置指定列的像素宽度,

GetItemCount用于返回列表控件中的列表项个数等,它们的原型如下:

BOOL SetColumnWidth(int nCol,int cx);

Int GetItemCount();

其中,nCol用于指定要设置的列的索引号,

Cx用于指定列的像素宽度,它可以是LVSCW_AUTOSIZE,表示自动调整宽度

3、列表控件的消息

在列表视图中,可以用MFC ClassWizard映射的控件消息有公共控件消息(如NM_DBLCLK)、标题头控件消息以及列表控件消息。常用的列表控件消息有:

LVN_BEGINDRAG 用户按鼠标左键拖动列表项

LVN_BEGINLABELEDIT 用户对某列表项标签进行编辑

LVN_COLUMNCLICK 某列被单击

LVN_ENDLABELEDIT 用户对某列表项标签结束编辑

LVN_ITEMACTIVATE 用户激活某列表项

LVN_ITEMCHANGED 当前列表项已被改变

LVN_ITEMCHANGING 当前列表项即将改变

LVN_KEYDOWN 某键被按下

说明:在用ClassWizard处理上述这些消息时,其消息处理函数参数中往往会出

现NM_LISTVIEW结构,其定义如下:

typedef struct tagNMLISTVIEW

{ NMHDR hdr; //包含通知消息的结构

int iItem; //列表项索引,没有为-1

int iSubItem;//子项索引,没有为0

UINT uNewState;//新的项目状态

UINT uOldState; //原来的项目状态

UINT uChanged;//项目属性更改标志

POINT ptAction; //事件发生的地点

LPARAM lParam; //用户定义的32位值

}NMLISTVIEW,FAR *LPNMLISTVIEW;

但对于LVN_ITEMACTIVATE来说,上述结构变成了NMITEMACTIVATE,它在结构NM_LISTVIEW基础上增加了一个成员“UINT uKeyFlags”,用于表示:

ALT、CTRL和SHIFT键的按下状态,它的值可以是LVKF_ALT、LVKF_CONTROL

和LVKF_SHIFT。

例:将当前文件夹中的文件用“大图标”、“小图标”、“列表视图”以及“报表视图”等4种不同方式在列表视图中显示出来。当双击某个列表项时,还将该项的文本标签内容用消息对话框的形式显示出来。实现这个示例有两个关键问题,一个是如何获取当前文件夹中的所有文件,另一个是如何获取各个文件的图标以便添加到与列表控件相关联的图像列表中。第一个问题可能通过MFC类CFileFind来解决,第二个问题,需要使用API函数SHGetFileInfo解决。需要说明的是,为了使添加到图像列表中的图标不重复,本例还使用了一个字符串数组集合类对象来保存图标的类型,每次添加图标时都先来验证该图标是否已经添加过。

操作步骤如书277页,项目名为:列表控件

(1)

(2)public:

CImageList m_ImageList;

CImageList m_ImageListSmall;

CStringArray m_strArray;

void CMyView::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);//设置新风格

}

}

(3)

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

(5)

LPNMITEMACTIVATE lpItem=(LPNMITEMACTIVATE)pNMHDR;

int nIndex=lpItem->iItem;

if(nIndex>=0)

{

CListCtrl&m_ListCtrl=GetListCtrl();

CString str=m_ListCtrl.GetItemText(nIndex,0);

MessageBox(str);

}

这样,当双击某个列表项时,就会弹出一个消息对话框,显示该列表项的文本标签内容。

(6)//创建图像列表

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

SHGetFileInfo(str,0,&fi,sizeof(SHFILEINFO),

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

注意:程序运行后是显示书上的280页图6.17形式。改变列表显示形式要按Ctrl+Shift+X

6.4.3树控件和树视图

一个“树控件”是一个用于显示层次列表项的窗口,比如一个文档中的标题、索引中的项目或磁盘中的文件和目录,每一个都包括一个标签和一个可选的位图图像,还有一个与其相关的子项的列表,单击一个项,用户可以展开或缩进该项的相关子项。例如:Visual C++6.0中的项目工作区窗口就是这种树控件。

树控件类为:CTreeCtrl,而CTreeView类简化了CTreeCtrl类的使用,却CTreeView类提供的成员函数GetTreeCtrl可使我们从CTreeView中得到封装的CTreeCtrl对象。

1、树形视图的风格

常见的树控件风格如下所示(书289页表7.11):其修改方法与列表控件的一般风格修改方法相同。

TVS_HASLINES子结点与它们的父结点之间用线连接

TVS_LINESATROOT用线连接子结点和根结点

TVS_HASBUTTONS在每一个父结点的左边添加一个按钮“+”和“-”

TVS_EDITLABELS允许用户编辑结点的标签文本内容

TVS_SHOWSELALWAYS当控件失去焦点时,被选择的结点仍然保持被选择

TVS_DISABLEDRAGDROP该控件被禁止发送TVN_BEGINDRAG通知消息

TVS_NOTOOLTIPS控件禁用工具提示

TVS_SINGLEEXPAND当使用这个风格时,结点可展开收缩

TVS_CHECKBOXES在每一结点的最左边有一个复选框

TVS_FULLROWSELECT多行选择,不能用于TVS_HASLINES风格

TVS_INFOTIP控件得到工具提示时发送TVN_GETINFOTIP通知消息

TVS_NONEVENHEIGHT结点的高度值不一样,默认结点高度是一样的

TVS_NOSCROLL不使用水平或垂直滚动条

TVS_TRACKSELECT使用热点跟踪

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

(2)函数DeleteItem和DeleteAllItems分别用于删除指定的结点和全部的结点,他们的原型如下:

BOOL DeleteAllItems();

BOOL DeleteItem(HTREEITEM hItem);

其中,hItem用于指定要删除的结点的句柄。如果hItem的值是TVI_ROOT,则所

有的结点都从此控件中被删除。

(3)函数Expand用于展开或收缩指定父结点的所有子结点,其原型如下:

BOOL Expand(HTREEITEM hItem,UINT nCode);

其中,hItem指定要被展开或收缩的结点的句柄

nCode用于指定动作标志,它可以是:

TVE_COLLAPSE 收缩所有子结点

TVE_COLLAPSERESET收缩并删除所有子结点

TVE_EXPAND展开所有子结点

TVE_TOGGLE如果当前是展开的则收缩,反之则展开

(4)函数GetNextItem用于获取下一个结点的句柄

HTREEITEM GetNextItem(HTREEITEM hItem,UINTnCode);

其中:hItem指定参数结点的句柄

nCode用于指定与hItem的关系标志,常见的标志有:

TVGN_CARET 返回当前选择结点的句柄

TVGN_CHILD 返回第1个子结点句柄,hItem必须为NULL

TVGN_NEXT 返回下一个兄弟结点(同一个树支上的结点)句柄

TVGN_PARENT 返回指定结点的父结点句柄

TVGN_PREVIOUS 返回上一个兄弟结点句柄

TVGN_ROOT 返回hItem父结点的第1个子结点句柄

(5)函数HitTest用于测试鼠标当前操作的位置位于哪一个结点,并返回该结点句柄

它的原型如下:

HTREEITEM HitTest(CPoing pt,UINT *pFlags);

其中pFlags包含当前鼠标所在的位置标志,如下列常用定义:

TVHT_ONITEM 在结点上

TVHT_ONITEMBUTTON 在结点前面的按钮上

TVHT_ONITEMICON 在结点文本前面的图标上

TVHT_ONITEMLABEL 在结点文本上

除了上述操作外,CtreeCtrl还有其他常见操作如下(283页表6.13)

UINT GetCount(); 获取树中结点的数目,没有返回-1

BOOL ItemHasChildren(HTREEITEM hItem); 判断一个结点是否有子结点

HTREEITEM GetChildItem(HTREEITEM hItem); 获取由hItem指定的结点的子结点句柄

HTREEITEM GetParentItem(HTREEITEM hItem); 获取由hItem指定的结点的父结点句柄

HTREEITEM GetSelectedItem(); 获取当前被选择的结点

HTREEITEM GetRootItem(); 获取根结点句柄

CString GetItemText(HTREEITEM hItem)const; 返回由hItem指定的结点的文本

BOOL SetItemText(HTREEITEM hItem,LPCTSTR lpszItem);设置由hIem指定的结点的文本

DWORD GetItemData(HTREEITEM hItem)const; 返回与指定结点关联的32位值

BOOL SetItemData(HTREEITEM hItem,DWORD dwData); 设置与指定结点关联的32位值

COLORREF SetBkColor(COLORREF clr); 设置控件的背景颜色

COLORREF SetTextColor(COLORREF clr); 设置控件的文本颜色

BOOL SelectItem(HTREEITEM hItem); 选中指定结点

BOOL SortChildren(HTREEITEM hIem); 用于将指定结点的所有子结点排序

3、树视图控件的通知消息

与列表视图相类似,树视图也可以用ClassWizard映射公共控件消息和树控件消息。其中,常用的树控件消息有:

TVN_BEGINDRAG 开始拖放操作

TVN_BEGINLABELEDIT 开始编辑文本

TVN_BEGINRDRAG 用鼠标右键开始拖放操作

TVN_ENDLABELEDIT 文本编辑结束

TVN_ITEMEXPANDED 含有子结点的父结点已展开或收缩

TVN_ITEMEXPANDING 含有子结点的父结点将要展开或收缩

TVN_SELCHANGED 当前选择结点发生改变

TVN_SELCHANGING 当前选择结点将要发生改变

而在用ClassWizard处理上述这些消息时,其消息处理函数参数中往往回出现NM_TREEVIEW结构,其定义如下:

Typedef struct tagNMTREEVIEW

{ NMHDR hdr; //含有通知代码的信息结构

UINT action; //通知方式标志

TVITEM itemOld; //原有结点的信息

TVITEM itemNew; //现在结点的信息

POINT ptDrag; //事件产生时,鼠标的位置

}NMTREEVIEW,FAR *LPNMTREEVIEW;

例:遍历本地磁盘所有的目录(文件夹)

为了能获取本地机器中有效的驱动器,需要使用GetLogicalDrives(获取逻辑驱动器)和GetDriveType(获取驱动器)函数,本例使用SHGetFileInfo来进行的。操作步骤如下:(按书284页去作):

(2)CString m_strPath;

CImageList m_ImageList;

(3)打开击项目工作区的ClassView右键击CMyView加成员函数InsertFoldItem

并加以下代码:

void CMyView::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);

}

}

(4)打开击项目工作区的ClassView右键击CMyView加成员函数GetFoldItemPath

并加以下代码:

CString CMyView::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;

}

(5)

void CMyView::OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult)

{

NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

// TODO: Add your control notification handler code here

HTREEITEM hSelItem=pNMTreeView->itemNew.hItem;

CTreeCtrl&treeCtrl=GetTreeCtrl();

CString strPath=GetFoldItemPath(hSelItem);

if(!strPath.IsEmpty())

{

InsertFoldItem(hSelItem,strPath);

treeCtrl.Expand(hSelItem,TVE_EXPAND);

}

(6)

cs.style|=TVS_HASLINES|TVS_LINESATROOT|TVS_HASBUTTONS;

(7)

CTreeView::OnInitialUpdate();

CTreeCtrl&treeCtrl=GetTreeCtrl();

m_ImageList.Create(16,16,ILC_COLOR8|ILC_MASK,2,1);

treeCtrl.SetImageList(&m_ImageList,TVSIL_NORMAL);

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

SHGetFileInfo(strPath,0,&fi,sizeof(SHFILEINFO),

SHGFI_ICON|SHGFI_SMALLICON|SHGFI_OPENICON);

m_ImageList.Add(fi.hIcon);

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

}

}

//HWND为窗口句柄

//DWORD 为unsigned long 32位无符号整数,段地址和相关的偏移地址

6.5文档视图结构

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

6.5.1文档视图的相互作用

正常情况下,MFC应用程序用一种编程模式使程序中数据与他的显示形式和用户交互分离开来,这种模式就是“文档视图结构“,文档视图结构能方便地实现文档视图的相互作用。一旦在用MFC AppWizard(exe)创建SDI/MDI的第1步中选中了Document/View architecture support(文档/视图体系结构支持)复选框,就可使用下列5个文档视图相互作用的重要成员函数。(287页)

1、CView::GetDocument函数

视图对象只有一个与之相联系的文档对象,它所包含的GetDocument函数允

许应用程序由视图得到与之相联系的文档

2、CDocument::UpdateAllViews函数

如果由于种种原因使文档中的数据发生了改变,那麽所有的视图都必须被通

知到,以便它们能够对所显示的数据进行相应的更新。UpdateAllViews函数就是

起到这样的作用的。他的原型如下:

void UpdateAllViews(CView *pSender,LPARAM lHint=OL,CObject *pHint=NULL);

参数:pSender表示视图指针,若在应用程序文档类的成员函数中调用该函数,则此参数应为NULL,若该函数被应用程序视图类中的成员函数调用,则此参数应为this。

lHint 通常表示更新视图时发送信息的提示标识值

pHint表示存储信息的对象指针

当UpdateAllViews函数被调用时,如果参数pSender指向某个特定的视图对象,那么除了该指定的视图之外,文档的所有其他视图的OnUpdate函数都会被调用。

3、CView::OnUpdate函数

这是一个虚函数,当应用程序调用了CDocument::UpdateAllViews函数时,应

用程序框架就会相应地调用该函数。

virtual void OnUpdate(CView *pSender,LPARAM lHint,CObject *pHint);

参数:pSender表示文档被更改的所在视图类指针,当为NULL时表示所有的视图需要更新。默认的OnUpdate函数(lHint=0,pHing=NULL)使得整个窗口矩形无效。

如果用户想要使视图的某部分无效,那么就要定义相关的提示(Hint)参数给出准确的无效区域;lHint和pHint的含义同UpdateAllViews

事实上,Hint机制主要用于在视图中根据提示标识值来获取文档或其他视图传递来的数据,例如将文档的CPoint数据传递给所有的视图类,则有下列语句:

GetDocument()->UpdateAllViews(NULL,1,(CObject *)&m_ptDraw);

4、 CView::OnInitialUpdate函数

当应用程序被启动时,或当用户从“文件”选单中选择了“新建”或“打开”

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

5、CDocument::OnNewDocument函数

文档应用程序中,当用户从“文件”选单中选择“新建”命令时,框架

首先构造一个文档对象,然后调用该虚函数。

6.5.2应用程序对象指针的互调

在MFC中,文档视图机制使框架窗口文档视图和应用程序对象之间具有一定的联系,通过相应的函数可实现各对象指针的互相调用。

1、从文档类中获取视图对象指针

文档类中有一个与其关联的各视图对象的列表,并可通过CDocument类的

下面2个成员函数来定位相应的视图对象。

(1)GetFirstViewPosition函数用于获得与文档类相关联的视图列表中第1个可

视图的位置。

(2)GetNextView函数用于获取指定视图位置的视图类指针,并将此视图位置

移动至下1个位置,若没有下一个视图,则视图位置为NULL,他们原型为:

virtual POSTTION GetFirstViewPosition()const;

virtual CView *GetNextView(POSTTION &rPosition)const;

例如:以下代码是使用CDocument::GetFirstViewPosition和GetNextView重绘每个视图

void CMyDoc::OnRepaintAllViews()

{ POSTTION pos=GetFirstViewPosition();

while(pos!=NULL)

{ CView *pView=GetNextView(pos);

pView->UpdateWindow();

}

}//实现上述功能也可直接调用UpdateAllViews(NULL);

2、从视图类中获取文档对象和主框架对象指针

视图类中获取文档对象指针是很容易的,只需调用视图类中的成员函数

GetDocument即可。而函数CWnd::GetParentFrame可实现从视图类中获取主框架指针,其原型如下:

CFrameWnd *GetParentFrame()const;

该函数将获得父框架窗口指针,它在父窗口链中搜索,直到一个CFrameWnd(或其派生类)被找到为止。成功返回一个CFrameWnd指针,否则返回NULL。

3、在主框架中获取视图对象指针

对于SDI应用程序来说,只需调用CFrameWnd类的GetActiveView成员函数即可,其原型如下:

CView *GetActiveView()const;

函数返回当前CView类指针,若没有当前视图,则返回NULL

若将此函数应用在多文档应用程序的CMDIFrameWnd类中,并不是想象的那样获得当前活动子窗口视图对象指针,而是返回NULL,这是因为:在一个多文档应用程序中,多文档应用程序主框架窗口(CMDIFrameWnd)没有任何相关的视图对象。相反,每个子窗口(CMDIChildWnd)却有一个或多个与之相关的视图对象。在多文档应用程序中获取活动视图对象指针的正确方法是:先获得多文档应用程序的活动文档窗口,然后再获得与该活动文档窗口相关联的活动视图,如下代码:

CMDIFrameWnd *pFrame=(CMDIFrameWnd*)AfxGetApp()->m_pMainWnd;

//获得MDI的活动子窗口

CMDIChildWnd *pChild=(CMDIChildWnd*)pFrame->GetActiveFrame();

//或CMDIChildWnd *pChild=pFrame->MDIGetActive();

//获得与子窗口相关联的活动视图

CMyView *pView=(CMyView*)pChild->GetActiveView();

另外,在框架类中还可直接调用CFrameWnd::GetActiveDocument函数获得当前活动的文档对象指针。

在同一个应用程序的任何对象中,可通过全局函数AfxGetApp来获得指向应用程序对象的指针。如下说明各种对象指针的互调方法:

所在的类 获取的对象指针 调用的函数 说明

文档视图 GetFirstViewPosition 获取第1个和下一个视图的位置

文档文档模板 GetDocTemplate 获取文档模板对象指针

视图文档 GetDocument 获取文档对象指针

视图框架窗口 GetParentFrame 获取框架窗口对象指针

框架窗口视图 GetActiveView 获取当前活动的视图对象指针

框架窗口文档 GetActiveDocument 获取当前活动的文档对象指针

MDI主框架类 MDI子窗口 MDIGetActive获得当前活动的MDI子窗口对象指针

6.5.3切分窗口

切分窗口是一种“特殊”的文档窗口,它可以有许多窗格(pane),在窗格中又可包含若干个视图

1、静态切分和动态切分

对于“静态切分”窗口来说,当窗口第1次被创建时,窗口就已经被切分好

了,窗格的次序和数目不能再被改变,但用户可以移动切分条来调整窗格的大小。

每个窗格通常是不同的视图类。

对于“动态切分”窗口来说,它允许用户在任何时候对窗口进行切分,用户即可以通过选择单项来对窗口进行切分,也可以通过拖动滚动条中的切分框对窗口进行切分。动态切分窗口中的窗格通常使用的是同一个视图类。当切分窗口被创建时,左上窗格通常被初始化成一个特殊的视图。当视图沿着某个方向被切分时,另一个新添加的视图对象被动态创建;当视图沿着2个方向被切分时,新添加的3个视图对象则被动态创建。当用户取消切分时,所有新添加的视图对象被删除,但最先的视图仍被保留,直到切分窗口本身消失为止。

无论是静态切分还是动态切分,在创建时都要指定切分窗口中行和列的窗格最大数目。对于静态切分,窗格在初始时就按用户指定的最大数目划分好了;而对于动态切分窗口,当窗口构造时,第1个窗格就被自动创建。动态切分窗口允许的最大窗格数目是2*2,而静态切分允许的最大窗格数目为16*16。

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);//书245页有WS_CHILD等的说明

BOOL CreateStatic(CWnd *pParentWnd,int nRows,int nCols,DWORD dwStyle =

WS_CHILD|WS_VISIBLE,UINT nID=AFX_IDW_PANE_FIRST);

参数:pParentWnd表示切分窗口的父框架窗口

nMaxRows表示窗口动态切分的最大行数(不能超过2)。

nMaxCols表示窗口动态切分的最大列数(不能超过2)

nRows表示窗口静态切分的行数(不能超过16)

nCols表示窗口静态切分的列数(不能超过16)

sizeMin表示动态切分时允许的窗格最小尺寸

CSplitterWnd类成员函数CreateView用于为静态窗格指定一个视图类,并创建视

窗口,其函数原型如下:

BOOL CreateView(int row,int col,CRuntimeClass *pViewClass,SIZE sizeInit,

CCreateContext *pContext);

参数:row和col用于指定具体的静态窗格

pViewClass用于指定与静态窗格相关联的视图

sizeInit表示视图窗口初始大小

pContext用于指定一个“创建上下文”指针

CCreateContext创建“上下文”结构,包含当前文档视图框架结构。

3、静态切分窗口实现

利用CSplitterWnd成员函数,用户可以在文档应用程序的文档窗口中添加动态或静态切分功能

例:将SDI应用程序中的文档窗口静态分成3x2个窗口(291页)

(1)建一个单文档的应用程序,项目名为:切分窗口

(2)在主框架MainFrm.h文件里加数据成员(protected:下)

CSplitterWnd m_wndSplitter;

(3)建一个新视图类CDemoView(基类为CView)

(4)CRect rc;

GetClientRect(rc);//获取客户区大小

CSize paneSize(rc.Width()/2-16,rc.Height()/3-16);//计算每个窗格的平均尺寸

m_wndSplitter.CreateStatic(this,3,2);//创建3*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),

paneSize,pContext);

m_wndSplitter.CreateView(1,1,RUNTIME_CLASS(CDemoView),

paneSize,pContext);

m_wndSplitter.CreateView(2,0,RUNTIME_CLASS(CDemoView),

paneSize,pContext);

m_wndSplitter.CreateView(2,1,RUNTIME_CLASS(CDemoView),

paneSize,pContext);

return TRUE;

//return CFrameWnd::OnCreateClient(lpcs, pContext);

(5)#include "DemoView.h"

(6)运行结果见293页图6.19

A、在调用CreateStatic函数创建静态切分窗口后,必须将每个窗格用CreateView

函数指定相关联的视图类。各窗格的视图类可以相同,也可以不同

B、切分功能只应用于文档窗口,对于单文档应用程序切分的创建是在CMainFrame

类进行的,而对于多文档应用程序,添加切分功能时应在文档窗口类CChildFrame

中进行。

4)动态切分窗口的实现

动态切分窗口的创建过程要比静态切分简单得多,它不需要重新为窗格指定其他视图类,动态切分窗口的所有窗格共享同一个视图。若在文档窗口中添加动态切分功能,除了上述方法外,还可在MFC AppWizard创建文档应用程序的”Step4”

对话框中单击”高级”按钮,通过选中”Advanced Options”对话框Window Styles页面中的”应用拆分窗体”(UseSplit Window)来创建,或是通过添加切分窗口组件来创建

例:通过添加切分窗口组件来创建动态切分(293页)

(1)创建一个单文档的应用程序,名为:动态切分

(2)选择ProjectàAdd To ProjectàComponents and Controls弹出293页图6.20所

示的“单文档应用程序动态切分”对话框。

(3)(4)(5)(6)

注意:通过上述方法可以向应用程序添加许多类似组件,如Splash screen(程序启

动画面)、Tip of the day(今日一贴)和Windows Multimedia library(Windows

多媒体库)等

6.5.4一档多视

多数情况下,一个文档对应于一个视图,但有时一个文档可能对应多个视图,这种情况称之为“一档多视”。

1、一档多视模式

MFC对于“一档多视”提供下列3个模式

(1)在各自MDI文档窗口中包含同一个视图类的多个视图对象。用户有时需要应用程序能为同一个文档打开另一个文档窗口,以便能同时使用2个文档窗口来查看文档的不同部分内容。用MFC AppWizard创建的多个文档应用程序支持这种模式,当用户选择“窗口”菜单的“新建窗口”命令时,系统就会为第1个文档窗口创建一个副本。

(2)在同一个文档中包含同一个视图类的多个视图对象。这种模式实际上是使用“切分窗口”机制使SDI应用程序具有多视的特征。

(3)在单独一个文档窗口中包含不同视图类的多个视图对象。在该模式下,多个视图共享一个文档窗口。它有点像“切分窗口”,但由于视图可由不同的视图类构造,所以同一个文档可以有不同的显示方法。例如:同一个文档同时有文字显示方式及图形显示方式的视图

2、例:在MDI中为同一个文档数据提供2种不同的显示和编辑方式(一档多视)

294页图6.24左边的窗格中,可以调整小方块在右边窗格的坐标位置。而若在右边窗格中任意单击鼠标,相应的小方块会移动到当前鼠标位置处,且左边窗格的编辑框内容也随之发生改变。

1、创建表单应用程序,设计表单

(1)建一个多文档(MDI)项目,名为“一档多视”,第6步中将视图的基类选

择为CFormView.

(2)参看书295页的图6.24,为创建出的对话框表单IDD_MY_FORM添加以下

所列一些控件:

控 件 ID号 标 题 属 性

组框 默认 坐标设置 默认

静态文本 默认 X= 默认

编辑框 IDC_EDIT1 ------- 默认

旋转按钮 IDC_SPIN1 -------- Auto buddy,

Set buddy integet,

Alignment Right

静态文本 默认 Y= 默认

编辑框 IDC_EDIT2 -------- 默认

旋转按钮 IDC_SPIN2 --------- Auto buddy,

Set buddy integer,

Alignment Right

(3)加成员变量(在View中)

295页表6.16

2、添加CMyDoc和CMyView类代码

(1)CPoint m_ptRect; //用于记录小方块的位置

(2)m_ptRect.x=m_ptRect.y=0; //或m_ptRect=CPoint(0,0)

(3)void CMyView::OnChangeEdit()

{

UpdateData(TRUE);

CMyDoc *pDoc=(CMyDoc *)GetDocument();

pDoc->m_ptRect.x=m_CoorX;

pDoc->m_ptRect.y=m_CoorY;

CPoint pt(m_CoorX,m_CoorY);

GetDocument()->UpdateAllViews(NULL,2,(CObject *)&pt);

}

(4)void CMyView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)

{

if(lHint==1)

{

CPoint *pPoint=(CPoint *)pHint;

m_CoorX=pPoint->x;

m_CoorY=pPoint->y;

UpdateData(FALSE); //在控件中显示

CMyDoc *pDoc=(CMyDoc *)GetDocument();

//pDoc->m_ptRect.x=m_CoorX;

//pDoc->m_ptRect.y=m_CoorY;

pDoc->m_ptRect=*pPoint; //保存在文档类中的m_ptRect

}

}

(5)void CMyView::OnInitialUpdate()

{

CFormView::OnInitialUpdate();

//GetParentFrame()->RecalcLayout();

ResizeParentToFit();

CMyDoc *pDoc=(CMyDoc *)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);

m_bEditOK=TRUE;

}

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

3、处理旋转按钮控件的运行错误

(1) BOOL m_bEditOK;

(2)m_bEditOK=FALSE;

(3)加在上面OnInitialUpdate()最后加:m_bEditOK=TRUE;

(4)OnChangeEdit()最前面加:if(!m_bEditOK)return;

4、新增CDrawView 类,添加框架窗口切分功能

(1)加新类CDrawView,基类为CView

(2)CRect rect;

GetWindowRect(&rect);

BOOL bRes=m_wndSplitter.CreateStatic(this,1,2);//创建2个水平静态窗格

m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(CMyView),

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

(3)#include "一档多视View.h"

#include "DrawView.h"

(4)CSplitterWnd m_wndSplitter;

(5)编译错,是基于这样一些事实:在用标准C/C++设计程序时,有一个原则即

2个代码文件不能相互包含,而且多次包含还会造成重复定义的错误。为解

决这个难题,Visual C++使用#pragma once来通知编译器在生成时只包含(打

开)一次,也就是说,在第1次 #include之后,编译器重新生成时不会再对

这些包含文件进行包含(打开)和读取,为此在用向导创建的所有类的头文

件中都有 #pragma once这样的语句。然而正是由于这个语句而造成了在第2

次 #include后编译器无法正确识别所引用的类,从而发生错误。解决的办法

是在相互包含时加入类的声明来通知编译器这个类是一个实际的调用,如下

一步操作。

(6)class CMyDoc;//声明CMyDoc类需要再次使用

5、添加CDrawView类代码

(1)CPoint m_ptDraw;

(2)CRect rc(m_ptDraw.x-5,m_ptDraw.y-5,m_ptDraw.x+5,m_ptDraw.y+5);

pDC->Rectangle(rc);//绘制矩形,以后还会详细讨论

(3)CMyDoc *pDoc=(CMyDoc *)m_pDocument;

m_ptDraw=pDoc->m_ptRect;

(4)#include "汉字文件名Doc.h"

(5)if(lHint==2)

{

CPoint *pPoint=(CPoint *)pHint;

m_ptDraw=*pPoint;

Invalidate();

}

(6)m_ptDraw=point;

GetDocument()->UpdateAllViews(NULL,1,(CObject*)&m_ptDraw);

Invalidate();

(7)编译运行并测试,结果见295页图6.24

程序要点:

A、几个视图之间的数据传输是通过CDocument::UpdateAllView和CView::OnUpdate

的相互作用来实现的,而且,为了避免传输的相互干涉,采用提示标识值(lHint)

来区分。例如:当在CDrawView中鼠标的坐标数据经文档类调用UpdateAllView

函数传递,提示标识值为1,在CMyView类接收数据时,通过提示标识值来判

断,如第5步中(6):GetDocument()->UpdateAllViews(NULL,1,(CObject*)&m_ptDraw);

//传输数据。第2步中(4):if(lHint==1)

再如,当CMyView类中的编辑框控件数据改变之后,经文档类调用UpdateAllViews函数传递,提示标识值为2,在CDrawView类接收数据时,通过OnUpdate函数判断提示标识值来决定接收数据。

B、 为了能及时更新并保存文档数据,相应的数据成员应在用户文档类中定义。这

样,由于所有的视图类都可与文档类进行交互,因而可以共享这些数据。

附:学生档案管理程序

一、首先再介绍一下文档视图的概念:

1、文档

文档类的基类是CDocument类,它描述了应用的数据。抽象地说,文档是一个应用程序数据基本元素的集合,它构成应用程序所使用的数据单元,此外文档负责管理和维护应用的数据。具体说,文档是一种数据源,数据源有很多种,最常见的是磁盘文件,但是文档不必非要是一个磁盘文件,文档的数据源也可以来自串行口或并行口的输入数据。文档对象负责管理来自所有数据源的数据。

2、视图

视图类则继承于视类CView,它是一个基于视类的窗口视图是数据的用户窗口,为用户提供了文档的可视的数据显示,它把文档的部分或全部内容在窗口中显示出来,视图给用户提供了一个同文件中的数据进行交互的界面,它把用户的输入转化为对文档中数据的操作。

3、文档视图的关系

每个文档都会有一个或多个视图显示,一个文档可以有多个不同的视图。比如,可以将一个集合关系以饼状图的形式显示,也可以将它以数据的形式来显示。总之要把握一点:文档用来保存数据,视图用来显示数据,视图是显示出的文档

4、文档视图的交互过程

简单的说,交互过程中是以下4个函数起了关键性的作用。

1)CView类的GetDocument()函数

一个视图对象只能与一个文档对象相联系。视图类CView包含的GetDocument()函数使用户可以在视图类中得到与当前视图相联系的文档。该函数返回的是一个CDocument类或其派生类的指针。从而可以利用得到的文档指针来访问文档中的数据。例如:

CMyDoc *pDoc=GetDocument();

ASSERT_VALID(pDoc);

2)CDocument类的UpdateAllViews()函数

上面说的是当视图收到用户数据变化的消息后,会通知文档当前的变化。同样,当文档的数据发生了变化后,文档也要通知视图当前的变化,以便让视图能够及时更新,忠实反映文档的数据。这样的一件工作是通过文档类中的UpdateAllViews()函数来实现的。

3)CView类的OnUpdate()函数

文档类调用UpdateAllViews()函数来通知更新视图时,OnUpdate()函数便会被调用。该函数是一个虚函数,当该函数被调用时,它会对文档类进行访问,读取文档的数据,然后对视图进行刷新。它的原理是该过程使视图的某一个部分无效,触发了对视图类成员函数OnDraw()的调用,从而重新绘制视图客户区。默认情况下OnUpdate()函数是使整个客户区都无效。用户可以重载该函数以使视图能够反映文档的最新更新情况。

4)CView类的OnInitialUpdate()函数

该函数原型为:virtual void OnInitialUpdate(); 当应用程序被启动或者用户选择了打开文件或新建文件时,OnInitialUpdate()函数都会被调用。CView类中的该函数只是调用了OnUpdate()函数,并没有做其它方面的事,用户要对派生类的OnInitialUpdate()函数进行初始化,则可以调用基类的该函数,也可以直接调用派生类的OnUpdate()函数。

学生电子档案管理系统实现的具体界面如下:

图1:学生电子档案管理系统实现界面

图2:进入系统界面

图3:用弹出式对话框输入班级学生的姓名 图4:数据库有改动时的退出界面

二、学生档案管理程序的编写:

1、建一个单文档应用程序,名为:《学生档案管理》。第6步选取CFormView(可以在主窗口内添加控件)作为视图的基类。

2、可视化设计:

根据以下的定义,编辑对话框资源,设计的对话框如下图所示:

下面是设计完后的对话框界面布局图:

烟台南山学院Edit Box 级Edit Box专业Edit Box 斑 学生电

信息工程学院 子档案

按输入的姓名 学生电子档案

照片 登记学生信息 Edit Box

Combo Box

(组合框)

单击此处输入学生姓名

BUTTON1

对应

姓名 Edit Box

学号 Edit Box

性别 Edit Box

出生

年月 Edit Box

电话: Edit Box

职务: Edit Box

按如下约定添加控件:

对象 属性 属性值

Edit Box ID IDC_EDIT_CLAS //Static 级

Caption

Edit Box ID IDC_EDIT_PRO //Static 专业

Caption

Edit Box ID IDC_EDIT_CLA //Static 班及学习性质

Button1 ID IDC_BUTTON1 //Static 单击此处输入学生姓名

Caption

Static Text ID IDC_STATIC

Caption 对应姓名:

Edit Box ID IDC_EDIT_NAME//姓名

Caption

Static Text ID IDC_STATIC

Caption 学号:

Edit Box ID IDC_EDIT_STUID//学号

Caption

Static Text ID IDC_STATIC

Caption 姓别:

Edit Box ID IDC_EDIT_SEX//性别

Caption

Static Text ID IDC_STATIC

Caption 出生年月:

Edit Box ID IDC_EDIT_AGE//出生年月

Caption

Static Text ID IDC_STATIC

Caption 电话:

Edit Box ID IDC_EDIT_TEL//电话

Caption

Static Text ID IDC_STATIC

Caption 职务:

Edit Box ID IDC_EDIT_POS//职务

Caption

Static Text ID IDC_STATIC

Caption 按输入的姓名

Static Text ID IDC_STATIC

Caption 登记学生信息

Combo Box ID IDC_COMBO

Type Dropdown

Sort Checked(stylesw)//选上

VerticalScroll Checked(styles) //选上

Static Text ID IDC_STATIC

Caption 学生电子档案:

Edit Box ID IDC_EDIT_SCHOOL

Multiline Checked //选上

Horizontal Checked //选上

Vertical scroll Checked //选上

3、给文档类添加成员变量

文档类中的数据就是要操作的对象,所以必须将其定义为文档类的数据成员。

(1)修改CMyDoc类的定义

在CMyDoc.h中(public:下)定义一个结构体:

public:

struct

{

char clas[10]; //级

char pro[20]; //专业

char cla[20]; //班及学习性质

char name[20]; //对应姓名

char stuid[20]; //学号

char sex[10]; //性别

char age[20]; //出生年月

char tel[20]; //电话

char pos[20]; //职务

char school[9999];//学生电子档案,最多写9999个字符

}m_student[80]; //一个班最多80个学生

(2)初始化变量

在CMyDoc.cpp的构造函数中,对这些变量进行初始化

for(int i=0;i<80;i++)

{

m_student[i].clas[0]=NULL;//级

m_student[i].pro[0]=NULL;//专业

m_student[i].cla[0]=NULL;//班及学习性质

m_student[i].name[0]=NULL; //对应姓名

m_student[i].stuid[0]=NULL;//学号

m_student[i].sex[0]=NULL;//性别

m_student[i].age[0]=NULL;//出生年月

m_student[i].tel[0]=NULL;//电话

m_student[i].pos[0]=NULL;//职务

m_student[i].school[0]=NULL;//学生电子档案

}

4、给视图类添加成员变量和记录姓名的变量

视图类是负责屏幕的显示内容。在此,要加入的视图类数据成员实际上就是文档类的数据成员在屏幕上的映射。而将视图类中的变量显示在屏幕上最简捷的方法是:将变量与某编辑控件关联,即为编辑框引入变量。

(1)为IDD_MY_FORM对话框中的各个控件引入变量(view类中)

利用MFC ClassWizard,按下面定义,为控件引入变量:

控件ID 类型 关联变量 说明

IDC_EDIT_CLAS CString m_clas 级

IDC_EDIT_PRO CString m_pro 专业

IDC_EDIT_CLA CStrint m_cla 班及学习性质

IDC_EDIT_NAME CString m_name 对应姓名

IDC_EDIT_STUID CString m_stuid 学号

IDC_EDIT_SEX CString m_sex 性别

IDC_EDIT_AGE CString m_age 出生年月

IDC _EDIT_TEL CString m_tel 电话号码

IDC_EDIT_POS CString m_pos 职务

IDC_COMBO CComboBox m_noList按输入的姓名登记学生信息(组合框)

IDC_EDIT_SCHOOL CString m_school 学生电子档案

操作如下:

ViewàClassWizardàMember Variables(加成员变量)àCMyView(在视图里)à点黑IDC_COMBOàAdd Variable…à写成员变量名m_noListà注意类型用Category:下拉成control变为CComboBox类型(其它变量的类型可下拉Variable Type改变成:int或CString类型等)

按如上步骤完成上面各控件的变量引入。

(2)添加一个记录姓名的变量

在CMyView.h里:

class CMyView : public CFormView

{ int m_nCurrentNo; //这条是加的记录姓名的变量,在私有模式下。

protected: // create from serialization only

CMyView();

DECLARE_DYNCREATE(CMyView)

………..

}

5、变量初始化

(1)修改OnInitialUpdate()函数,做2个工作:

1)设置Combo Box”姓名”

2)将文档类中变量赋给对应的编辑控件变量,在View.cpp里

void CMyView::OnInitialUpdate()

{

CFormView::OnInitialUpdate();

GetParentFrame()->RecalcLayout();

ResizeParentToFit();

CMy1Doc *pDoc = GetDocument();

m_noList.ResetContent();//删除组合框的全部项和编辑文本

for (int i=0; i<80; i++)

{

if(strcmp(pDoc->m_student[i].name, ""))//如果对应姓名空

//向组合框尾部添加对应姓名字符串(nIndex=-1)

m_noList.InsertString(-1, pDoc->m_student[i].name);

//将对应的姓名pDoc->m_student[i].name,添到组合框末尾

}

m_noList.SetCurSel(0);//设置当前选择项(参数为当前选择项索引)

m_clas = pDoc->m_student[0].clas;//级

m_pro = pDoc->m_student[0].pro; //专业

m_cla = pDoc->m_student[0].cla; //班及学习性质

m_name = pDoc->m_student[0].name;//对应姓名

m_stuid = pDoc->m_student[0].stuid;//学号

m_sex = pDoc->m_student[0].sex;//性别

m_age = pDoc->m_student[0].age;//出生年月

m_tel = pDoc->m_student[0].tel;//电话

m_pos = pDoc->m_student[0].pos;//职务

m_school = pDoc->m_student[0].school;//学生电子档案

UpdateData(false);

}

(2)在构造函数(View.cpp里)对变量m_nCurrentNo(记录学号的变量)初始化

CMyView::CMyView()

: CFormView(CMyView::IDD)

{

//{{AFX_DATA_INIT(CMyView)

m_clas = _T("");

m_pro = _T("");

m_cla = _T("");

m_name = _T("");

m_stuid = _T("");

m_sex = _T("");

m_tel = _T("");

m_pos = _T("");

m_age = _T("");

m_school = _T("");

//}}AFX_DATA_INIT

// TODO: add construction code here

m_nCurrentNo=0; //在这加的

}

6、用另一个对话框输入全班学生的姓名(见图3)

(1)插入一个对话框

点击工作区ResourceViewà右键单击DialogàInsert Dialogà见已插入一个对话框,ID为缺省IDD_DIALOG1,将OK改为“确定”,将CANCEL改为“取消”,

拖一个静态文本static写“请逐个输入学生姓名”,拖一个编辑框Edit Box,ID为IDC_EDIT_NUM

(2)为这个对话框建类

双击这个对话框àOKà类名写CinDlogàOK退出

(3)用ViewàClassWizard为这个对话框上的编辑框IDC_EDIT_NUM建成员变量m_num 类型为:CString

7、弹出对话框并完成从对话框的编辑框向组框加入姓名的工作

(1)将BUTTON1映射消息加入View.cpp中,并将对话框类inDlog包含在View

中。

(2)加入下列代码

void CMy1View::OnButton1()

{

// TODO: Add your control notification handler code here

UpdateData(true);

//将姓名插入到组合框

CinDlog dlg;

//dlg.DoModal();

dlg.m_num.TrimLeft();

dlg.m_num.TrimRight();

if(IDOK==dlg.DoModal())

{

if (dlg.m_num.IsEmpty())

{

MessageBox("姓名不能为空!!");

return;

}

if ((m_noList.FindStringExact(-1, dlg.m_num))!=LB_ERR)

{

MessageBox("列表中已有相同的姓名,不能添加!!");

return;

}

}

m_noList.InsertString(-1, dlg.m_num); //向组合框尾部添加字符串

//设置当前选择项(获取组合框的项数)

m_noList.SetCurSel(m_noList.GetCount()-1);

m_nCurrentNo = m_noList.GetCurSel();//获得当前选择项的索引

//m_clas=” “;//级 这三个框不充零,一个班所有学生只写一次即可

//m_pro=” “//专业

//m_cla=” “;//学习性质与班级

m_name=" ";//姓名

m_stuid=” “;//学号

m_sex=" ";//性别

m_age=" ";//出生年月

m_tel=" ";//电话

m_pos=" ";//职务

m_school=" ";//学生电子档案

UpdateData(false);

}

8、处理数据记录的录入(见图4)

实现数据记录的编辑,就是处理编辑框的EN_CHANGE(见第5章最后部分说明)事件。为此,要为编辑框的EN_CHANGE事件添加响应函数,且要在消息响应函数中编写将编辑的数据传给文档类对应的变量代码。

(1)添加消息响应函数的操作步骤:

ViewàClassWizardàMessage MapsàCMyView(在视图类)àIDC_EDIT_NAME

àEN_CHANGEàAdd Function…àOK(函数加在视图类文件末尾)

按如上步骤将以下各消息响应函数添加到View.cpp中:

控件ID 消息(事件) 函数 说明

IDC_EDIT_CLAS EN_CHANGE OnChangeEditClas 编辑与显示级

IDC_EDIT_PRO EN_CHANGE OnChangeEditPro 编辑与显示专业

IDC_EDIT_CLA EN_CHANGE OnChangeEditCla编辑与显示班及学习性质

IDC_EDIT_NAME EN_CHANGE OnChangeEditName编辑与显示对应姓名

IDC_EDIT_STUID EN_CHANGE OnChangeEditStuid 编辑与显示学号

IDC_EDIT_SEX EN_CHANGE OnChangeEditSex 编辑与显示性别

IDC_EDIT_AGE EN_CHANGE OnChangeEditAge 编辑显示出生年月

IDC_EDIT_TEL EN_CHANGE OnChangeEditTel 编辑与显示电话号码

IDC_EDIT_POS EN_CHANGE OnChangeEditPos 编辑与显示职务

IDC_COMBO CBN_SELCHANGE OnSelchangeCombo 选择姓名的组合框

IDC_EDIT_SCHOOL EN_CHANGE OnChangeEditSchool 编辑与显示学生的电子档案

(2)为函数OnChangeEditName()加代码

void CMyView::OnChangeEditName() //对应姓名

{

// TODO: If this is a RICHEDIT control, the control will not

// send this notification unless you override the CFormView::OnInitDialog()

// function and call CRichEditCtrl().SetEventMask()

// with the ENM_CHANGE flag ORed into the mask.

CMyDoc *pDoc = GetDocument();//获取指向文档类对象的指针,用于

//操作文档类中的数据。

UpdateData(true);//用控件的值去更新与之关联的变量,从而使

//m_name的值与其关联的"对应姓名"编辑框一致

//下面的if语句:是当对应的变量值有所改变时,进行值的传递,

//这样保证文档类与视图类相对应的变量值时刻保持一致。同时设置

//数据修改标志,当用户退出程序时,程序会自动提示是否将数据保存

if(strcmp(pDoc->m_student[m_nCurrentNo].name,m_name))

{ strcpy(pDoc->m_student[m_nCurrentNo].name,m_name);

pDoc->SetModifiedFlag();

}

// TODO: Add your control notification handler code here

}

(3)为OnChangeEditStuid()函数添代码

void CMyView::OnChangeEditStuid() //学号

{

// TODO: If this is a RICHEDIT control, the control will not

// send this notification unless you override the CFormView::OnInitDialog()

// function and call CRichEditCtrl().SetEventMask()

// with the ENM_CHANGE flag ORed into the mask.

CMyDoc *pDoc = GetDocument();//获取指向文档类对象的指针,用于

//操作文档类中的数据。

UpdateData(true);//用控件的值去更新与之关联的变量,从而使

//m_stuid的值与其关联的"学号"编辑框一致

if(strcmp(pDoc->m_student[m_nCurrentNo].stuid,m_stuid))

{

strcpy(pDoc->m_student[m_nCurrentNo].stuid,m_stuid);

pDoc->SetModifiedFlag();//设置数据修改标志

}

// TODO: Add your control notification handler code here

}

(4)为OnChangeEditClas()函数添代码//级

void CMyView::OnChangeEditClas() //级

{

// TODO: If this is a RICHEDIT control, the control will not

// send this notification unless you override the CFormView::OnInitDialog()

// function and call CRichEditCtrl().SetEventMask()

// with the ENM_CHANGE flag ORed into the mask.

CMyDoc *pDoc = GetDocument();//获取指向文档类对象的指针,用于

//操作文档类中的数据。

UpdateData(true);//用控件的值去更新与之关联的变量,从而使

//m_clas的值与其关联的"级"编辑框一致

if(strcmp(pDoc->m_student[m_nCurrentNo].clas,m_clas))

{

strcpy(pDoc->m_student[m_nCurrentNo].clas,m_clas);

pDoc->SetModifiedFlag();//设置数据修改标志

}

// TODO: Add your control notification handler code here

}

(5)为OnChargeEditPro()函数添代码

void CMyView::OnChangeEditPro() //专业

{

// TODO: If this is a RICHEDIT control, the control will not

// send this notification unless you override the CFormView::OnInitDialog()

// function and call CRichEditCtrl().SetEventMask()

// with the ENM_CHANGE flag ORed into the mask.

CMyDoc *pDoc = GetDocument();//获取指向文档类对象的指针,用于

//操作文档类中的数据。

UpdateData(true);//用控件的值去更新与之关联的变量,从而使

//m_pro的值与其关联的"专业"编辑框一致

if(strcmp(pDoc->m_student[m_nCurrentNo].pro,m_pro))

{

strcpy(pDoc->m_student[m_nCurrentNo].pro,m_pro);

pDoc->SetModifiedFlag();//设置数据修改标志

}

// TODO: Add your control notification handler code here

}

(6)为OnChangeEditCla()函数添代码

void CMyView::OnChangeEditCla()//班及学习性质

{

// TODO: If this is a RICHEDIT control, the control will not

// send this notification unless you override the CFormView::OnInitDialog()

// function and call CRichEditCtrl().SetEventMask()

// with the ENM_CHANGE flag ORed into the mask.

CMyDoc *pDoc = GetDocument();//获取指向文档类对象的指针,用于

//操作文档类中的数据。

UpdateData(true);//用控件的值去更新与之关联的变量,从而使

//m_cla的值与其关联的"班及学习性质"编辑框一致

if(strcmp(pDoc->m_student[m_nCurrentNo].cla,m_cla))

{

strcpy(pDoc->m_student[m_nCurrentNo].cla,m_cla);

pDoc->SetModifiedFlag();//设置数据修改标志

}

// TODO: Add your control notification handler code here

}

(7)为OnChangeEditAge()函数添代码

void CMyView::OnChangeEditAge() //出生年月

{ // TODO: If this is a RICHEDIT control, the control will not

// send this notification unless you override the CFormView::OnInitDialog()

// function and call CRichEditCtrl().SetEventMask()

// with the ENM_CHANGE flag ORed into the mask.

CMyDoc *pDoc = GetDocument();//获取指向文档类对象的指针,用于

//操作文档类中的数据。

UpdateData(true);//用控件的值去更新与之关联的变量,从而使

//m_age的值与其关联的"出生年月"编辑框一致

if(strcmp(pDoc->m_student[m_nCurrentNo].age,m_age))

{

strcpy(pDoc->m_student[m_nCurrentNo].age,m_age);

pDoc->SetModifiedFlag();//设置数据修改标志

}

// TODO: Add your control notification handler code here

}

(8)为OnChangeEditSchool()函数添代码

void CMyView::OnChangeEditSchool() //编辑与显示学生电子档案

{

// TODO: If this is a RICHEDIT control, the control will not

// send this notification unless you override the CFormView::OnInitDialog()

// function and call CRichEditCtrl().SetEventMask()

// with the ENM_CHANGE flag ORed into the mask.

CMyDoc *pDoc = GetDocument();//获取指向文档类对象的指针,用于

//操作文档类中的数据。

UpdateData(true);//用控件的值去更新与之关联的变量,从而使

//m_school的值与其关联的"学生情况"编辑框一致

if(strcmp(pDoc->m_student[m_nCurrentNo].school,m_school))

{

strcpy(pDoc->m_student[m_nCurrentNo].school,m_school);

pDoc->SetModifiedFlag();//设置数据修改标志

}

// TODO: Add your control notification handler code here

}

(9)为OnChangeEditSex()函数添代码

void CMyView::OnChangeEditSex() //性别

{

// TODO: If this is a RICHEDIT control, the control will not

// send this notification unless you override the CFormView::OnInitDialog()

// function and call CRichEditCtrl().SetEventMask()

// with the ENM_CHANGE flag ORed into the mask.

CMyDoc *pDoc = GetDocument();//获取指向文档类对象的指针,用于

//操作文档类中的数据。

UpdateData(true);//用控件的值去更新与之关联的变量,从而使

//m_sex的值与其关联的"性别"编辑框一致

if(strcmp(pDoc->m_student[m_nCurrentNo].sex,m_sex))

{

strcpy(pDoc->m_student[m_nCurrentNo].sex,m_sex);

pDoc->SetModifiedFlag();//设置数据修改标志

}

// TODO: Add your control notification handler code here

}

(10)为OnChangeEditTel()函数添代码

void CMyView::OnChangeEditTel() //电话

{

// TODO: If this is a RICHEDIT control, the control will not

// send this notification unless you override the CFormView::OnInitDialog()

// function and call CRichEditCtrl().SetEventMask()

// with the ENM_CHANGE flag ORed into the mask.

CMyDoc *pDoc = GetDocument();//获取指向文档类对象的指针,用于

//操作文档类中的数据。

UpdateData(true);//用控件的值去更新与之关联的变量,从而使

//m_tel的值与其关联的"电话"编辑框一致

if(strcmp(pDoc->m_student[m_nCurrentNo].tel,m_tel))

{

strcpy(pDoc->m_student[m_nCurrentNo].tel,m_tel);

pDoc->SetModifiedFlag();//设置数据修改标志

}

// TODO: Add your control notification handler code here

}

(11)为OnChangeEditPos()函数加代码

void CMyView::OnChangeEditPos() //职务

{

// TODO: If this is a RICHEDIT control, the control will not

// send this notification unless you override the CFormView::OnInitDialog()

// function and call CRichEditCtrl().SetEventMask()

// with the ENM_CHANGE flag ORed into the mask.

CMyDoc *pDoc = GetDocument();//获取指向文档类对象的指针,用于

//操作文档类中的数据。

UpdateData(true);//用控件的值去更新与之关联的变量,从而使

//m_pos的值与其关联的"职务"编辑框一致

if(strcmp(pDoc->m_student[m_nCurrentNo].pos,m_pos))

{

strcpy(pDoc->m_student[m_nCurrentNo].pos,m_pos);

pDoc->SetModifiedFlag();//设置数据修改标志

}

// TODO: Add your control notification handler code here

}

(12)为OnSelchangeCombo()函数加代码

void CMyView::OnSelchangeCombo() //选择学号的组合框

{

// TODO: Add your control notification handler code here

CMyDoc *pDoc = GetDocument();//获取指向文档类对象的指针,用于

//操作文档类中的数据。

UpdateData(true);

CinDlog dlg;

m_nCurrentNo = m_noList.GetCurSel();//获得当前选择项的索引

int i=m_noList.GetCurSel();

//m_clas = pDoc->m_student[m_nCurrentNo].clas;//级 这三条和上面一样

//m_pro = pDoc->m_student[m_nCurrentNo].pro;//专业 注释掉,使一个班

//m_cla = pDoc->m_student[m_nCurrentNo].cla;//班及学习性质 只输一次

dlg.m_num = pDoc->m_student[m_nCurrentNo].name; //文档的姓名赋给对话框//上的编辑框

m_name = pDoc->m_student[m_nCurrentNo].name;// 文档姓名赋给视图成员变量

m_stuid = pDoc->m_student[m_nCurrentNo].stuid;// 文档学号

m_sex = pDoc->m_student[m_nCurrentNo].sex;// 文档性别

m_age = pDoc->m_student[m_nCurrentNo].age;// 文档出生年月

m_tel = pDoc->m_student[m_nCurrentNo].tel;// 文档电话

m_pos = pDoc->m_student[m_nCurrentNo].pos;// 文档职务

m_school=pDoc->m_student[m_nCurrentNo].school;// 文档学生电子档

// 到这,要在视图类View.h的public里加一位图类变量:CBitmap m_bitmap;

// 见上面5、CFormView类应用:”显示背景图”,来显示照片。

// 下面是添加照片,照片为.bmp类型,大小为166*149,用photoshop处理好

if(i==0)

{

CDC *pDC=GetDC();

HBITMAP hBitmap=(HBITMAP)LoadImage(NULL,_T("X06jyz400.bmp"),

IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_DEFAULTSIZE|

LR_LOADFROMFILE);

m_bitmap.Attach(hBitmap);

BITMAP bm;

m_bitmap.GetBitmap(&bm);//注意此处好出错,与你加的图形有关。

CDC dcImage;

if(!dcImage.CreateCompatibleDC(pDC))

return;

CBitmap *pOldBitmap=dcImage.SelectObject(&m_bitmap);

pDC->BitBlt(0,0,bm.bmWidth,bm.bmHeight,&dcImage,0,0,SRCCOPY);

//调用BitBlt()函数显示照片

dcImage.SelectObject(pOldBitmap);

DeleteObject(m_bitmap.Detach());//释放位图存储空间

}

if(i==1)

{

CDC *pDC=GetDC();

HBITMAP hBitmap=(HBITMAP)LoadImage(NULL,_T("X06jyz401.bmp"),

IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_DEFAULTSIZE|

LR_LOADFROMFILE);

m_bitmap.Attach(hBitmap);

BITMAP bm;

m_bitmap.GetBitmap(&bm);//注意此处好出错,与你加的图形有关。

CDC dcImage;

if(!dcImage.CreateCompatibleDC(pDC))

return;

CBitmap *pOldBitmap=dcImage.SelectObject(&m_bitmap);

pDC->BitBlt(0,0,bm.bmWidth,bm.bmHeight,&dcImage,0,0,SRCCOPY);

//调用BitBlt()函数显示照片

dcImage.SelectObject(pOldBitmap);

DeleteObject(m_bitmap.Detach());//释放位图存储空间

}

if(i==2)

{

CDC *pDC=GetDC();

HBITMAP hBitmap=(HBITMAP)LoadImage(NULL,_T("X06jyz402.bmp"),

IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_DEFAULTSIZE|

LR_LOADFROMFILE);

m_bitmap.Attach(hBitmap);

BITMAP bm;

m_bitmap.GetBitmap(&bm);//注意此处好出错,与你加的图形有关。

CDC dcImage;

if(!dcImage.CreateCompatibleDC(pDC))

return;

CBitmap *pOldBitmap=dcImage.SelectObject(&m_bitmap);

pDC->BitBlt(0,0,bm.bmWidth,bm.bmHeight,&dcImage,0,0,SRCCOPY);

//调用BitBlt()函数显示照片

dcImage.SelectObject(pOldBitmap);

DeleteObject(m_bitmap.Detach());//释放位图存储空间

}

if(i==3)

{

}

UpdateData(false);

}

注意:下面的13、14可以不做

(13)在工具栏中添加“打开”和“另存为”按钮

学生档案写完后,要保存,而用到时要打开。故要设计“打开”和“保存”控件。具体作法如下:

1)在Workspace窗口的资源(ResourceView)列表中,双击“Toolbar”下的“ID_MAINFRAME”项,打开工具拦编辑器,单击最后一个空白按钮,画上适当图形,再双击这个按钮,ID处写:ID_FILE_MYOPEN,下面:Prompt处写:

打开\n打开。(“\n”之前是程序运行时状态拦的提示信息,“ \n “之后为鼠标光标移动至该按钮时光标下方给出的提示信息)

2)按上步骤再加一个保存按钮:ID_FILE_MYSAVE,Prompt处写:保存\n保存

(14)在CMyView类中添加消息响应函数

1)添加“打开”消息响应函数

ViewàClassWizardàMessage MapsàClass name:CMyViewàObject IDs:

ID_FILE_MYOPENàMessage:COMMANDàAdd FunctionàEdit Code开始加代码:

void CMyView::OnFileMyopen()

{

// TODO: Add your command handler code here

CString strFilter="All Files(*.*)|*.*|Dat File(*.dat)|*.dat||";//打开文件对话框

CFileDialog FileDlg(true,NULL,NULL,OFN_HIDEREADONLY|

OFN_OVERWRITEPROMPT,(LPCSTR)strFilter,this);

if(FileDlg.DoModal()!=IDOK) return;

CString strFileName=FileDlg.GetPathName();

CFile f;

if(!f.Open(strFileName,CFile::modeRead))

{

AfxMessageBox("打开文件失败");

return;

}

CMyDoc *pDoc=GetDocument();//读出文件中的数据,存放

//到文档类的数据成员中

f.Read(&m_nCurrentNo,sizeof(int));

for(int i=0;i<80;i++)

{

f.Read(pDoc->m_student[i].clas,10);//级

f.Read(pDoc->m_student[i].pro,20);//专业

f.Read(pDoc->m_student[i].cla,10);//班

f.Read(pDoc->m_student[i].ins,20);//对应学号

f.Read(pDoc->m_student[i].name,10);

f.Read(pDoc->m_student[i].sex,3);

f.Read(pDoc->m_student[i].age,4);

f.Read(pDoc->m_student[i].tel,14);

f.Read(pDoc->m_student[i].pos,20);//职务

f.Read(pDoc->m_student[i].school,999);//学生电子档案

}

f.Close();

//将文档类的数据传递给视图类数据成员,并显示

m_noList.SetCurSel(m_nCurrentNo);//设置当前选择项

m_clas=pDoc->m_student[m_nCurrentNo].clas;//级

m_pro=pDoc->m_student[m_nCurrentNo].pro;//专业

m_cla=pDoc->m_student[m_nCurrentNo].cla;//班

m_ins=pDoc->m_student[m_nCurrentNo].ins;//对应学号

m_name=pDoc->m_student[m_nCurrentNo].name;

m_sex=pDoc->m_student[m_nCurrentNo].sex;

m_age=pDoc->m_student[m_nCurrentNo].age;

m_tel=pDoc->m_student[m_nCurrentNo].tel;

m_pos=pDoc->m_student[m_nCurrentNo].pos; //职务

m_school=pDoc->m_student[m_nCurrentNo].school;//学生电子档案

UpdateData(false);

}

2)添加“保存”消息响应函数(按如上步骤将“保存”函数加到View.cpp里,并加代码:

void CMyView::OnFileMysave()

{

// TODO: Add your command handler code here

CString strFilter="All File(*.*)|*.*|Dat File(*.dat)|*.dat||";

CFileDialog FileDlg(false,NULL,NULL,OFN_HIDEREADONLY|

OFN_OVERWRITEPROMPT,(LPCSTR)strFilter,this);

if(FileDlg.DoModal()!=IDOK) return;

CString strFileName=FileDlg.GetPathName();

CFile f;

if(!f.Open(strFileName,CFile::modeCreate|CFile::modeWrite))

{

AfxMessageBox("创建文件失败");

return;

}

CMyDoc *pDoc=GetDocument();

f.Write(&m_nCurrentNo,sizeof(int));

for(int i=0;i<80;i++)

{

f.Write(pDoc->m_student[i].clas,10);//级

f.Write(pDoc->m_student[i].pro,20);//专业

f.Write(pDoc->m_student[i].cla,10);//班

f.Write(pDoc->m_student[i].ins,20);//对应学号

f.Write(pDoc->m_student[i].name,10);

f.Write(pDoc->m_student[i].sex,10);

f.Write(pDoc->m_student[i].age,10);

f.Write(pDoc->m_student[i].tel,20);

f.Write(pDoc->m_student[i].pos,20);//职务

f.Write(pDoc->m_student[i].school,9999);//学生电子档案

}

f.Close();

}

(15)添加串行化存储和装入

程序运行后,你按学号写入一斑学生的信息,并可按个浏览。但这样退出程序后一斑学生的信息就没了,为了将学生的信息保留下来,并在用到时还能打开,你要进行串行化存储和装入:在CMyDoc.cpp文件的Serialize(CArchive &ar)函数里加:

void CMyDoc::Serialize(CArchive& ar)

{

if (ar.IsStoring())

{

for(int i=0;i<80;i++)

{

ar.Write(m_student[i].clas,10);//级

ar.Write(m_student[i].pro,20);//专业

ar.Write(m_student[i].cla,20);//班及学习性质

ar.Write(m_student[i].name,20);//对应姓名

ar.Write(m_student[i].stuid,20);//学号

ar.Write(m_student[i].sex,10);//性别

ar.Write(m_student[i].age,20);//出生年月

ar.Write(m_student[i].tel,20);//电话

ar.Write(m_student[i].pos,20);//职务

ar.Write(m_student[i].school,9999);//学生电子档案

}

// TODO: add storing code here

}

else

{

for(int i=0;i<80;i++)

{

ar.Read(m_student[i].clas,10);//级

ar.Read(m_student[i].pro,20);//专业

ar.Read(m_student[i].cla,20);//班及学习性质

ar.Read(m_student[i].name,20);//对应姓名

ar.Read(m_student[i].stuid,20);//学号

ar.Read(m_student[i].sex,10);//性别

ar.Read(m_student[i].age,20);//出生年月

ar.Read(m_student[i].tel,20);//电话

ar.Read(m_student[i].pos,20);//职务

ar.Read(m_student[i].school,9999);//学生电子档案

}

// TODO: add loading code here

}

}

程序正确运行后,你往里敲学生信息,敲完后à保存(起一个文件名:”一斑学生信息”),接着可浏览à退出。再运行此程序时,只见是空的信息,你要à文件à打开à”一斑学生信息”,便显示出一斑的学生信息了,接着你还可以进行添加修改等工作。

如果你再建二班学生信息,你可在再运行此程序时,直接敲进二班学生信息,之后再起名:”二班学生信息”,其它如此类推。

各个班都建好后,你进入e盘的vcpp文件夹,找到这个程序,点开,在Debug里把带图形的那个执行文件拷出来,它就可当项目执行了。

注意:要为本软件设置密码(口令),请按第五章“口令设置实例”进行,见图2

组合框介绍:

本例使用了组合框来存储与显示学号。组合框可以看作是一个编辑框或静态文本框与一个列表框的组合,组合框的名称也正是由此而来的。当前选定的项将显示在组合框的编辑框或静态文本框中,如果组合框具有下拉列表(drop-down list)样式,则用户可以在编辑框中键入列表框中某一项的首字母,在列表框可见时,与该字母相匹配的最近的项将被加亮显示,在绘制组合框时,可以使用控件的”Properties”对话框设置控件的各种属性式样。如:

选项 风格

Simple 创建包括编辑框控件和列表框架的简单组合框,其中编辑框控件用来接受用户的输入

Dropdown 创建下拉组合框,该类型与简单组合框类似,但仅当用户单击编辑框控件部分右边的下拉箭头时,组合框的列表框部分才被显示。

Drop List 该类型类似于下拉样式,只是使用静态文本项代替编辑框控件来显示列表框中的当前选项。

Combo Box控件与CComboBox类关联。CComboBox类常见的成员函数如下:

函数 说明

GetCount 获得组合框中列表框项的数目

GetCurSet 返回组合框中列表框的当前选定项的索引

SetCurSel 设置组合框中列表框的一个字符串

DeleteString 从组合框的列表框中删除一个字符串

InsertString 向组合框的列表中插入一个字符串

AddString 向组合框的列表中添加一个字符串

SelectString 在组合框的列表框中查找字符串,如果找到,在列表中选择该字符串,并复制到编辑框控件中。

CArchive类:

CArchive类没有基类,它提供了串行化对象从文件中读写的类型安全缓冲机制,可以把CArchive对象想象成一种二进制流,就象输入/输出流一样可以顺序高效的处理二进制对象数据。使用CArchive对象之前,必须先创建一个CFile对象,同时保证CArchive对象的读写标志设置和文件打开方式相一致。对于一个CArchive对象,可以进行存储操作,也可以读取,但不能两者同时进行。

CArchive类的成员函数

Read、Write 读写指定字节数的缓冲区内容

ReadString、WriteString 读写一行文本

ReadObject、WriteObject 调用一个对象的Serialize函数来读或写

ReadClass、WriteClass 读写一个CRuntimeClass指明对象

IsLoading、IsStoring 判断当前读写状态

Serialize()函数

在“MFC AppWizard”自动生成的程序框架中,文件的串行化操作操作都是由CDocument派生类的成员函数Serialize()完成。

在这个成员函数中,使用CArchive对象完成具体的操作。参数中传递进来的CArchive引用对象,是由MFC程序框架根据用户输入需要执行串行化操作时创建的,它包含了所需要的文件的信息,使用它可以对各种CArchive类支持的数据格式进行读写、调用其他CObject派生类对象的串行化函数等。

“MFC AppWizard”自动生成的代码已经完成的工作是:用户选择“打开”、“保存”或“另存为”等命令时,程序框架创建着这个文件的CFile对象,将它关联到新创建的CArchive对象上,并设置CArchive对象的“Store”或“Load”标志,用这个对象来调用CDocument派生类的Serialize()成员函数。在Serialize()函数完成读写操作返回后,自动删除Serialize()函数和CFile对象。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页