设计思路:
分层:界面层,数据访问层,数据库;
数据访问层的作用是为界面层提供一个数据访问接口,隔离界面层和数据库;界面层不需要知道采用的哪种数据库,只需要调用数据访问层提供的接口来完成各种操作;
1.新建一个基于对话框的MFC程序,命名CallList
2.设计程序界面如下图:
3.数据访问层的实现:
在StdAfx.h中添加#import "c:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace rename("EOF", "EndOfFile")
新建一个类CDataSource,作用是封装数据层,定义如下
Code
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的初始化,实现代码为:
Code
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数据指针移动操作的封装:
Code
void CDataSource::MoveFirst()
{
m_pRecordset->MoveFirst();
}
void CDataSource::MoveLast()
{
m_pRecordset->MoveLast();
}
void CDataSource::MovePrev()
{
m_pRecordset->MovePrevious();
}
void CDataSource::MoveNext()
{
m_pRecordset->MoveNext();
}
下面实现的是当前记录指针位置的函数:
Code
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的直接封装,很容易理解,接来下实现设置和返回记录值的函数
Code
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);
}
最后要实现的是记录集的新建、更新和删除函数,代码如下:
Code
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函数即可。
5.界面层的实现
界面层是对界面显示控制的封装,本例模块比较简单,可将代码直接写入CCallList类中,
在CCallList类中定义一个CDataSource类对象: CDataSource m_ds;
在对话框初始化函数OnIninDialog中初始化m_ds的数据,添加:m_ds.InitData();
为每个编辑框定义一个CEdit类型的对象,如图
为CCaiiListDlg类添加函数void LoadData,作用是将当前记录的内容装载到界面中
Code
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(),作用是判断用户对界面上的数据是否进行了修改,判断的方法是使用记录中的字段和当前界面中数据比较,如有不同,说明用户修改了具体代码为:
Code
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(),作用是刷新界面各控件的可用性,两个函数的代码如下:
Code
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 事件,编辑代码如下:
Code
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事件,编辑代码为:
Code
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
编译运行该程序吧!