(九)文档和视图,Invalidate,数据库编程

 

一、文档视图结构

文档类(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

  void Invalidate( BOOL bErase = TRUE );
  该函数的作用是使整个窗口客户区无效。窗口的客户区无效意味着需要重绘,例如,如果一个被其它窗口遮住的窗口变成了前台窗口,那么原来被遮住的部分就是无效的,需要重绘。这时Windows会在应用程序的消息队列中放置WM_PAINT消息。
MFC为窗口类提供了WM_PAINT的消息处理函数OnPaint,OnPaint负责重绘窗口。视图类有一些例外,在视图类的OnPaint函数中调用了OnDraw函数,实际的重绘工作由OnDraw来完成。
参数bErase为TRUE时,重绘区域内的背景将被擦除,否则,背景将保持不变。
  它和 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

 

 

posted @ 2019-05-05 17:56  狂奔~  阅读(441)  评论(0编辑  收藏  举报