应用程序类包含着一个模板文档的对象。
通过模板文档可生成任意数目文档,用于存放数据。
存储的一个文档的数据可由任意视图来呈现数据。
框架窗口类则可以管理文档类与视图类。
?一、数据的存储与加载
序列化:以二进制方式写文件
反序列化:以二进制方式读文件
使用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类中,为其创建一个private的bool变量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),不接收会自动回收。
?
|