一、文档视图结构
文档类(CDocument):存储加载(读写)数据
视图类(CView):显示和修改数据
1)单文档
a)文档模板:把框架窗口、文档、视图关联在一起
b)文档类(CDocument):
OnNewDocument(),第一次新建窗口调用,后面每次按“新建”,自动调用此函数
DeleteContents(),做一些释放资源的操作,每次按“新建”,新建前先调用此函数
c)框架类可以认为是视图类的容器
测试多文档
// 注册应用程序的文档模板。 文档模板 // 将用作文档、框架窗口和视图之间的连接 CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate(IDR_TestTYPE, RUNTIME_CLASS(CTestDoc), RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 RUNTIME_CLASS(CTestView)); if (!pDocTemplate) return FALSE; AddDocTemplate(pDocTemplate);
2)各类相关访问
文档视图各类之间相互访问
a) 在视图类,如何访问文档对象指针 CView::GetDocument
CDocument* GetDocument() const;
二、文档序列化(二进制操作文件 CArchive) 相当于Qt QDataStream
序列化:以二进制方式写文件
反序列化:以二进制方式读文件
1)写文件
a) 创建文件对象 CFile
b) 以写方式打开文件 CFile::Open
c) 创建序列化对象,并且和文件关联在一起 CArchive
CArchive::store 把数据保存到归档文件中。允许CFile写操作。
d) 往数据流写数据(相当于往文件写数据)
ar << a << b << c
e) 断开数据流和文件的关联 CArchive::Close
f) 关闭文件 CFile::Close
2)读文件
a) 创建文件对象 CFile
b) 以读方式打开文件 CFile::Open
c) 创建序列化对象,并且和文件关联在一起 CArchive
CArchive::load 从归档文件装载数据。CFile只读。
d) 往数流读数据(相当于往文件读数据)
ar >> a >> b >> c
e) 断开数据流和文件的关联 CArchive::Close
f) 关闭文件 CFile::Close
单文档建立mfc工程:
添加事件处理函数
// CMainFrame 消息处理程序 //写文件 void CMainFrame::OnArchiveWrite() { // TODO: 在此添加命令处理程序代码 /* a) 创建文件对象 CFile b) 以写方式打开文件 CFile::Open c) 创建序列化对象,并且和文件关联在一起 CArchive CArchive::store 把数据保存到归档文件中。允许CFile写操作。 d) 往数据流写数据(相当于往文件写数据) ar << a << b << c e) 断开数据流和文件的关联 CArchive::Close f) 关闭文件 CFile::Close */ CFile file; BOOL isOk = file.Open(TEXT("../demo.txt"),CFile::modeCreate|CFile::modeWrite); if (!isOk) { return; } //和CArchive管理 //CArchive对象是数据流,文件和CArchive绑定一起, //store: 存储,写 CArchive ar(&file,CArchive::store); //和cout用法一样 int a = 10; CString str = TEXT("ABC"); TCHAR ch = 't'; //箭头代表流向 //数据流向ar, ar指向文件 ar << a << str << ch; ar.Close();//断开数据流和文件的关联 file.Close(); } //读文件 void CMainFrame::OnArchiveRead() { // TODO: 在此添加命令处理程序代码 CFile file; BOOL isOk = file.Open(TEXT("../demo.txt"),CFile::modeRead); if (!isOk) { return; } //和CArchive管理 //CArchive对象是数据流,文件和CArchive绑定一起, //load: 读 CArchive ar(&file,CArchive::load); //和cout用法一样 int a; CString str; TCHAR ch; ar >> a >> str >> ch; CString buf; buf.Format(TEXT("%d,%s,%c"),a,str,ch); MessageBox(buf); ar.Close();//断开数据流和文件的关联 file.Close(); }
三、文档视图案例
1)文档类自带序列化作函数 Serialize()
void CMy01_CArchiveDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: 在此添加存储代码、
//按保存按钮时调用
}
else
{
// TODO: 在此添加加载代码
//按打开按钮调用
}
}
// CCArchiveDoc 序列化 void CCArchiveDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) // //按保存,调用此处 { // TODO: 在此添加存储代码 CString str = TEXT("Hello World"); int a = 200; ar << str << a; } else { // TODO: 在此添加加载代码 //打开文件 CString str; int a; ar >> str >> a; CString buf; buf.Format(TEXT("%s,%d"),str,a); AfxMessageBox(buf); } }
画圆点
1.在doc.h中定义定义两个变量保存点的信息
// 特性 public: CPoint m_pt[200]; int m_num;
2,重写 DeleteContents() 函数,初始化数据
void CMy05_CArchiveProDoc::DeleteContents() { // TODO: 在此添加专用代码和/或调用基类 //AfxMessageBox(TEXT("DeleteContents")); memset(&m_pt, 0, sizeof(m_pt)); m_num = 0; CDocument::DeleteContents(); }
3,在视图中处理鼠标左键消息,保存点的数据
// CMy05_CArchiveProView 消息处理程序 //鼠标左键 void CMy05_CArchiveProView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 //获取文档类对象指针 CMy05_CArchiveProDoc *pDoc = GetDocument(); //不能超过200 if (pDoc->m_num > 200) { pDoc->m_num = 200; return; } pDoc->m_pt[pDoc->m_num] = point; pDoc->m_num++; //每点完一个点,绘图 Invalidate(); //-> OnDraw() CView::OnLButtonDown(nFlags, point); }
4,在视图中绘制点
// CMy05_CArchiveProView 绘制 void CMy05_CArchiveProView::OnDraw(CDC* pDC) { CMy05_CArchiveProDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: 在此处为本机数据添加绘制代码 for (int i = 0; i < pDoc->m_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); } }
5,序列化数据
// CMy05_CArchiveProDoc 序列化 void CMy05_CArchiveProDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // TODO: 在此添加存储代码 //保存数据 ar << m_num; for (int i = 0; i < m_num; i++) { ar << m_pt[i]; } } else { // TODO: 在此添加加载代码 ar >> m_num; for (int i = 0; i < m_num; i++) { ar >> m_pt[i]; } } }
Invalidate
该函数的作用是使整个窗口客户区无效。窗口的客户区无效意味着需要重绘,例如,如果一个被其它窗口遮住的窗口变成了前台窗口,那么原来被遮住的部分就是无效的,需要重绘。这时Windows会在应用程序的消息队列中放置WM_PAINT消息。
它和 UpdateWindow( )区别在于:
UpdateWindow( )的作用是使窗口立即重绘。调用Invalidate等函数后窗口不会立即重绘,这是由于WM_PAINT消息的优先级很低,它需要等消息队列中的其它消息发送完后才能被处理。调用UpdateWindow函数可使WM_PAINT被直接发送到目标窗口,从而导致窗口立即重绘。
2)学生管理系统
a)定义一个学生类Stu
b)文档类存储数据,视图类修改和显示数据
1)从尾部添加元素 CList::AddTail
2)获得此列表尾部元素的位置 CList::GetTailPosition
3)获取上一个元素 CList::GetPrev
4)获取下一个元素 CList::GetNext
5)获取首元素位置 CList::GetHeadPosition
6)获取最后一个元素位置 CList::GetTailPosition
7)获取指定位置的元素 CList::GetAt
8)移除头结点元素(并没有释放空间)CList::RemoveHead
c)视图的基类是 CFormView
d)重写文档类 DeleteContents(),做一些释放资源的操作,每次按“新建”,新建前先调用此函数
https://docs.microsoft.com/zh-cn/previous-versions/bxde0zae%28v%3dvs.110%29
新建单文档mfc工程,生成的类view选CFormView
1,student 类
class Student { public: Student(int id, CString name, int age, float score); ~Student(void); int m_num; CString m_name; int m_age; float m_score; };
2,doc文档增加变量
// 特性 public: CList<Student *> m_list; POSITION m_pos;
3,doc文档重写DeleteContents()
// CmyviewDoc 命令 void CmyviewDoc::DeleteContents() { // TODO: 在此添加专用代码和/或调用基类 //每次新建前,应该清空内容 while (m_list.GetHeadPosition() != NULL) { //把头结点元素移除 Student *p = m_list.RemoveHead(); delete p; } m_pos = NULL; CDocument::DeleteContents(); }
4,视图消息处理
// CmyviewView 消息处理程序 // 提交 void CmyviewView::OnBnClickedButton1() { // TODO: 在此添加控件通知处理程序代码 //把编辑区的内容更新到对应的变量中 UpdateData(TRUE); Student *stu = new Student(m_num,m_name,m_age,m_score); // 获取文档对象指针 CmyviewDoc *pDoc = GetDocument(); // 从尾部添加节点 pDoc->m_list.AddTail(stu); // 获取最后一个元素节点 pDoc->m_pos = pDoc->m_list.GetTailPosition(); } // 上一个 void CmyviewView::OnBnClickedButton2() { // TODO: 在此添加控件通知处理程序代码 Student *stu = new Student(m_num,m_name,m_age,m_score); //获取文档对象指针 CmyviewDoc *pDoc = GetDocument(); //获取上一个元素后,pDoc->m_pos会自动往上移动 pDoc->m_list.GetPrev(pDoc->m_pos); //移动到头结点的上一个无效节点 if (pDoc->m_pos == NULL) { //设置尾结点 pDoc->m_pos = pDoc->m_list.GetTailPosition(); } //获取当前位置的元素 Student *p = pDoc->m_list.GetAt(pDoc->m_pos); //把节点的值给变量赋值 m_num = p->m_num; m_name = p->m_name; m_age = p->m_age; m_score = p->m_score; //更新到编辑区 UpdateData(FALSE); } // 下一个 void CmyviewView::OnBnClickedButton3() { // TODO: 在此添加控件通知处理程序代码 Student *stu = new Student(m_num,m_name,m_age,m_score); //获取文档对象指针 CmyviewDoc *pDoc = GetDocument(); //获取上一个元素后,pDoc->m_pos会自动往上移动 pDoc->m_list.GetNext(pDoc->m_pos); //移动到头结点的上一个无效节点 if (pDoc->m_pos == NULL) { //设置尾结点 pDoc->m_pos = pDoc->m_list.GetHeadPosition(); } //获取当前位置的元素 Student *p = pDoc->m_list.GetAt(pDoc->m_pos); //把节点的值给变量赋值 m_num = p->m_num; m_name = p->m_name; m_age = p->m_age; m_score = p->m_score; //更新到编辑区 UpdateData(FALSE); } // 编辑 void CmyviewView::OnBnClickedRadio1() { // TODO: 在此添加控件通知处理程序代码 m_buttonNext.EnableWindow(FALSE); m_buttonPre.EnableWindow(FALSE); } // 预览 void CmyviewView::OnBnClickedRadio2() { // TODO: 在此添加控件通知处理程序代码 m_buttonNext.EnableWindow(TRUE); m_buttonPre.EnableWindow(TRUE); }
四、数据库编程
1)准备工作
a) 安装MySQL服务器
b) MySQL odbc驱动
2)odbc层次图
a) odbc一套标准接口(内部通过sql语句操作数据库,用户就算不懂sql语句也可以借助odbc操作数据库)
b) 数据源
3)如何创建数据源(MySql只能是快照)
a)快照(Snapshot)记录集:每次操作重新查询后才更新
b)动态(Dynaset)记录集:每次操作自动更新(添加记录外)
4)应用程序框架
a) CRecordset的子类,主要是对数据库进行相应操作
1)DoFieldExchange() 自动把数据库的字段和变量相关联
2)GetDefaultConnect() 获取数据库连接信息
3)GetDefaultSQL() 获取数据库连接的表
b) CFormView的子类,显示数据库内容的视图
1)OnInitialUpdate() 主要作初始化功能
5)通过 CRecordset 类对数据库进行相应操作
a) 视图类头文件创建 CRecordset的子类对象
b) 视图类做 增删改查 操作
1)打开数据库 CRecordset::Open
2)查询记录 CRecordset::Requery
3)移动上一个记录集 CRecordset::MovePrev
4)移动下一个记录集 CRecordset::MoveNext
5)是否为最后一个记录的下一个 CRecordset::IsEOF
6)是否为第一个记录的上一个 CRecordset::IsBOF
7)移动到第一个记录 CRecordset::MoveFirst
8)移动到最后一个记录 CRecordset::MoveLast
9)添加空记录 CRecordset::AddNew
10)如果记录集可修改 CRecordset::CanUpdate
11)更新记录集 CRecordset::Update
12)删除当前记录 CRecordset::Delete
13)编辑当前记录 CRecordset::Edit
14)过滤 CRecordset::m_strFilter
15)排序 CRecordset::m_strSort(默认升序,降序加 desc)
c) 注意点
1)移动记录集,注意越界处理
2)更新记录前,先通过 CRecordset::CanUpdate 判断可更新后,才进行更新
3)删除数据后,最好移动到下一个记录集
以下针对vs2017
建立单文档MFC工程
第二步:对项目进行配置,让它可以用代码连接到数据库:
1.由于电脑和数据库有32位和64位的,所以要根据自己的电脑和安装的数据库的情况,自己选择,
点击 ‘项目’ ——》 ‘属性‘ ——》’配置管理器‘,在这里可以选择自己需要的位数;
2.点击 ‘项目’ ——》 ‘属性‘ ——》’VC++属性‘,对其包含目录,引用目录和库目录进行配置。
(1) 选中包含目录后, 右边会出现下拉箭头, 点击该箭头,再点击 ‘编辑’,把你电脑里 MySQL 安装目录中的 include文件的路径填写在编辑框里面:
3.对附加依赖项进行设置。
在左侧点击 ‘配置属性’——》‘链接器’——》‘输入’,然后在右边的附加依赖项中加入 “ libmysql.lib”编辑框就行了。
4.将 MySql 安装目录中 libmysql.dll和 libmysql.lib 两个文件拷贝到当前项目的主目录下,以及主目录下与项目名同名的文件夹下。
写到这数据库基本已经可以连上了
下面开始写代码,但在这之前要在数据库中新建一个数据库(test)
3.数据上传在‘插入’按钮的消息处理函数中实现,代码如下:
//因为数据库是通过网络连接的,必须包含网络相关头文件 #include "winsock.h" //这个没什么好说的,mysql头文件自然要包含 #include "mysql.h" void Cmysql_testDlg::OnBnClickedInsertButton() { // TODO: 在此添加控件通知处理程序代码 MYSQL m_sqlCon; //初始化数据库对象 mysql_init(&m_sqlCon); //localhost:服务器地址,可以直接填入IP;root:账号; //123:密码;test:数据库名;3306:网络端口 if (!mysql_real_connect(&m_sqlCon, "localhost", "root", "123456", "test", 3306, NULL, 0)) { AfxMessageBox(_T("数据库连接失败!")); return; } else//连接成功则继续访问数据库,之后的相关操作代码基本是放在这里面的 { AfxMessageBox(_T("数据库连接成功!")); UpdateData(true); //设置数据库字符格式,解决中文乱码问题 mysql_query(&m_sqlCon, "set names 'gb2312'"); char* num = (char*)m_num.GetBuffer(); char* name = (char*)m_name.GetBuffer(); char* age = (char*)m_age.GetBuffer(); char insert[1000]; sprintf_s(insert, "insert into student(num, name, age) values (\'%s\', \'%s\', \'%s\')", num, name, age); // 执行 sql 语句。 // mysql_query() 的返回值份很多情形, 进行判断使要注意。 if (mysql_query(&m_sqlCon, insert) == 0) { AfxMessageBox(_T("插入数据成功!")); } else { AfxMessageBox(_T("插入数据失败!")); } } UpdateData(false); mysql_close(&m_sqlCon);//关闭Mysql连接 }
这样代码已经写好了,但是还要修改字符集
点击 ‘项目’ ——》 ‘属性‘ ——》‘常规’,在右面有个字符集,把它改成 ‘使用多字节字符集’
这样在向数据库中写入数据室就不会报错了。
vs2015