一、引言 在用vc开发关于数据库的项目时,通常我们只好用微软的DBGRID作为数据库表格控件,其实微软的DBGRID并不好用,想找一份好的帮助文档都 找不到,并且界面并不友好,比起C++Builder中的DBGRID来说是逊色不少,但是DBGRID在开发数据库的项目中又是常用的控件,所以就一直 想找一个好用的DBGRID,可是网上又没有找到。上次在无意中看到了CGridCtrl(一个很漂亮的表格控件,如果你还没有用过,可以到 http://www.codetools.com/miscctrl/gridctrl.asp/下载,上面还有详细的使用说明)支持虚模式,在这种模 式下,即使你向这个表格插入一百万条数据,它并不会真的生成一百万行,而是随着你的滚动条的滚动,计算出在屏幕上要显示的行和列,然后会向你提供一个接 口,通过这个接口,你可以在这儿设置你要显示的数据。这给了我一些启示,我决定用它来做一个DBGRID。下面的例子是它的一个应用。 二、原理 幸运的是,CGridCtrl类已经为我们提供了这种机制,它是采用虚模式的方式,要使用这种方式,按照以下的步骤就可以了: void SetVirtualMode(TRUE) 设为虚模式步骤二 响应消息 显示数据 我们假设CGridCtrl是放在对话框上,而且它关联的变量是m_Grid,利用ClassWizard添加对话框的OnNotify响应函数。这个响应函数的写法是固定的,类似下面的代码: BOOL CMyOdbcDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)在上面的代码中,SetGridItem(pDispInfo)是我们自己加的函数,在这个函数里我们设置当前要显示的数据,pDispInfo是一个 GV_DISPINFO的结构体对象,在这个结构中包含了每个单元格的信息,如行号,列号,有没有位图,背景色,前景色等,CGRIDCTRL会在当前要 显示那个单元格时,会把这个单元格的行号,列号传递给我们,我们只要在里面设置要显示的数据就可以了。如下面是一个显示数据的例子。 int CMyOdbcDlg::SetGridItem(GV_DISPINFO *pDispInfo) 通过上面的介绍,我们应该已经会使用CGridCtrl虚模式,下面说明一下用CGridCtrl虚模式做DBGRID的原理,大家都知 道,MFC的 CRecordset类支持多种游标机制,如双向游标的,如果我们是用ClassWizard来生成一个查询的CRecordset的派生类的话,那么可 以调用函数CRecordset::SetAbsolutePosition(),用这种方式方式来做DBGRID真是太简单了,因为在上面的int CMyOdbcDlg::SetGridItem(GV_DISPINFO *pDispInfo)函数中,我们已经知道要显示的是哪一行,哪一列的数据,所以只要通过 CRecordset::SetAbsolutePosition(pDispInfo->item.row)函数,把游标定位到那一行,然后获取 每个字段的数据就可以了。 但是使用上面的方法有一个不好的地方在于,我们必须用ClassWizard为每个查询从CRecordset派生出新类,这样做很不方便,在VC知识库 第六期上面有一篇介绍“单独使用CRecordset”文章,可是上面的CRecordset打开方式只能使用 CRecordset::forwardOnly,游标只能向前滚动,我们不能使用CRecordset::SetAbsolutePosition() 函数,如果要想使用方便的话,我们必须想别的办法,来提供当前要显示的那个单元格的数据。 我们知道,oracle数据库并不支持双向游标,那么为什么我们用ClassWizard为查询从CRecordset派生的类能够使游标双向移动呢?我 的猜想是这样的,CRecordset类其实把每次获取的数据都保存了下来,并且保存了每行记录所在的位置,这样,当我们调用 CRecordset::SetAbsolutePosition()函数时,它就可以得到我们想要的数据了。不管这种猜想对不对,按照上面的思路已经实 现一个很好用的DBGRID。我把封装成了类COdbcDBGRIDFILE,它实现的原理是首先得到所查询的行数,列数,然后设置CGridCtrl的 属性,如把它设为虚模式,设定CGridCtrl的行数和列数等。然后使用内存映射文件来保存每条记录数据,同时用一个结构体来记录下这条记录所在的位 置。 这里有一个小技巧,如果查询的结果有几十万条的话,如果我们一开始就把所有的这些记录都保存在内存映射文件中的话,那么时间要很长,所以根本不能满足应 用,所以我们在开始时,只会获取记录集的一部分用于显示,当用户在CGRIDCTRL上拖动滚动条向下滚动时,它会随着用户的滚动不断的获取数据,这样显 示上面就一点问题也没有。 三、使用实例 好了,给大家说了这么多,下面就看看如何使用这个封装好的类COdbcDBGRIDFILE 这个类对外的接口只有4个函数可以被调用,下面简单的说明一下这4个函数 COdbcDBGridFILE(CGridCtrl *pGrid = NULL,按照下面的步骤做一遍,你就能够知道它是不是很实用了。 步骤一 新建一个基于对话框的工程,命名为demo2,打开stdafx.h文件,加入#include<afxdb.h>,从例子中把 OdbcDBGRIDFILE.h, OdbcDBGRIDFILE.cpp复制到这个工程的目录下,并且加入到工程中,方法是菜单project->add to project->files,选择这二个文件就可以了。不要忘了,你要把CGRIDCTRL类的文件都包含进来。 步骤二 CString m_strPass;//口令在类CDemo2Dlg中加入下面的几个成员变量 CGridCtrl m_Grid;当然你要在CDemo2Dlg的声明文件中加入 #include "gridctrl.h"在CDemo2Dlg::DoDataExchange(CDataExchange* pDX)函数中加入DDX_Control(pDX, IDC_GRIDODBC, m_Grid); 在CDemo2Dlg的构造函数中加入 m_pMapFile = NULL;步骤三 用ClassWizard生成CDemo2Dlg的CDemo2Dlg::OnNotify消息响应函数,在这个函数中,输入如下的代码 BOOL CDemo2Dlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)在上面的代码中,SetGridItem(pDispInfo)是我们自己加的函数,在这个函数里我们设置当前要显示的数据 步骤四 为CDemo2Dlg加入查询按钮的响应函数OnBtnquery() 下面是这个函数的代码 void CDemo2Dlg::OnBtnquery()在上面的代码中,调用了COdbcDBGRIDFILE的构造函数,它的原型是 COdbcDBGRIDFILE::COdbcDBGRIDFILE(CGridCtrl *pGrid , CDatabase *pDatabase, CString strSql, CString strFilePath),其中的参数的含义如下 CGridCtrl *pGrid-------是一个指向CGridCtrl的指针 CDatabase *pDatabase------是一个指向Cdatabase的指针,你必须把一个已经连接好的Cdatabase的对象传递进去。 CString strSql------是一个查询语句,如select * from emp; CString strFilePath-----是一个用于生成内存映射文件的文件的路径 步骤五 为CDemo2Dlg加入成员函数SetGridItem(pDispInfo),这个函数是CDemo2Dlg::OnNotify()中调用的,我们用它来设置显示记录数据。 void CDemo2Dlg::SetGridItem(GV_DISPINFO *pDispInfo)步骤六 添加成员函数Release(),下面是它的代码,用于释放资源 void CDemo2Dlg::Release()在上面的代码中 m_pMapFile->Release()是COdbcDBGRIDFILE的成员函数,用于释放所有的资源。
void CDemo2Dlg::OnClose()完成好上面的工作,你就可以编译运行了,在上面的编辑框上输入数据源名、用户名、口令和查询的sql语句,你就可以试试效果了,你可以找一张大一点的表,最好有几十万条记录来测试一下,这个DBGRID怎么样。 四、总结 |