单文档界面与多文档界面
- 单文档程序指程序只可以打开一个文档,如:记事本
- 多文档程序指可以同时打开多个文档,如:word
创建单文档应用程序
创建单文档应用程序需要使用应用程序向导功能。使用该功能,可以方便地创建应用程序的框架。程序员可以在此基础上添加实现预定功能的代码,从而创建实现预定功能的应用程序。
- 选择project、MFC AppWizard(exe)、single document、下一步默认。
- 最后一步,选择基类,改变CTestView的基类为CEditView,就可以变为记事本一样的程序。
- 单文档程序的核心:消息传递
文档/视图结构分析
文档、视图框架通过联系几个不同的类实现整个应用程序,他们分别是应用程序类CWinApp\框架窗口类CFrameWnd、视图类CView、文档类CDocument类和CDocTemplate类。
- 主程序类CWinApp:负责进程的启动、终止、消息循环和资源管理。在整个应用程序中利用CWinApp的成员函数InitInstance进入MFC程序,同时其成员函数还包括消息循环、加载图标等。由于整个框架已经建立,一般对CWinApp不需要改变。
- 文档类的基类CDocument:提供基本操作:设置文档标题、建立新文档、打开新文档等。在CDocument类中最重要的两个函数时SetModifiedFlag和UpdateAllViews。前者设置一个标志位,一般在文档修改时调用该函数,当文档关闭时提醒用户保存修改的内容。后者是刷新所有和文档关联的视图,以保证显示的是最新内容。
- 文档类CView:最常用的是OnDraw,该函数在屏幕发生变化或因为焦点的变化需要重绘时调用。没有该函数,就不能保证程序切换后保证屏幕的正确显示,若想在数据更新时强制视图更新,可调用Invalidate和UpdateWindow来实现。
- 文档模板类CDocTemplate:将独立的文档、视图和框架窗口对象联系在一起。
- 框架窗口类CFrameWnd:负责框架窗口的维护工作,例如工具栏、菜单、状态栏的显示和更新。
单文档程序实战
- 新建单文档test,最后一步改变CTestView类基类为CFormView类。
- 添加控件
- 为控件添加数据成员
- 为按钮添加响应函数
- 类向导,添加CStudent,选择CEdit派生,这样可以使类串行化操作。在类中添加变量信息:
// Attributes
public:
CString add; //家庭住址
int age; //年龄
CString name; //姓名
int num; //学号
CString sex; //性别
CString tel; //电话号码
// Operations
- 在Student.cpp中编写函数Serialize:
void CStudent::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{ // storing code
ar<<add<<age<<name<<num<<sex<<tel; //输入文件中
}
else
{ // loading code
ar>>add>>age>>name>>num>>sex>>tel; //从文件中读取
}
}
- 在Student类中声明Serialize
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CStudent)
public:
virtual void Serialize(CArchive& ar);
//}}AFX_VIRTUAL
// Implementation
- 在类CTestDoc.h中添加数据成员:
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "student.h" //+++++++++++++++++++添加头文件
#define N 33 //++++++++++++++++++++++++++++添加1
class CTestDoc : public CDocument
{
protected: // create from serialization only
CTestDoc();
DECLARE_DYNCREATE(CTestDoc)
// Attributes
public:
CStudent student[N]; //++++++++++添加2,对象数组
// Operations
- 编写消息响应函数:
void CTestView::Onsave()
{
// TODO: Add your control notification handler code here
CTestDoc *pdoc=GetDocument();
UpdateData(true); //数据更新
int number=m_num-1;
if(m_num>=34||m_num<=1)
AfxMessageBox("学号需处于1-34之间"); //弹出消息参考
else
{
pdoc->student[number].add=m_add; //数据保存在对象数组中
pdoc->student[number].age=m_age;
pdoc->student[number].name=m_name;
pdoc->student[number].num=m_num;
pdoc->student[number].sex=m_sex;
pdoc->student[number].tel=m_tel;
}
}
void CTestView::Onundo()
{
// TODO: Add your control notification handler code here
//编辑框内内容清空
m_add="";
m_age=0;
m_name="";
m_sex="";
m_tel="";
m_num=0;
UpdateData(false); //更新显示
}
void CTestView::Onlast()
{
// TODO: Add your control notification handler code here
AfxMessageBox("输入学号单击\"上一个\"按钮可以查看上一条记录");
UpdateData(true);
if(m_num<=0)
AfxMessageBox("没有上一个");
else
{
CTestDoc *pdoc=GetDocument();
UpdateData(true); //更新数据
int number=m_num-2;
//在对象数组中读取数据
m_add=pdoc->student[number].add;
m_age=pdoc->student[number].age;
m_name=pdoc->student[number].name;
m_num=pdoc->student[number].num;
m_sex=pdoc->student[number].sex;
m_tel=pdoc->student[number].tel;
UpdateData(false);
}
}
void CTestView::Onnext()
{
// TODO: Add your control notification handler code here
AfxMessageBox("输入学号单击\"下一个\"按钮可以查看下一条记录");
UpdateData(true);
if(m_num>=34)
AfxMessageBox("没有下一个");
else
{
CTestDoc *pdoc=GetDocument(); //得到当前文档指针
UpdateData(true);
int number=m_num;
//在对象数组中读取数据
m_add=pdoc->student[number].add;
m_age=pdoc->student[number].age;
m_name=pdoc->student[number].name;
m_num=pdoc->student[number].num;
m_sex=pdoc->student[number].sex;
m_tel=pdoc->student[number].tel;
UpdateData(false); //数据更新
}
}
- 在CTestDoc.cpp中实现串行处理:
void CTestDoc::Serialize(CArchive& ar)
{
for(int i = 0;i < N;i++)
{
student[i].Serialize(ar); //进行串行化操作
}
}
- 运行调试
多文档实例
- 新建多文档test
- 在CTestView.h中添加数据成员
// Implementation
public:
virtual ~CTestView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
CPoint start;//+++++++++++++++添加1
CPoint end;//+++++++++++++++++添加2
// Generated message map functions
- 添加一个新类,实现矩阵的保存:插入、类、CPRetangle、基于CEdit类(为了实现串行化)
- 在CPRetangle类中添加数据成员,并修改其构造函数:
class CPRetangle : public CEdit
{
// Construction
public:
CPRetangle(CPoint a = 0,CPoint b = 0);//+++++++++++++
CPoint x;//+++++++++++++++++++++++++++++++++++++++++
CPoint y;//+++++++++++++++++++++++++++++++++++++++++
// Attributes
- 在CPRetangle.cpp中,构造函数初始化定义如下:
// CPRetangle
CPRetangle::CPRetangle(CPoint a ,CPoint b)//++++++++++++++++++++++
{
x = a;//+++++++++++++++++++++++++++
y = b;//+++++++++++++++++++++++++++++
}
CPRetangle::~CPRetangle()
{
}
- 在CTestDoc类中添加头文件,,并添加数据成员CTypedPtrList……,用于模板链类生成的链表指针,可以实现数据的存储和管理。
#include "PRetangle.h"//+++++++++++++++++++++++++
#include <afxtempl.h>//+++++++++++++++++++++++
// Attributes
public:
CTypedPtrList<CPtrList,CPRetangle *> m_list;//+++++++++++
// Operations
- 利用类向导添加CTestView的鼠标按下和弹起的消息映射。鼠标消息的处理程序是利用CTestView中的两个数据成员保存鼠标按下和弹起的坐标,然后把一个新的CPRetangle放入链表,实现数据的存储。为了存储数据,将其传给CTestDoc类的成员。
void CTestView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
start = point;
CView::OnLButtonDown(nFlags, point);
}
void CTestView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
end = point;
CPRetangle *p = new CPRetangle(start,end);
this->GetDocument()->m_list.AddTail(p);
Invalidate();
CView::OnLButtonUp(nFlags, point);
}
- 现在已经记录了鼠标按下和弹起的坐标,并且把数据存储在CTestDoc中的成员中,但并没有实现画图操作,需要在CTestView::OnDraw中添加代码:
void CTestView::OnDraw(CDC* pDC)
{
CTestDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CTypedPtrList <CPtrList,CPRetangle *>&list = this->GetDocument()->m_list;//+++++++得到CTestDoc中的成员
POSITION pos = list.GetHeadPosition();//+++++得到头的位置
while(pos != NULL)
{
CPRetangle *retangle = list.GetNext(pos);//++++++画图
pDC->Rectangle(retangle->x.x,retangle->x.y,retangle->y.x,retangle->y.y);
}
}
- 在CTestDoc::Serialize添加如下代码:
void CTestDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: add storing code here
int n = m_list.GetCount();//+++++++++++++++++++++得到数目
ar << n;//++++++++++++++++++++++++++++++++++++++串行化输入总数目
POSITION pos = m_list.GetHeadPosition();
for(int i=0;i<n;i++)
{
CPRetangle *retangle = m_list.GetNext(pos);//+++++++得到对象链表
retangle->Serialize(ar);//+++++++++++++++++++++串行化输入
}
}
else
{
// TODO: add loading code here
int n;
ar >> n;//++++++++++++++++++++++++++++++++++++得到文件中对象目数
while (m_list.IsEmpty() == FALSE)
{
delete m_list.GetHead();
m_list.RemoveHead();
}
POSITION pos=m_list.GetHeadPosition(); //得到当前头
for(int i=0;i<n;i++)
{
CPRetangle* retangle=new CPRetangle();
retangle->Serialize(ar); //串行化输出
m_list.AddTail(retangle);
}
}
}
- 上面的数据存储和读取操作,用到了CPRetangle的成员函数以实现数据存储,因此需要在CPRectangle中重载Serialize函数,利用类向导对该函数进行重载,编写函数如下:
void CPRetangle::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{ // storing code
ar << x.x << x.y << y.x << y.y;//变量输入文件
}
else
{ // loading code
ar >> x.x >> x.y >> y.x >> y.y;//变量输出文件
}
}
本文详细介绍了如何使用MFC创建单文档和多文档应用程序。从创建单文档应用开始,通过应用程序向导设置基类,实现了类似记事本的程序。接着,深入讲解了文档/视图结构,包括CWinApp、CDocument、CView、CDocTemplate和CFrameWnd类的角色。在单文档实战部分,展示了添加控件、响应函数和数据成员的过程。随后,转向多文档实例,通过创建新的视图类和链表管理数据,实现数据的存储和画图操作,涉及CPRetangle类的重载Serialize函数。
1038

被折叠的 条评论
为什么被折叠?



