上回老师又布置了一个作业,用MFC实现类的序列化功能,于是乎我就滚去摸索mfc的序列化,经过两三周的时间,终于把这个会员管理器搭得差不多了,主要的功能均已实现,算是半成品吧,因为还有一个小问题没解决(虽说不影响使用)。交给老师答辩当天其实还在赶工写这个,虽然没写完,但是还是得了这次答辩的全班最高分(虽然这个程序算不上啥,不过还是请允许我在这里装一下B<-泥垢了)~
好吧闲话不扯那么多了(<-已经扯得够多了啊喂),这个会员管理器的核心功能是类的序列化和反序列化,其中还用到了许多控件和一些消息的事件,对于刚学mfc没多久的同学的学习应该也是挺不错的。运行界面如下:
这个小程序的代码量还是有点多的,在这里我就只挑关键的知识点讲解就行了,源工程会在文章后面放出
1、文件的序列化
比如,在这个程序中,我写了一个叫CMember的类,用来表示一个会员,为了让这个类支持序列化,这个类写成了这样:
Member.h
#pragma once
#include "afx.h"
///<summary>
///会员类,继承自CObject以满足序列化条件
///</summary>
class CMember :
public CObject
{
public:
//空类型的构造函数
CMember(void);
~CMember(void);
//使用DECLARE宏
DECLARE_SERIAL(CMember)
//重写Serialize成员函数
void Serialize(CArchive& archive);
void setName(CString name);
void setMemberId(CString id);
void setSex(CString sex);
void setIdcardNum(CString idnum);
void setJoinTime(CString jointime);
void setLove(CString love);
void setRemarks(CString remarks);
void setImagePath(CString imagePath);
CString getName();
CString getMemberId();
CString getSex();
CString getIdcardNum();
CString getJoinTime();
CString getLove();
CString getRemarks();
CString getImagePath();
//运算符=的重载
//CMember& operator = (const CMember& member);
private:
//会员号
CString m_memberId;
//姓名
CString m_name;
//身份证号
CString m_idcardNum;
//性别
CString m_sex;
//入会时间
CString m_joinTime;
//爱好
CString m_love;
//备注
CString m_remarks;
//用户头像
CString m_imagePath;
};
Member.cpp
#include "stdafx.h"
#include "Member.h"
//使用IMPLEMENT宏定义序列化中所需要的各种函数
IMPLEMENT_SERIAL( CMember, CObject, 1 )
CMember::CMember(void)
{
}
CMember::~CMember(void)
{
}
void CMember::Serialize(CArchive& archive)
{
//首先调用基类的序列化函数以确保当前对象能被序列化
CObject::Serialize(archive);
//正在存储
if(archive.IsStoring())
{
//依次序列化会员号、姓名、身份证号、性别、入会时间、爱好、备注、头像路径名称
archive<<m_memberId<<m_name<<m_idcardNum<<m_sex<<m_joinTime<<m_love<<m_remarks<<m_imagePath;
}
//正在读取
else
{
//依次反序列化会员号、姓名、身份证号、性别、入会时间、爱好、备注、头像路径名称
archive>>m_memberId>>m_name>>m_idcardNum>>m_sex>>m_joinTime>>m_love>>m_remarks>>m_imagePath;
}
//也可使用 CArchive::Read及 CArchive::Write成员函数来读写大量未键入的数据
}
//CMember& CMember::operator = (const CMember& member)
//{
// if(this != &member)
// {
// this->m_idcardNum = member.m_idcardNum;
// this->m_joinTime = member.m_joinTime;
// this->m_love = member.m_love;
// this->m_memberId = member.m_memberId;
// this->m_name = member.m_name;
// this->m_remarks = member.m_remarks;
// this->m_sex = member.m_sex;
// }
//
//
// //返回左边的对象而不是右边的对象
// return *this;
//}
void CMember::setName(CString name)
{
m_name = name;
}
void CMember::setMemberId(CString id)
{
m_memberId = id;
}
void CMember::setSex(CString sex)
{
m_sex = sex;
}
void CMember::setIdcardNum(CString idnum)
{
m_idcardNum = idnum;
}
void CMember::setJoinTime(CString jointime)
{
m_joinTime = jointime;
}
void CMember::setLove(CString love)
{
m_love = love;
}
void CMember::setRemarks(CString remarks)
{
m_remarks = remarks;
}
void CMember::setImagePath(CString imagePath)
{
m_imagePath = imagePath;
}
CString CMember::getName()
{
return m_name;
}
CString CMember::getMemberId()
{
return m_memberId;
}
CString CMember::getSex()
{
return m_sex;
}
CString CMember::getIdcardNum()
{
return m_idcardNum;
}
CString CMember::getJoinTime()
{
return m_joinTime;
}
CString CMember::getLove()
{
return m_love;
}
CString CMember::getRemarks()
{
return m_remarks;
}
CString CMember::getImagePath()
{
return m_imagePath;
}
然后使用这个类进行序列化和反序列化的时候:
序列化这个类(存储数据),这里一次连续序列化了两个类的实例:
CMember member;
//注意路径的"/"不要写成"\",否则会读取失败
CFile file(_T("D:/MembersMessages.txt"),CFile::modeCreate|CFile::modeReadWrite);
CArchive ar(&file, CArchive::store);
member.setIdcardNum(_T("23333"));
member.setJoinTime(_T("2014"));
member.setLove(_T("miku"));
member.setMemberId(_T("001"));
member.setName(_T("新名字"));
member.setRemarks(_T("没有备注。。。"));
member.setSex(_T("男"));
CMember member2;
member2.setIdcardNum(_T("233333399999"));
member2.setJoinTime(_T("2015"));
member2.setLove(_T("miku~~~~~~~"));
member2.setMemberId(_T("002"));
member2.setName(_T("新名字2"));
member2.setRemarks(_T("没有备注。。。。。。"));
member2.setSex(_T("女"));
ar.WriteObject(&member2);
ar.WriteObject(&member);
//确保缓存区的数据都保存到文件
ar.Flush();
//关闭Archive流
ar.Close();
//file.SeekToBegin();
file.Close();
反序列化(从本地文件中读取出数据):
CMember new1;
//CMember member;
//读取数据的时候不应该用CFile::modeCreate,否则会抛出Exception
CFile input(_T("D:/MembersMessages.txt"),CFile::modeReadWrite);
CArchive arLoad(&input,CArchive::load);
CRuntimeClass* runtimeClass = new1.GetRuntimeClass();
CMember* p = (CMember*)arLoad.ReadObject(runtimeClass);
runtimeClass = new2.GetRuntimeClass();
//这时候p就读取出了之前存进去的数据,如果要全部读取出来,需将这整个方法循环执行
p = (CMember*)arLoad.ReadObject(runtimeClass);
arLoad.Close();
input.Close();
另一种序列化的方法是构建一个数组或者链表,然后将需要序列化的类全部存进这些数组或者链表中,然后将这个数组或者链表序列化,这种方法如果执行成功,个人认为会较上面的方法方便很多,而且MFC中的许多集合类都支持序列化,比如CArray、CList、CPtrArray、CTypedPtrArray等,具体用法参照网上和msdn给出的方法如下:
//假设有两个CList的对象分别为dataList和loadList
//序列化过程
CFile output(_T("data.txt"),CFile::modeCreate|CFile::modeWrite);
CArchive ar(&output, CArchive::store);
dataList.Serialize(ar);
ar.Close();
output.Close();
//反序列化过程,这时候loadList读取出来的内容就是dataList存进去的内容
CFile input(_T("data.txt"),CFile::modeRead);
CArchive in(&input,CArchive::load);
loadList.Serialize(in);
in.Close();
input.Close();
MFC其他的集合类的序列化的用法基本类似,不过我个人测试了许多,发现用这种序列化集合的方法问题多多,而且目前测试了多个集合类都不成功,个人不推荐使用。
2、对话框的构建
这个程序中增删改查的对话框都是一个个自定义的类,在使用他们的时候,只需要将这些类实例化,即可使用并且弹出对话框。
新增对话框的一般方法为:打开资源视图,右键->插入dialog,比如插入了一个“新增”的对话框:
然后在对话框的可视编辑窗口右键->添加类
然后输入类的名字,直接点击“完成”就可以了
调用出这个对话框的时候,只需:
//比如刚才添加添加的类的名称就是CAddDialog
//创建“新增”对话框
CAddDialog AddDlg;
//保存DoModal返回的值
INT_PTR ptr;
//弹出模态对话框
ptr = AddDlg.DoModal();
//如果按下“取消”按钮
if(ptr == IDCANCEL)
{
//做些什么
}
//如果按下“确定”按钮
if(ptr == IDOK)
{
//做些什么
}
3、工具栏以及一些控件的使用
由于程序仍然是基于单文档的,因此可以在资源视图编辑工具栏:
由于工具栏是共用菜单的消息事件的,所以想要让某个工具项生效,只需要将它的ID设置为某个菜单项的ID一致,然后只需要在菜单项中添加消息处理函数,比如“新增会员”工具栏选项对应菜单栏上的“会员管理”->“新增会员”,则将菜单栏上的”新增会员“的ID赋给工具栏”新增会员的“ID:
另外“选择图片”这个功能用的不是默认的控件,而是使用了MFC中的向导页和属性页:
新建两个对话框,分别是选择默认图片和从本地资源中任意选取一张图片
然后新建一个MFC类(注意是MFC类而不是普通的C++类),将这两个对话框作为自己的子页添加进去,这里我建的类名为CSelectPages:
SelectPages.h
#pragma once
#include "SelectHeadPicPage.h"
#include "SelectHeadPicFromLocal.h"
// CSelectPages
class CSelectPages : public CPropertySheet
{
DECLARE_DYNAMIC(CSelectPages)
public:
CSelectPages(UINT nIDCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0);
CSelectPages(LPCTSTR pszCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0);
virtual ~CSelectPages();
void setImagePath(CString imagePath);
CString getImagePath();
//选择默认图片的对话框
CSelectHeadPicPage page1;
//从本地选择图片的对话框
CSelectHeadPicFromLocal page2;
//图片的路径
CString m_imagePath;
protected:
DECLARE_MESSAGE_MAP()
};
SelectPages.cpp
// SelectPages.cpp : 实现文件
//
#include "stdafx.h"
#include "MemberControlTest.h"
#include "SelectPages.h"
// CSelectPages
IMPLEMENT_DYNAMIC(CSelectPages, CPropertySheet)
CSelectPages::CSelectPages(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)
:CPropertySheet(nIDCaption, pParentWnd, iSelectPage)
{
//初始化的时候将这两个对话框添加为自己的子页
AddPage(&page1);
AddPage(&page2);
m_imagePath = _T("");
}
CSelectPages::CSelectPages(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
:CPropertySheet(pszCaption, pParentWnd, iSelectPage)
{
//初始化的时候将这两个对话框添加为自己的子页
AddPage(&page1);
AddPage(&page2);
m_imagePath = _T("");
}
CSelectPages::~CSelectPages()
{
}
BEGIN_MESSAGE_MAP(CSelectPages, CPropertySheet)
END_MESSAGE_MAP()
void CSelectPages::setImagePath(CString imagePath)
{
m_imagePath = imagePath;
}
CString CSelectPages::getImagePath()
{
return m_imagePath;
}
// CSelectPages 消息处理程序
使用的时候只需:
//创建属性页对话框
INT_PTR ptr;
//设置标题
CSelectPages pages(_T("选择头像"));
ptr = pages.DoModal();
//如果按下了“确定”按钮
if(ptr == IDOK)
{
//做些什么
}
关于Picture Control这个控件,想要自由改变控件的大小,需在控件的属性中将“Center Image”设置为True
之后才能方便地控制图片的自适应,不过还需要在代码中控制,控件的属性中没有控制图片自适应的方法。
这个小程序就介绍到这里,源程序地址:http://download.youkuaiyun.com/detail/yueya_shanhua/8404419,有疑问还请提出,多多交流,多多指教~