一、引言 在用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,並且用這個類做了一個範例,就是文章篇頭提供的配套代碼二。 |