1、 基于ADO方式的VC+数据库访问技术介绍一、ADO概述我们先介绍一下ADO,让大家对ADO有个大概的了解。ADO是Microsoft为最新和最强大的数据访问范例 OLE DB 而设计的,是一个便于使用的应用程序层接口。ADO 使您能够编写应用程序以通过 OLE. DB 提供者访问和操作数据库服务器中的数据。与ODBC相比,ADO 最主要的优点是易于使用、速度快、内存支出少和磁盘遗迹小及可移植性好。下面我们结合具体的例子给大家讲解VC+中ADO是如何对数据库进行操作的。二、系统设计本系统主要由用户登陆模块、用户操作界面模块和管理模块构成。实现生物医学工程学生信息的管理。三、建立数据库和数据表我
2、们用Microsoft Office 2003中的Microsoft Access 2003来进行数据库与数据表的创建工作。1、 建立“shengyi”数据库(1)启动Microsoft Office 2000中的Microsoft Access 2000应用程序,再出现的对话框中创建一个新的数据库或打开一个已经存在的数据库。(2)选择创建新数据库,即选择“空Access 数据库” 选项。(3)单击“确定”按钮后出现保存对话框,确定保存数据库的文件位置,将数据库命名为:shengyi。(4)在数据表设计界面中,用户可以打开已经存在的数据表,也可以创建新的数据表。单击“设计”菜单项,出现数据表设
3、计器,用户可以设计一个新的数据表结构。2、分别建立用户登陆表、年级表、学生个人信息表,如图1图1(1) class数据表的结构如图2 图2(2) denglu数据表的结构如图3图3(3)personal数据表的结构如图4图4创建完成后可以在数据表中输入一些数据。以在后面的登陆系统界面中体现其效果。四、ADO与数据库的连接具体的建立过程相信大家都已经知道了,这里我们就不再详述了。本系统建立的是但文档类型,工程名称为sheng。1、 引入ADO库文件使用ADO前必须在工程的stdafx.h头文件里用直接引入符号#import引入ADO库文件,以使编译器能正确编译。代码如下所示: 用#import引
4、入ADO库文件 #import c:program filescommon filessystemadomsado15.dllno_namespaces rename(EOF,adoEOF) 这行语句声明在工程中使用ADO,但不使用ADO的名字空间,并且为了避免常数冲突,将常数EOF改名为adoEOF。现在不需添加另外的头文件,就可以使用ADO接口了。2、初始化OLE/COM库环境必须注意的是,ADO库是一组COM动态库,这意味应用程序在调用ADO前,必须初始化OLE/COM库环境。在MFC应用程序里,一个比较好的方法是在应用程序主类的InitInstance成员函数里初始化OLE/COM库环
5、境。AfxOleInit();m_pConnection.CreateInstance(ADODB.Connection);/*连接数据库*/trym_pConnection-ConnectionTimeout = 8;m_pConnection-Open(Provider=Microsoft.Jet.OLEDB.4.0;Data Source=shengyi.mdb,adModeUnknown);catch(_com_error e)/捕捉异常AfxMessageBox(数据库连接失败!);return FALSE;其中m_pConnection为_ConnectionPtr类的变量。最后还
6、要在ExitInstance()中编写代码释放程序占用的COM资源。m_pConnection-Close();:OleUninitialize();注意:在vc开发环境中运行,则需要将该数据库文件放在工程目录下,否则会出现错误。五、系统设计1、设计主对话框,并加入相应的控件。本系统主要应用List Control和Tree Control控件,设置List Control的属性如图5图5Tree Control的属性如图6图6设计的主对话框如图7图7利用ClassWizard建立基于主对话框的类,命名为CMaindlg。为相应的控件关联相应的变量,如图8图8(1)连接相应的数据表为了对数据表
7、中的纪录进行操作,我们必须先连接数据表,在CMaindlg:OnInitDialog()编写如下代码即可。m_mRecordset.CreateInstance(ADODB.Recordset);m_mRecordset-Open(select * from class,theApp.m_pConnection.GetInterfacePtr(),adOpenDynamic,adLockOptimistic,adCmdText);其中m_mRecordset为_RecordsetPtr类型的共有变量。用于访问数据库中的记录集。(2)初始化List Control和Tree Control。其实
8、只要在OnInitDialog()中加入相应的代码即可,但为了提高代码的重复利用率。我们编写相关的子函数。这样只需在OnInitDialog()调用子函数可。(a)void CMaindlg:listctrl() m_mRecordset-Close();m_mRecordset.CreateInstance(ADODB.Recordset);m_mRecordset-Open(select * from personal,theApp.m_pConnection.GetInterfacePtr(),adOpenDynamic,adLockOptimistic,adCmdText); DWOR
9、D style;m_list.DeleteAllItems();style=m_list.GetStyle();style=(style|LVS_EX_GRIDLINES|LVS_EX_FULLROWSELECT)&(LVS_EX_CHECKBOXES);m_list.SetExtendedStyle(style);/m_list.InsertColumn(0,ID,LVCFMT_LEFT,100); m_list.InsertColumn(0,姓名,LVCFMT_LEFT,100);m_list.InsertColumn(1,出生日期,LVCFMT_LEFT,100);m_list.Inse
10、rtColumn(2,性别,LVCFMT_LEFT,100);m_list.InsertColumn(3,年级,LVCFMT_LEFT,100);m_list.InsertColumn(4,班级,LVCFMT_LEFT,100);m_list.InsertColumn(5,联系方式,LVCFMT_LEFT,100);int i=0;while(!(m_mRecordset-adoEOF)/判断是不是数据表结束/将数据插入列表框中CString str;m_list.InsertItem(i,(CShengApp*)AfxGetApp()-GetStringFromVariant(m_mReco
11、rdset-GetCollect(姓名);m_list.SetItemText(i,1,(CShengApp*)AfxGetApp()-GetStringFromVariant(m_mRecordset-GetCollect(出生日期);m_list.SetItemText(i,2,(CShengApp*)AfxGetApp()-GetStringFromVariant(m_mRecordset-GetCollect(性别);m_list.SetItemText(i,3,(CShengApp*)AfxGetApp()-GetStringFromVariant(m_mRecordset-GetC
12、ollect(年级);m_list.SetItemText(i,4,(CShengApp*)AfxGetApp()-GetStringFromVariant(m_mRecordset-GetCollect(班级);m_list.SetItemText(i,5,(CShengApp*)AfxGetApp()-GetStringFromVariant(m_mRecordset-GetCollect(联系方式);i+;m_mRecordset-MoveNext();(b)void CMaindlg:showlistctrl()/m_mRecordset.CreateInstance(ADODB.Re
13、cordset);m_mRecordset.CreateInstance(ADODB.Recordset);m_mRecordset-Open(select * from personal,theApp.m_pConnection.GetInterfacePtr(),adOpenDynamic,adLockOptimistic,adCmdText); listctrl();(c)void CMaindlg:showclass()TV_INSERTSTRUCT tvInsert; HTREEITEM h1;tvInsert.hParent=NULL;tvInsert.hInsertAfter=T
14、VI_LAST;tvInsert.item.mask=TVIF_TEXT;tvInsert.item.pszText= _T(生物医学工程专业);h1=m_tree.InsertItem(&tvInsert);CString n,c;/n=m_pRecordset-GetCollect(nianji).bstrVal;/b=m_pRecordset-GetCollect(bianji).bstrVal;CString a100;int j=1; m_mRecordset-MoveFirst();c=m_mRecordset-GetCollect(nianji).bstrVal;a0=c; wh
15、ile(!m_mRecordset-adoEOF) CString n;n=m_mRecordset-GetCollect(nianji).bstrVal; if(c!=n)aj=n;j+; c=n; m_mRecordset-MoveNext();int r=0;CString g100; m_mRecordset-MoveFirst(); CString k1=d;for(int i=0;ij;i+) for(int t=i+1;tj;t+) if(ai=at|at=k1)at=k1;for (int u=0;uj;u+) if(au!=k1) gr+=au;for(int f=0;fMo
16、veFirst(); while(!m_mRecordset-adoEOF) CString n1; n1=m_mRecordset-GetCollect(nianji).bstrVal;if(gf=n1) CString b;b=m_mRecordset-GetCollect(banji).bstrVal;HTREEITEM s1=m_tree.InsertItem(b,3,4,g1); m_mRecordset-MoveNext(); m_tree.Expand(h1,TVE_EXPAND);(3) 其他控件的消息响应函数代码见本程序即可。这里就不再一一说明了。2、 学生信息对话框,如图9
17、图9(1)初始化同样我们也要建一个基于此对话框的类,这里我们命名为CInformation,为了实现数据库记录集的操作,我们添加一个_RecordsetPtr类型的成员变量m_pRecordset,在CInformation:OnInitDialog()添加代码实现与数据表的连接(上面已介绍过,这里就不在说明,具体代码见本程序)。(2)控件属性及消息映射函数这里我们只介绍有关图片的部分,其他控件就不在叙述了。从前面的数据表可以看出,图片在数据库中的存储形式为OLE对象。通过数据集对象的成员变量自动交换得到的图像数据是二进制形式,因而并不能直接显示。(a) 从数据库读图片从数据库读取图片时,可以
18、直接用Field对象的函数GetChunk()将数据库中的位图数据(对应字段为“照片”)读出来,保存在char*类型的变量m_pBMPBuffer中。然后将内存中的BMP文件内容转换到HBITMAP类型的句柄,便于显示。这些工作分别在函数ReadData()和BufferToHBITMAP()中实现,其代码如下:/度曲记录集当前记录中的数据,包括图像数据/void CInformation:ReadData() m_pRecordset-Move(m_pos);/通过m_pos传递双击位置m_ename.SetFocus();DestroyPhoto();/清除原图像if(m_pRecords
19、et-adoEOF|m_pRecordset-BOF) m_name.Empty();m_nian.Empty();m_birthday.Empty();m_ban.Empty();m_call.Empty();UpdateData(FALSE);return;long lDataSize = m_pRecordset-GetFields()-GetItem(照片)-ActualSize;if(lDataSize 0)_variant_tvarBLOB;varBLOB = m_pRecordset-GetFields()-GetItem(照片)-GetChunk(lDataSize);if(v
20、arBLOB.vt = (VT_ARRAY | VT_UI1)/重新分配必要的存储空间if(m_pBMPBuffer = new charlDataSize+1) char *pBuf = NULL; SafeArrayAccessData(varBLOB.parray,(void *)&pBuf);/复制数据到缓冲区m_pBMPBuffermemcpy(m_pBMPBuffer,pBuf,lDataSize);SafeArrayUnaccessData (varBLOB.parray);m_nFileLen = lDataSize;/生成BITMAP对象m_hPhotoBitmap = Bu
21、fferToHBITMAP();m_name = m_pRecordset-GetCollect(姓名).bstrVal;m_nian=m_pRecordset-GetCollect(年级).bstrVal; m_ban=m_pRecordset-GetCollect(班级).bstrVal;m_sex1=m_pRecordset-GetCollect(性别).bstrVal;m_birthday=m_pRecordset-GetCollect(出生日期).bstrVal;m_call=m_pRecordset-GetCollect(联系方式).bstrVal;m_ename.EnableWi
22、ndow();m_ecall.EnableWindow();m_enian.EnableWindow();m_eban.EnableWindow();m_ebirthday.EnableWindow();m_sex.EnableWindow();m_buttonSelectPhoto.EnableWindow();UpdateData(FALSE);/将内存中的BMP文件内容转换到HBITMAP/HBITMAP CInformation:BufferToHBITMAP()HBITMAPhBmp;LPSTRhDIB,lpBuffer = m_pBMPBuffer;LPVOIDlpDIBBits;
23、BITMAPFILEHEADERbmfHeader;DWORDbmfHeaderLen;/获得位图的头信息bmfHeaderLen = sizeof(bmfHeader);strncpy(LPSTR)&bmfHeader,(LPSTR)lpBuffer,bmfHeaderLen);/根据获得的信息头判断是否是位图if (bmfHeader.bfType != (*(WORD*)BM) return NULL;/获取位图数据hDIB = lpBuffer + bmfHeaderLen;BITMAPINFOHEADER &bmiHeader = *(LPBITMAPINFOHEADER)hDIB
24、;BITMAPINFO &bmInfo = *(LPBITMAPINFO)hDIB ;lpDIBBits=(lpBuffer)+(BITMAPFILEHEADER *)lpBuffer)-bfOffBits;/创建位图CClientDC dc(this);hBmp = CreateDIBitmap(dc.m_hDC,&bmiHeader,CBM_INIT,lpDIBBits,&bmInfo,DIB_RGB_COLORS);return hBmp;将转换后的位图句柄在函数DrawUserPhoto(int x, int y, CDC *pDC)中将位图句柄输出到屏幕,然后在OnPaint()函数
25、中调用即可。其代码如下:/在屏幕上输出图像/void CInformation:DrawUserPhoto(int x, int y, CDC *pDC)if(!m_hPhotoBitmap) return;HBITMAP OldBitmap;CDC MemDC;MemDC.CreateCompatibleDC(pDC);OldBitmap=(HBITMAP)MemDC.SelectObject(m_hPhotoBitmap);pDC-BitBlt(x,y,100,130,&MemDC,0,0,SRCCOPY);MemDC.SelectObject(OldBitmap);/画客户区/void
26、CInformation:OnPaint() CPaintDC dc(this); / device context for painting/ TODO: Add your message handler code here/CPaintDC dc(this);if (IsIconic()SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);/ Center icon in client rectangleint cxIcon = GetSystemMetrics(SM_CXICON);int cyIcon = GetSyst
27、emMetrics(SM_CYICON);CRect rect;GetClientRect(&rect);int x = (rect.Width() - cxIcon + 1) / 2;int y = (rect.Height() - cyIcon + 1) / 2;/ Draw the icondc.DrawIcon(x, y, m_hIcon);elseDrawUserPhoto(295,60,&dc);CDialog:OnPaint();/ Do not call CDialog:OnPaint() for painting messages(b) 保存图像数据时,先将位图文件读入内存,
28、保存在m_pBMPBuffer中,然后调用Field对象的函数AppendChunk()一次性将m_pBMPBuffer中的数据写入数据库的“照片”字段中。这些工作分别在和函数中实现,期代码如下:/加载BMP文件到内存/BOOL CInformation:LoadBMPFile(const char *pBMPPathname)CFile file;if( !file.Open( pBMPPathname, CFile:modeRead) )return FALSE;m_nFileLen = file.GetLength();m_pBMPBuffer = new charm_nFileLen
29、+ 1;if(!m_pBMPBuffer)return FALSE;if(file.ReadHuge(m_pBMPBuffer,m_nFileLen) != m_nFileLen)return FALSE;return TRUE;/保存学生信息/void CInformation:Onsave() / TODO: Add your control notification handler code hereGetDlgItem(IDC_add)-SetWindowText(清空字段);if(!m_bModify) return;UpdateData();if(m_pBMPBuffer = NU
30、LL | m_hPhotoBitmap = NULL | m_name = | m_nian = |m_ban=|m_call=|m_birthday=|m_sex1=)AfxMessageBox(您没有提供完整的用户信息。rn这些信息包括:姓名、出生日期、照片等);return;m_buttonSaveInfo.EnableWindow(FALSE);char*pBuf = m_pBMPBuffer;VARIANTvarBLOB;SAFEARRAY*psa; SAFEARRAYBOUNDrgsabound1;if(m_bNewUser)m_pRecordset-AddNew();m_pRec
31、ordset-PutCollect(姓名,_variant_t(m_name);m_pRecordset-PutCollect(出生日期,atol(m_birthday);m_pRecordset-PutCollect(年级,_variant_t(m_nian);m_pRecordset-PutCollect(班级,_variant_t(m_ban);m_pRecordset-PutCollect(性别,_variant_t(m_sex1);m_pRecordset-PutCollect(联系方式,_variant_t(m_call);if(pBuf) rgsabound0.lLbound =
32、 0;rgsabound0.cElements = m_nFileLen;psa = SafeArrayCreate(VT_UI1, 1, rgsabound);for (long i = 0; i GetFields()-GetItem(照片)-AppendChunk(varBLOB);m_pRecordset-Update();m_bNewUser = FALSE;m_bModify = FALSE;注意:学生信息对话框与主对话框靠列表框的双击事件联系起来的所以在CInformation:OnInitDialog()中还必须调用ReadData()。3、 登陆对话框,如图10图10(1)
33、其中编辑框的属性设置选中Password,并建一个基于该对话框的类CDenglu,用前面介绍的方法在CDenglu:OnInitDialog()中实现数据表的连接。并为相应的控件关联变量。这里也不做详述。(2) 操作登陆表。为了美化程序,我们把对登陆表的操作放在菜单里,如图11图11为“用户登陆”菜单添加消息函数,右击在弹出菜单中选择ClassWizard,如图12图12选中后回弹出如图13所示的对话框。具体代码见本程序。图13选中COMMAND双击即可。这样就完成了消息函数的添加,其他菜单项也如此添加。(3) 美化登陆界面为了美化登陆界面,我们为其添加一个CButtonST类,为了对Stat
34、ic文本框进行操作,我们要给他改一个ID,如图14图14此外我们还要对OK与Cancel两个Button控件在ClassWizard中关联变量。如图15图15接着在CDenglu:OnInitDialog()和CDenglu(CWnd* pParent /*=NULL*/): CDialog(CDenglu:IDD, pParent)里添加初始化程序BOOL CDenglu:OnInitDialog() CDialog:OnInitDialog();/ TODO: Add extra initialization herem_dcan.SetActiveBgColor(RGB(230,232,
35、250); m_dok.SetActiveBgColor(RGB(230,232,250); m_dcan.SetInactiveBgColor(RGB(162,189,252); m_dok.SetInactiveBgColor(RGB(162,189,252); return TRUE; / return TRUE unless you set the focus to a control / EXCEPTION: OCX Property Pages should return FALSECDenglu:CDenglu(CWnd* pParent /*=NULL*/): CDialog(
36、CDenglu:IDD, pParent)/AFX_DATA_INIT(CDenglu)m_password = _T();m_username = _T();/AFX_DATA_INITm_brush.CreateSolidBrush(RGB(160,180,220);其中m_brush为CBrush 类型成员变量。最后控件颜色的改变在CDenglu:OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)函数中实现。HBRUSH CDenglu:OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) HBRU
37、SH hbr = CDialog:OnCtlColor(pDC, pWnd, nCtlColor);/ TODO: Change any attributes of the DC hereif(pWnd-GetDlgCtrlID()=IDC_u)/pDC-SetBkMode(TRANSPARENT);pDC-SetTextColor(RGB(0,0,0);/pDC-SetBkColor(RGB(233,233,220);pDC-SetBkMode(TRANSPARENT);/HBRUSH b=CreateSolidBrush(RGB(233,233,220);return m_brush;el
38、se if(pWnd-GetDlgCtrlID()=IDC_mima)/pDC-SetBkMode(TRANSPARENT);pDC-SetTextColor(RGB(0,0,0);pDC-SetBkMode(TRANSPARENT);return m_brush;else if(pWnd-GetDlgCtrlID()=IDC_username)/pDC-SetBkMode(TRANSPARENT);pDC-SetTextColor(RGB(0,0,0);pDC-SetBkColor(RGB(233,233,220);HBRUSH b=CreateSolidBrush(RGB(233,233,
39、220);return b;else if(pWnd-GetDlgCtrlID()=IDC_password)/pDC-SetBkMode(TRANSPARENT);pDC-SetTextColor(RGB(0,0,0);pDC-SetBkColor(RGB(233,233,220);HBRUSH b=CreateSolidBrush(RGB(233,233,220);return b;/ TODO: Return a different brush if the default is not desired return m_brush;这样就完成了登陆对话框的美化。六、总结1、我在前面的叙
40、述中没有提到头文件的添加问题,故在这里做一个总结。如果你在一个类中要用到另一个类的公有成员变量或成员函数,就必须添加那个类的头文件。格式为#include ”xxxx.h”。2、当我们用m_pRecordset.CreateInstance(ADODB.Recordset);m_pRecordset.CreateInstance(ADODB.Recordset);m_pRecordset-Open(select * from denglu,theApp.m_pConnection.GetInterfacePtr(),adOpenDynamic,adLockOptimistic,adCmdText);这些语句连接数据表时,一定要定义外部变量theApp,即extern CShengApp theApp;否则会报错。作者:南昌航空大学自动化学院生物医学工程系 050841 张轩(女)25