VC结合Access制作通讯录程序    
 设计思路:

  分层:界面层,数据访问层,数据库;数据访问层的作用是为界面层提供一个数据访问接口,隔离界面层和数据库;界面层不需要知道采用的哪种数据库,只需要调用数据访问层提供的接口来完成各种操作;

  1.新建一个基于对话框的MFC程序,命名CallList

  2.设计程序界面如下图:

 

  3.数据访问层的实现:

  在StdAfx.h中添加#import "c:\Program Files\Common Files\System\ADO\msado15.dll"no_namespace rename("EOF", "EndOfFile")

  新建一个类CDataSource,作用是封装数据层,定义如下

class CDataSource  
{
public:
    //当前记录指针是否到了所有记录之后
    BOOL IsEOF();
    //当前记录指针是否到了所有记录之前
    BOOL IsBOF();
    //删除当前记录
    void Delete();
    //设置FieldName字段的值为Value(int型)
    void SetAsInteger(CString FieldName, int Value);
    //设置FieldName字段的值为Value(CString型)
    void SetAsString(CString FieldName, CString Value);
    //将记录的修改更新到数据库中
    void Update();
    //新增一条记录
    void New();
    //得到FieldName字段的值(int型)
    int GetAsInteger(CString FieldName);
    //得到FieldName字段的值(CString型)
    CString GetAsString(CString FieldName);
    //当前记录指针是否到了最后一条记录
    BOOL IsLast();
    //当前记录指针是否到了第一条记录
    BOOL IsFirst();
    //移动当前记录指针到下一条记录
    void MoveNext();
    //移动当前记录指针到上一条记录
    void MovePrev();
    //移动当前记录指针到最后一条记录
    void MoveLast();
    //移动当前记录指针到第一条记录
    void MoveFirst();
    //初始化数据
    void InitData();
    CDataSource();
    virtual ~CDataSource();
private:
    int m_MaxID;
    _RecordsetPtr m_pRecordset;
    _ConnectionPtr m_pConn;
    //释放数据
    void FreeData();
    
};

  该类对外提供的方法是一些基本数据库操作,用户可根据需要进行添加。

  4.对各种方法的实现,本例中采用的是ADO访问,所以定义了m_pConn和m_pRecordset两个对象。

  实现InitData,作用是初始化数据库访问模块,包括数据库连接的初始化、记录集的初始化和当前数据库中最大ID的初始化,实现代码为:

void CDataSource::InitData()
{
    //初始化Com对象,为使用ADO做准备
    CoInitialize(NULL);
    
    //初始化连接对象
    m_pConn.CreateInstance("ADODB.Connection");
    //初始化记录集对象
    m_pRecordset.CreateInstance("ADODB.Recordset");
    try
    {
        //打开数据库连接
        m_pConn->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\Demo.mdb;Persist Security Info=False", "", "", adConnectUnspecified);
        
        //初始化m_MaxID
        m_pRecordset->Open("Select Max(ID) as MAXID From Profile", _variant_t(m_pConn, true), adOpenStatic, adLockOptimistic, adCmdText);        
        m_MaxID = GetAsInteger("MAXID");
        m_pRecordset->Close();
        
        //打开指定记录集
        m_pRecordset->Open("Select * From Profile", _variant_t(m_pConn, true), adOpenStatic, adLockOptimistic, adCmdText);
        
    }
    catch(_com_error &e)
    {
        ::AfxMessageBox(e.ErrorMessage());
    }
}

  和InitData方法对应的是FreeData方法,作用是关闭打开的记录集和数据库并释放到记录集和数据库指针,注意该函数在CDataSource的析构函数中调用,实现代码为:

void CDataSource::FreeData()
{
    if (m_pConn)
    {
        m_pConn->Close();
        m_pRecordset.Release();
        m_pConn.Release();
        CoUninitialize();
    }
}

  接来下实现数据指针移动函数,这些函数是对ADO数据指针移动操作的封装:

void CDataSource::MoveFirst()
{
    m_pRecordset->MoveFirst();
}
void CDataSource::MoveLast()
{
    m_pRecordset->MoveLast();
}
void CDataSource::MovePrev()
{
    m_pRecordset->MovePrevious();
}
void CDataSource::MoveNext()
{
    m_pRecordset->MoveNext();
}

下面实现的是当前记录指针位置的函数:

BOOL CDataSource::IsFirst()
{
    if (m_pRecordset->BOF)
    {
        return TRUE;
    }
    else
    {
        m_pRecordset->MovePrevious();
        BOOL Result = m_pRecordset->BOF;
        m_pRecordset->MoveNext();
        return Result;
    }
}
BOOL CDataSource::IsLast()
{
    if (m_pRecordset->EndOfFile)
    {
        return TRUE;
    }
    else
    {
        m_pRecordset->MoveNext();
        BOOL Result = m_pRecordset->EndOfFile;
        m_pRecordset->MovePrevious();
        return Result;
    }
}
BOOL CDataSource::IsBOF()
{
    return m_pRecordset->BOF;
}
BOOL CDataSource::IsEOF()
{
    return m_pRecordset->EndOfFile;
}

  其中IsBOF和IsEOF函数是对ADO的直接封装,很容易理解,接来下实现设置和返回记录值的函数

CString CDataSource::GetAsString(CString FieldName)
{
    //如果在第一条记录之前或者最后一条记录之后,返回空
    if (IsBOF() || IsEOF())
        return "";
    
    LPTSTR lpFieldName = FieldName.GetBuffer(FieldName.GetLength());
    
    //得到当前记录指定列的值
    _variant_t vValue = m_pRecordset->Fields->Item[lpFieldName]->Value;
    
    //如果为空值则返回空
    if ((V_VT(&vValue) == VT_NULL) || (V_VT(&vValue) == VT_EMPTY))
    {
        return "";
    }
    //否则以字符串形式返回vValue的值
    else
    {
        CString strResult;
        LPTSTR lpResult = strResult.GetBuffer(strlen(_bstr_t(vValue)));
        strcpy(lpResult, _bstr_t(vValue));
        strResult.ReleaseBuffer();
        return strResult;
    }
}
int CDataSource::GetAsInteger(CString FieldName)
{
    //如果在第一条记录之前或者最后一条记录之后,返回0
    if (IsBOF() || IsEOF())
        return 0;
    LPTSTR lpFieldName = FieldName.GetBuffer(FieldName.GetLength());
    
    //得到当前记录指定列的值
    _variant_t vValue = m_pRecordset->Fields->Item[lpFieldName]->Value;
    
    //如果为空值则返回空
    if (V_VT(&vValue) == VT_NULL)
    {
        return 0;
    }
    
    //否则以int形式返回vValue的值
    else
    {
        return atoi(_bstr_t(vValue));
    }
}
void CDataSource::SetAsString(CString FieldName, CString Value)
{
    //将列名(FieldName)由CString转为LPTSTR型
    LPTSTR lpFieldName = FieldName.GetBuffer(FieldName.GetLength());
    
    //将Value由CString转为LPTSTR型
    LPTSTR lpValue = Value.GetBuffer(Value.GetLength());
    
    //将Value值更新到Recordset中
    m_pRecordset->Fields->Item[lpFieldName]->Value = lpValue;
    //释放缓冲区
    FieldName.ReleaseBuffer();    
    Value.ReleaseBuffer();    
}
void CDataSource::SetAsInteger(CString FieldName, int Value)
{
    CString cs;
    
    //将Value由int型转为CString型
    cs.Format("%d", Value);
    //使用SetAsString设置指定列的值
    SetAsString(FieldName, cs);
}

  最后要实现的是记录集的新建、更新和删除函数,代码如下:

 

  5.界面层的实现

  界面层是对界面显示控制的封装,本例模块比较简单,可将代码直接写入CCallList类中,在CCallList类中定义一个CDataSource类对象:    CDataSource m_ds;在对话框初始化函数OnIninDialog中初始化m_ds的数据,添加:m_ds.InitData();

  为每个编辑框定义一个CEdit类型的对象,如图

 

void CDataSource::New()
{
    //添加一条新的记录
    m_pRecordset->AddNew();
    
    //设置初始值
    m_MaxID++;
    SetAsInteger("ID", m_MaxID);
    SetAsString("NAME", "无名氏");
    SetAsString("NUMBER", "");
    SetAsInteger("CALL", 0);
    SetAsString("QQ", "");
    SetAsString("HOME", "");
    SetAsString("NOTE", "");
    
    //更新
    m_pRecordset->Update();
}
void CDataSource::Update()
{
    m_pRecordset->Update();
}
void CDataSource::Delete()
{
    m_pRecordset->Delete(adAffectCurrent);//只删除当前记录
} 至此,数据访问层类CDataSource已经实现了。整体上看,是对ADO操作的封装,更改数据库时,只需重写InitData函数即可。
 
 
为CCaiiListDlg类添加函数void LoadData,作用是将当前记录的内容装载到界面中

void CCallListDlg::LoadData()
{
     //年龄
    m_edtAge.SetWindowText(m_ds.GetAsString("AGE"));
    //姓名
    m_edtName.SetWindowText(m_ds.GetAsString("NAME"));
    //学号
    m_edtNumber.SetWindowText(m_ds.GetAsString("NUMBER"));
    //手机
    m_edtCall.SetWindowText(m_ds.GetAsString("CALL"));
    //QQ
    m_edtQq.SetWindowText(m_ds.GetAsString("QQ"));
    //家庭住址
    m_edtHome.SetWindowText(m_ds.GetAsString("ADDRESS"));
    //备注
    m_edtNote.SetWindowText(m_ds.GetAsString("NOTE"));
}

  为CCaiiListDlg类添加函数BOOL IsNeedUpdate(),作用是判断用户对界面上的数据是否进行了修改,判断的方法是使用记录中的字段和当前界面中数据比较,如有不同,说明用户修改了具体代码为:

BOOL CCallListDlg::IsNeedUpdate()
{
    CString strTemp;
    
    //判断家庭住址
    m_edtHome.GetWindowText(strTemp);
    if (strTemp != m_ds.GetAsString("HOME"))
        return TRUE;    
    //判断学号
    m_edtNumber.GetWindowText(strTemp);
    if (strTemp != m_ds.GetAsString("NUMBER"))
        return TRUE;
    //判断姓名
    m_edtName.GetWindowText(strTemp);
    if (strTemp != m_ds.GetAsString("NAME"))
        return TRUE;
    //判断备注
    m_edtNote.GetWindowText(strTemp);
    if (strTemp != m_ds.GetAsString("NOTE"))
        return TRUE;    
    //判断手机
    m_edtCall.GetWindowText(strTemp);
    if (strTemp != m_ds.GetAsString("CALL"))
        return TRUE;
    //判断QQ
    m_edtQq.GetWindowText(strTemp);
    if (strTemp != m_ds.GetAsString("QQ"))
        return TRUE;        
    return FALSE;
}

  为每个按钮定义一个CButton类型的对象,如上图2中为CCallListDlg类添加函数 void RefreshUpdateBtn(),作用是刷新“更新”按钮的可用与否;添加void RefreshView(),作用是刷新界面各控件的可用性,两个函数的代码如下:

void CCallListDlg::RefreshUpdateBtn()
{
    //存在更改,设置“更新”按钮可用
    if (IsNeedUpdate())
    {
        m_btnUpdate.EnableWindow(TRUE);
    }
    //不存在更改,设置“更新”按钮不可用
    else 
    {
        m_btnUpdate.EnableWindow(FALSE);
    }
}

void CCallListDlg::RefreshView()
{
    //如果当前记录指针处于数据集第一条记录之前或最后一条记录之后,
    //编辑框控件和单选按钮控件不可用
    if (m_ds.IsBOF() || m_ds.IsEOF())
    {
        m_edtHome.EnableWindow(FALSE);
        m_edtNumber.EnableWindow(FALSE);
        m_edtName.EnableWindow(FALSE);
        m_edtCall.EnableWindow(FALSE);
        m_edtNote.EnableWindow(FALSE);
        m_edtQq.EnableWindow(FALSE);
            
    }
    //否则编辑框控件和单选按钮控件可用
    else
    {
        m_edtHome.EnableWindow(TRUE);
        m_edtNumber.EnableWindow(TRUE);
        m_edtName.EnableWindow(TRUE);
        m_edtCall.EnableWindow(TRUE);
        m_edtNote.EnableWindow(TRUE);
        m_edtQq.EnableWindow(TRUE);
    }
    
    //首先设置各控件均可用
    m_btnDelete.EnableWindow(TRUE);
    m_btnInsert.EnableWindow(TRUE);
    m_btnNext.EnableWindow(TRUE);+
        m_btnPrev.EnableWindow(TRUE);
    m_btnUpdate.EnableWindow(TRUE);
    
    //如果当前记录集处于第一条记录,“上一条”按钮不可用
    if (m_ds.IsFirst())
    {
        m_btnPrev.EnableWindow(FALSE);
    }
    
    //如果当前记录集处于最后一条记录,“下一条”按钮不可用
    if (m_ds.IsLast())
    {
        m_btnNext.EnableWindow(FALSE);
    }
    
    //如果记录集中没有数据,“删除”按钮不可用
    if (m_ds.IsEOF() && m_ds.IsBOF())
        m_btnDelete.EnableWindow(FALSE);
    
    //刷新“更新”按钮
    RefreshUpdateBtn();
} 为每一个按钮定义一个 BN_CLICKED 事件,编辑代码如下:

void CCallListDlg::OnBtnDelete() 
{
    // TODO: Add your control notification handler code here
    m_ds.Delete();
    m_ds.MoveNext();
    if (!m_ds.IsBOF() && m_ds.IsEOF())
    {
        m_ds.MovePrev();
    };
    LoadData();
    RefreshView();
}
void CCallListDlg::OnBtnInsert() 
{
    // TODO: Add your control notification handler code here
    //新增一条记录
    m_ds.New();
    //装载当前记录的数据
    LoadData();
    //刷新界面
    RefreshView();
}
void CCallListDlg::OnBtnNext() 
{
    // TODO: Add your control notification handler code here
    //移动到下一条记录
    m_ds.MoveNext();
    //装载当前记录的数据
    LoadData();    
    //刷新界面显示
    RefreshView();
}
void CCallListDlg::OnBtnPrev() 
{
    // TODO: Add your control notification handler code here
    //移动到上一条记录
    m_ds.MovePrev();
    //装载当前记录的数据
    LoadData();
    //刷新界面显示
    RefreshView();
}
void CCallListDlg::OnBtnUpdate() 
{
    // TODO: Add your control notification handler code here
    CString strTemp;
    
    //将家庭住址保存到记录集
    m_edtHome.GetWindowText(strTemp);
    m_ds.SetAsString("HOME", strTemp);
    
    //将姓名保存到记录集
    m_edtName.GetWindowText(strTemp);
    m_ds.SetAsString("NAME", strTemp);
    
    //将学号保存到记录集
    m_edtNumber.GetWindowText(strTemp);
    m_ds.SetAsString("NUMBER",strTemp);
    
    //将备注保存到记录集
    m_edtNote.GetWindowText(strTemp);
    m_ds.SetAsString("NOTE", strTemp);
    
    //将QQ保存到记录集
    m_edtQq.GetWindowText(strTemp);
    m_ds.SetAsString("QQ", strTemp);
    //将手机保存到记录集
    m_edtCall.GetWindowText(strTemp);
    m_ds.SetAsString("CALL", strTemp);
    //更新到数据库
    m_ds.Update();
    
    //设置更新按钮不可用
    m_btnUpdate.EnableWindow(FALSE);
}

 

  为每个编辑框定义一个EN_CHARGE事件,编辑代码为:

void CCallListDlg::OnChangeEditCall() 
{
    RefreshUpdateBtn();
}

void CCallListDlg::OnChangeEditHome() 
{
    RefreshUpdateBtn();    
}
void CCallListDlg::OnChangeEditName() 
{
    RefreshUpdateBtn();
}
void CCallListDlg::OnChangeEditNote() 
{
    RefreshUpdateBtn();    
}
void CCallListDlg::OnChangeEditNumber() 
{
    RefreshUpdateBtn();    
}
void CCallListDlg::OnChangeEditQq() 
{
    RefreshUpdateBtn();
}

  至此基本信息和按钮部分已经设定完毕,对数据库的表格显示部分操作为:

  在对话框编辑窗口右键单击,选择 Insert ActiveX Control ,插入 Microsoft ADO Data Control 6.0 (SP6) 控件,设置其属性选中Control页签——Use Connection String ,按Build按钮,本例中选择 Microsoft Jet 4.0 OLE DB Provider,点击下一步,写入 C:\\Demo.mdb ; 选中RecordSource页签,在Command Type中选择 2-adCmdTable ,在下面的表中选择储存通讯录的表名;

  插入Microsoft Hierarchical FlexGrid Control控件

  在其属性的ALL标签页中将DataSourcr属性设置为 IDC_ADODC1

  编译运行该程序吧!

 
 

关联文档