MFC文档与视图(笔记)

应用程序类包含着一个模板文档的对象。

通过模板文档可生成任意数目文档,用于存放数据。

存储的一个文档的数据可由任意视图来呈现数据。

框架窗口类则可以管理文档类与视图类。

 一、数据的存储与加载

序列化:以二进制方式写文件

反序列化:以二进制方式读文件

使用CArchive类,它没有基类。


创建MFC应用程序,使用单文档,MFC标准。

在资源视图中的菜单中,为其添加如下标签

右击,分别为其添加事件处理

 此处,笔者将消息处理的位置放在框架类

其实现如下

//写文件模式
void CMainFrame::OnCarchiveWrite()
{
	// TODO: 在此添加命令处理程序代码
	CFile file;
	//地址,模式(创建模式,写模式)
	BOOL isDown = file.Open(_T("res/output.txt"),CFile::modeCreate|CFile::modeWrite);
	if (isDown == FALSE) {//操作失败则结束
		return;
	}
	//将CArchive与CFile绑定
	//文件指针,存储
	CArchive ar(&file, CArchive::store);
	int a = 10;
	CString str = _T("ABC");
	ar << a << str;
	ar.Close();
	file.Close();
}

//读文件模式
void CMainFrame::OnCarchiveRead()
{
	// TODO: 在此添加命令处理程序代码
	CFile file;
	//地址,模式(读模式)
	BOOL isDown = file.Open(_T("res/output.txt"),CFile::modeRead);
	if (isDown == FALSE) {//操作失败则结束
		return;
	}
	//将CArchive与CFile绑定
	//文件指针,加载
	CArchive ar(&file, CArchive::load);
	DATE date;//获取时间
	int a;
	CString str;
	ar >> a >>str;
	str.Format(_T("%d,%s"), a, str);
	MessageBox(str);
	ar.Close();
	file.Close();
}

二、文档类的序列化函数

 通过类视图可以快速转到文档类的Serialize函数

 先将其修改

void CMFCApplication1Doc::Serialize(CArchive& ar)
{
	if (ar.IsStoring())
	{
		// TODO:  在此添加存储代码
		//点击保存时被调用
		CString str = _T("test");
		int a = 250;
		ar << str << a;
	}
	else
	{
		// TODO:  在此添加加载代码
		//打开文件时被调用
		CString str;
		int a;
		ar >> str >> a;
		str.Format(_T("%d,%s"), a, str);
		//要使用全局的,因为没有MessageBox
		AfxMessageBox(str);
	}
}

 运行,先点击保存按钮任意命名(因为以二级制模式读写),然后再通过打开按钮打开。

三、 存储绘图以模拟事务的保存

 为文档类新增如下成员变量,为方便设为public

	//设置最大可存储200个绘图点的数组
	CPoint m_pt[200];
	//记录当前绘制的点是第几个点
	int num;

 注意OnNewDocument是每新建一个文档就会调用一次。

(程序刚开始运行时,和新建文档按钮按下时被调用)

同时DeleteContents则是每一次新建前会被调用(是一个虚函数,通过重写列表可以找到)

类似于析构函数。


现将析构函数重写,用于初始化(因为Delete最先执行,当然也可以在new里面)

void CMFCApplication1Doc::DeleteContents()
{
	// TODO: 在此添加专用代码和/或调用基类
	//将m_pt初始化
	//地址,初始化值,每一个的大小
	memset(&m_pt, 0, sizeof(m_pt));
	num = 0;
	CDocument::DeleteContents();
}

然后为视图添加左键点击事件

void CMFCApplication1View::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	//获取文档类指针
	CMFCApplication1Doc* pDoc = GetDocument();
	if (pDoc->num > 199) {//超过则禁止
		MessageBox(_T("不能超过200个点"));
		return;
	}
	pDoc->m_pt[pDoc->num] = point;
	pDoc->num++;
	//窗口失效,即调用OnDraw函数重绘页面
	Invalidate();
	CView::OnLButtonDown(nFlags, point);
}

 在转到View的绘图函数

void CMFCApplication1View::OnDraw(CDC* pDC)
{
	CMFCApplication1Doc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;
	
	// TODO: 在此处为本机数据添加绘制代码
	for (int i = 0;i < pDoc->num;i++) {
		//将点击位置设置为圆
		pDC->Ellipse(pDoc->m_pt[i].x - 5,pDoc->m_pt[i].y-5,pDoc->m_pt[i].x+5,pDoc->m_pt[i].y+5);
	}
}

结果如下 

现在开始保存数据,转到文档类的Serialize函数


void CMFCApplication1Doc::Serialize(CArchive& ar)
{
	if (ar.IsStoring())
	{
		// TODO:  在此添加存储代码
        //存储
		ar << num;
		for (int i = 0;i < num;i++) {
			ar << m_pt[i];
		}

	}
	else
	{
		// TODO:  在此添加加载代码
        //加载
		ar >> num;
		for (int i = 0;i < num;i++) {
			ar >> m_pt[i];
		}
	}
}

四、文档视图案例 

通过文档与视图合作,对学生信息进行查询。

本案例的数据存储在MFC的链表之中,而不是在数据库。

使用单文档,MFC标准。

以及修改View的基类,该方式使得View支持控件。

(若报“没有对CForm的打印支持”等错误,可能是组件不全,但本案例用不到,可以无视。)

 在设计页面为该View添加如下控件

记得将“编辑”单选框的Group设为true。并注意单选框的顺序时连着的("ctrl+D"查看)

为“上一个”按钮与“下一个”以及”添加“按钮,添加控制变量。

 现在双击,为其添加点击事件。

另外在View类中,为其创建一个privatebool变量flag,用于判断当前状态(编辑还是预览)

//编辑模式
void CStudentInfoView::OnBnClickedRadio1()
{
	// TODO: 在此添加控件通知处理程序代码
	m_Pre.EnableWindow(FALSE);
	m_Next.EnableWindow(FALSE);
	flag = false;
	m_add.SetWindowTextW(_T("修改"));
}

//预览模式
void CStudentInfoView::OnBnClickedRadio2()
{
	// TODO: 在此添加控件通知处理程序代码
	m_Pre.EnableWindow(TRUE);
	m_Next.EnableWindow(TRUE);
	flag = true;
	m_add.SetWindowTextW(_T("添加"));
}

 随后设置默认选中项(在ViewForm中,我们可以在OnInitialUpddate处初始化)


void CStudentInfoView::OnInitialUpdate()
{
	CFormView::OnInitialUpdate();
	GetParentFrame()->RecalcLayout();
	ResizeParentToFit();
	//第一个单选框,第二个单选框,默认选中
	CheckRadioButton(IDC_RADIO1, IDC_RADIO2, IDC_RADIO1);
	//调用第一个单选框的点击事件
	OnBnClickedRadio1();

}

结果

 

现在为数据存储做准备。 

通过资源管理器添加新的类

 大致建一个就好了

#pragma once

class Student{
public:
	Student(int id,CString name,int age,int score);
	~Student();

	int getId();
	CString getName();
	int getAge();
	int getScore();

	void setId(int id);
	void setName(CString name);
	void setAge(int age);
	void setScore(int score);
private:
	int id;
	CString name;
	int age;
	int score;

};
#include "pch.h"
#include "Student.h"

Student::Student(int id, CString name, int age, int score){
	this->id = id;
	this->name = name;
	this->age = age;
	this->score = score;
}

Student::~Student() {


}

int Student::getId()
{
	return this->id;
}

CString Student::getName()
{
	return this->name;
}

int Student::getAge()
{
	return this->age;
}

int Student::getScore()
{
	return this->score;
}

void Student::setId(int id)
{
	this->id = id;
}

void Student::setName(CString name)
{
	this->name = name;
}

void Student::setAge(int age)
{
	this->age = age;
}

void Student::setScore(int score)
{
	this->score = score;
}

 接着在文档类中导入该类,然后为为文档类添加如下成员变量

public:
	CList<Student*>m_list;//MFC的列表容器
	POSITION m_pos;//当前位置节点,类似于迭代器

 接着为所有文本编辑框准备之变量。

  而后为“添加”、"上一个"、”下一个“按钮添加点击事件。

//添加/修改
void CStudentInfoView::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
	//编辑区数据更新到后台
	UpdateData(TRUE);
	if (m_Uid == 0 || m_Uname.IsEmpty()|| m_Uage == 0) {
		MessageBox(_T("请注意学号、名称以及年龄不能为空"));
		return;
	}
	//获取文档对象指针
	CStudentInfoDoc* pDoc = GetDocument();
	if (flag == true || pDoc->m_list.GetCount() == 0) {
		Student* student = new Student(m_Uid, m_Uname, m_Uage, m_Uscore);
		//此处直接插入尾部
		pDoc->m_list.AddTail(student);
		//获取最后一个节点
		pDoc->m_pos = pDoc->m_list.GetTailPosition();
	}
	else{
		Student* stu = pDoc->m_list.GetAt(pDoc->m_pos);
		stu->setId(m_Uid);
		stu->setName(m_Uname);
		stu->setAge(m_Uage);
		stu->setScore(m_Uscore);
	}
}
//上一个
void CStudentInfoView::OnBnClickedButton2()
{
	// TODO: 在此添加控件通知处理程序代码
	//获取文档对象指针
	CStudentInfoDoc* pDoc = GetDocument();
	if (pDoc->m_list.GetCount() == 0) {
		return;
	}
	//获取上一个元素
	//该方式会让m_pos前移,但返回的时移动前的值
	pDoc->m_list.GetPrev(pDoc->m_pos);
	if (pDoc->m_pos == NULL) {//移动到头部时
		//设置尾部节点
		pDoc->m_pos = pDoc->m_list.GetTailPosition();
	}
	//获取当前位置的元素
	Student* student = pDoc->m_list.GetAt(pDoc->m_pos);
	m_Uid = student->getId();
	m_Uname = student->getName();
	m_Uage = student->getAge();
	m_Uscore = student->getScore();
	//更新到编辑区
	UpdateData(FALSE);
}
//下一个
void CStudentInfoView::OnBnClickedButton3()
{
	// TODO: 在此添加控件通知处理程序代码
	//获取文档对象指针
	CStudentInfoDoc* pDoc = GetDocument();
	if (pDoc->m_list.GetCount() == 0) {
		return;
	}
	//获取上一个元素
	//该方式会让m_pos前移,但返回的时移动前的值
	pDoc->m_list.GetNext(pDoc->m_pos);
	if (pDoc->m_pos == NULL) {//移动到头部时
		//设置尾部节点
		pDoc->m_pos = pDoc->m_list.GetHeadPosition();
	}
	//获取当前位置的元素
	Student* student = pDoc->m_list.GetAt(pDoc->m_pos);
	m_Uid = student->getId();
	m_Uname = student->getName();
	m_Uage = student->getAge();
	m_Uscore = student->getScore();
	//更新到编辑区
	UpdateData(FALSE);
}

最后再在文档类中重写DeleteContents,来清空数据

void CStudentInfoDoc::DeleteContents()
{
	// TODO: 在此添加专用代码和/或调用基类
	//该方法可以直接释放所占用空间
	m_list.RemoveAll();
	CDocument::DeleteContents();
}

通过其底层可知,该方法调用了析构函数 ,直接调用每一个节点的析构函数。

此外,用RemoveHead从头部一项一项移除也是可以的,但不需要用一个类去接收其返回值,因为它返回的是一个值,而不是一个动态分配的地址(new),不接收会自动回收。

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值