第7章 手把手教你学MFC之单文档和多文档

本文详细介绍了如何使用MFC创建单文档和多文档应用程序。从创建单文档应用开始,通过应用程序向导设置基类,实现了类似记事本的程序。接着,深入讲解了文档/视图结构,包括CWinApp、CDocument、CView、CDocTemplate和CFrameWnd类的角色。在单文档实战部分,展示了添加控件、响应函数和数据成员的过程。随后,转向多文档实例,通过创建新的视图类和链表管理数据,实现数据的存储和画图操作,涉及CPRetangle类的重载Serialize函数。

单文档界面与多文档界面

  • 单文档程序指程序只可以打开一个文档,如:记事本
  • 多文档程序指可以同时打开多个文档,如: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;//变量输出文件
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值