CGridCtrl_demo19_01.zip為演示CGridCtrl的使用
CGridCtrl_demo19_02.zip演示與CMYODBC的配合使用
一、引言
在用vc開發關於數據庫的項目時,通常我們只好用微軟的DBGRID作為數據庫表格控件,其實微軟的DBGRID並不好用,想找一份好的幫助文檔都找不到,並且界面並不友好,比起C++Builder中的DBGRID來說是遜色不少,但是DBGRID在開發數據庫的項目中又是常用的控件,所以就一直想找一個好用的DBGRID,可是網上又沒有找到。上次在無意中看到了CGridCtrl(一個很漂亮的表格控件,如果你還沒有用過,可以到http://www.codetools.com/miscctrl/gridctrl.asp/下載,上面還有詳細的使用說明)支持虛模式,在這種模式下,即使你向這個表格插入一百萬條數據,它並不會真的生成一百萬行,而是隨著你的滾動條的滾動,計算出在屏幕上要顯示的行和列,然後會向你提供一個接口,通過這個接口,你可以在這兒設置你要顯示的數據。這給了我一些啟示,我決定用它來做一個DBGRID。下面的例子是它的一個應用。
二、原理
DBGRID和一般的GRID的不同之處在於,一般的GRID並不適合顯示大的數據量,如果你的一個查詢結果有上萬條記錄的話,如果你都要插入到GRID中,這將是一個很慢的過程,並且你在GRID中移動滾動條的話,它的記錄的滾動也是很慢的,而DBGRID並不會真正把這些記錄的數據全部插入到控件中,當DBGRID的滾動條滾動時,它會根據DBGRID的顯示面積的大小和查詢得到的總的記錄數計算出當前應該顯示哪那些行,然後會把那幾行的記錄數據插入到表格中,這樣速度當然是很快的,而且沒有數據量多少的限制。
幸運的是,CGridCtrl類已經為我們提供了這種機制,它是采用虛模式的方式,要使用這種方式,按照以下的步驟就可以了:
步驟一 初始化
void SetVirtualMode(TRUE) 設為虛模式
BOOL SetRowCount(int nRows) 設置總的行數。
BOOL SetFixedRowCount(int nFixedRows = 1)設置固定的行數據
BOOL SetColumnCount(int nCols) 設置列數
BOOL SetFixedColumnCount(int nFixedCols = 1)設置固定的列數
步驟二 響應消息 顯示數據
我們假設CGridCtrl是放在對話框上,而且它關聯的變量是m_Grid,利用ClassWizard添加對話框的OnNotify響應函數。這個響應函數的寫法是固定的,類似下面的代碼:
BOOL CMyOdbcDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
if (wParam == (WPARAM)m_Grid.GetDlgCtrlID())
{
*pResult = 1;
GV_DISPINFO *pDispInfo = (GV_DISPINFO*)lParam;
if (GVN_GETDISPINFO == pDispInfo->hdr.code)
{
/*這是我們自己加的函數,在這個函數裡我們設置當前要顯示的數據*/
SetGridItem(pDispInfo);
return TRUE;
}
}
return CDialog::OnNotify(wParam, lParam, pResult);
}
在上面的代碼中,SetGridItem(pDispInfo)是我們自己加的函數,在這個函數裡我們設置當前要顯示的數據,pDispInfo是一個GV_DISPINFO的結構體對象,在這個結構中包含了每個單元格的信息,如行號,列號,有沒有位圖,背景色,前景色等,CGRIDCTRL會在當前要顯示那個單元格時,會把這個單元格的行號,列號傳遞給我們,我們只要在裡面設置要顯示的數據就可以了。如下面是一個顯示數據的例子。
int CMyOdbcDlg::SetGridItem(GV_DISPINFO *pDispInfo)
{
pDispInfo->item.strText.Format("row%d,col%d",pDispInfo->item.row, pDispInfo->item.col);
return 0;
}
通過上面的介紹,我們應該已經會使用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,
CDatabase *pDatabase = NULL,
CString strSql = "",
CString strFilePath = ""); //構造函數,必須把所要的變量傳遞進去。
int InitGrid();//初始化函數
int SetGridText(GV_DISPINFO *pDispInfo);//被外部函數調用接口,用來返回每個單元格應該顯示的
int Release();//釋放資源
按照下面的步驟做一遍,你就能夠知道它是不是很實用了。
步驟一
新建一個基於對話框的工程,命名為demo2,打開stdafx.h文件,加入#include<afxdb.h>,從例子中把OdbcDBGRIDFILE.h, OdbcDBGRIDFILE.cpp復制到這個工程的目錄下,並且加入到工程中,方法是菜單project->add to project->files,選擇這二個文件就可以了。不要忘了,你要把CGRIDCTRL類的文件都包含進來。
步驟二
在對話框上,按照上面的樣例放上一個CURSTOMER CTROL(就是一個人頭的那個控件),在屬性的CLASS上輸入MFCGridCtrl,ID為IDC_GRIDODBC,然後在放上其它編輯框和按鈕控件,為四個編輯框控件通過CLASSWIZARD關聯變量
CString m_strPass;//口令
CString m_strSource;//數據源名
CString m_strSql;//查詢sql
CString m_strUser;//用戶名
在類CDemo2Dlg中加入下面的幾個成員變量
CGridCtrl m_Grid;
COdbcDBGRIDFILE *m_pMapFile;
CImageList m_ImageList;
CDatabase m_db;
當然你要在CDemo2Dlg的聲明文件中加入
#include "gridctrl.h"
#include "OdbcDBGRIDFILE.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)
{
if (wParam == (WPARAM)m_Grid.GetDlgCtrlID())
{
*pResult = 1;
GV_DISPINFO *pDispInfo = (GV_DISPINFO*)lParam;
if (GVN_GETDISPINFO == pDispInfo->hdr.code)
{
SetGridItem(pDispInfo);
return TRUE;
}
}
return CDialog::OnNotify(wParam, lParam, pResult);
}
在上面的代碼中,SetGridItem(pDispInfo)是我們自己加的函數,在這個函數裡我們設置當前要顯示的數據
步驟四
為CDemo2Dlg加入查詢按鈕的響應函數OnBtnquery()
下面是這個函數的代碼
void CDemo2Dlg::OnBtnquery()
{
this->Release();/*這個函數用於釋放資源的*/
CString strConn;
UpdateData(TRUE);
strConn.Format("ODBC;DSN=%s;UID=%s;PWD=%s",m_strSource,m_strUser, m_strPass);
BOOL bResult = m_db.Open(strConn);
if(bResult == FALSE)
{
return;
}/*上面的代碼用於連接數據庫*/
m_pMapFile = new COdbcDBGRIDFILE(&m_Grid, &m_db, m_strSql, "c:\\csl.txt");
m_pMapFile->InitGrid();/*初始化函數,用於創建內存映射文件等*/
}
在上面的代碼中,調用了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)
{
/*
在這兒你可自己進行設置,如第一行為查詢的字段中文名,
第一行為行的序號等,如果你不進行設置,那麼默認會取每
個查詢得到的字段名,每一行的序號
*/
m_pMapFile->SetGridText(pDispInfo);
/*
上面的函數只是用來得到了本來,你還可以用它來進行另
外一些屬性,如當超過一定的值是顯示DBGRID的不同的前
景色和前景色等
*/
}
步驟六
添加成員函數Release(),下面是它的代碼,用於釋放資源
void CDemo2Dlg::Release()
{
if(this->m_pMapFile)
{
m_pMapFile->Release();
m_db.Close();
delete m_pMapFile; m_pMapFile = NULL;
}
}
在上面的代碼中 m_pMapFile->Release()是COdbcDBGRIDFILE的成員函數,用於釋放所有的資源。
步驟七
添加對話框的消息響應函數OnClose(),釋放所有資源
void CDemo2Dlg::OnClose()
{
this->Release();
CDialog::OnClose();
}
完成好上面的工作,你就可以編譯運行了,在上面的編輯框上輸入數據源名、用戶名、口令和查詢的sql語句,你就可以試試效果了,你可以找一張大一點的表,最好有幾十萬條記錄來測試一下,這個DBGRID怎麼樣。
四、總結
使用這個DBGRID的好處在於我們在查詢時,不需要進行先綁定的操作,而且它比微軟的DBGRID控件方便多了,希望它能為我們用VC開發數據庫項目提高一點效率。如果你看過上一篇文章,用類CMYODBC和CODBCSet來代替MFC的CDatabase和CRecordset的話,那麼運行的效率會有很大的提高,我也寫了一個相似的類CMyODBCDBGRIDFile,並且用這個類做了一個樣例,就是文章篇頭提供的配套代碼二。
本文配套源碼