在 Visual C++ 開發資料庫應用時,ODBC 雖非最新技術,但成熟穩定、文獻豐富,仍是許多維護專案的首選。本文示範以 CRecordView 為視圖基底,搭配 CRecordset 与 CListCtrl(報表模式),實現記錄的新增、刪除、修改、排序、查詢與即時顯示,採用 Access 資料庫(亦可擴充至 SQL Server、Oracle),介面簡潔直觀。
CRecordView 直接綁定 CRecordset,資料移動自動同步 UI。LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES,整行選取、格線清晰。void CLhwyView::OnInitialUpdate()
{
CRecordView::OnInitialUpdate();
// 啟用完整列選取與格線
m_ListCtrl.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
// 插入欄位(標題、寬度、對齊)
m_ListCtrl.InsertColumn(0, _T("學號"), LVCFMT_LEFT, 90);
m_ListCtrl.InsertColumn(1, _T("姓名"), LVCFMT_LEFT, 90);
m_ListCtrl.InsertColumn(2, _T("數學"), LVCFMT_RIGHT, 90);
m_ListCtrl.InsertColumn(3, _T("英語"), LVCFMT_RIGHT, 90);
// 重要:屬性頁必須設定 View = Report
Show(); // 初始載入所有記錄
}
int CLhwyView::Show()
{
m_ListCtrl.DeleteAllItems(); // 清空舊資料
if (!m_pSet->IsOpen()) return 0;
m_pSet->MoveFirst();
int i = 0;
CString str;
while (!m_pSet->IsEOF()) {
// 欄位 0:學號(整數轉字串)
str.Format(_T("%d"), m_pSet->m_column1);
m_ListCtrl.InsertItem(i, str);
// 欄位 1:姓名
m_ListCtrl.SetItemText(i, 1, m_pSet->m_column2);
// 欄位 2、3:數學、英語分數
str.Format(_T("%d"), m_pSet->m_column3);
m_ListCtrl.SetItemText(i, 2, str);
str.Format(_T("%d"), m_pSet->m_column4);
m_ListCtrl.SetItemText(i, 3, str);
i++;
m_pSet->MoveNext();
}
return i; // 回傳筆數
}
void CLhwyView::OnAdd()
{
if (m_pSet->CanAppend()) {
m_pSet->AddNew();
// 這裡可彈出對話框讓使用者輸入欄位值
// 假設已由 DDX 綁定至 m_pSet 成員變數
m_pSet->Update(); // 寫入資料庫
Show(); // 刷新顯示
}
}
void CLhwyView::OnDelete()
{
int nSel = m_ListCtrl.GetNextItem(-1, LVNI_SELECTED);
if (nSel == -1) return; // 未選取
// 取得該列主鍵(學號)
CString strId = m_ListCtrl.GetItemText(nSel, 0);
long id = _ttol(strId);
// 定位至該記錄並刪除
CString strFilter;
strFilter.Format(_T("學號 = %ld"), id);
m_pSet->m_strFilter = strFilter;
m_pSet->Requery();
if (!m_pSet->IsEOF()) {
m_pSet->Delete();
m_pSet->m_strFilter.Empty();
m_pSet->Requery();
Show();
}
}
void CLhwyView::OnEdit()
{
int nSel = m_ListCtrl.GetNextItem(-1, LVNI_SELECTED);
if (nSel == -1) return;
CString strId = m_ListCtrl.GetItemText(nSel, 0);
long id = _ttol(strId);
CString strFilter;
strFilter.Format(_T("學號 = %ld"), id);
m_pSet->m_strFilter = strFilter;
m_pSet->Requery();
if (!m_pSet->IsEOF()) {
m_pSet->Edit();
// 彈出編輯對話框,DDX 綁定 m_pSet 成員
// 使用者確定後
m_pSet->Update();
m_pSet->m_strFilter.Empty();
m_pSet->Requery();
Show();
}
}
// 排序:點擊欄標題時
void CLhwyView::OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNM = (NM_LISTVIEW*)pNMHDR;
static int lastCol = -1;
static BOOL bAsc = TRUE;
if (pNM->iSubItem == lastCol) bAsc = !bAsc;
else { lastCol = pNM->iSubItem; bAsc = TRUE; }
CString sortField;
switch (pNM->iSubItem) {
case 0: sortField = _T("學號"); break;
case 1: sortField = _T("姓名"); break;
case 2: sortField = _T("數學"); break;
case 3: sortField = _T("英語"); break;
}
m_pSet->m_strSort = sortField + (bAsc ? _T(" ASC") : _T(" DESC"));
m_pSet->Requery();
Show();
*pResult = 0;
}
// 查詢:依姓名關鍵字模糊搜尋
void CLhwyView::OnSearch()
{
CString keyword;
GetDlgItemText(IDC_EDIT_SEARCH, keyword);
if (keyword.IsEmpty()) { m_pSet->m_strFilter.Empty(); }
else { m_pSet->m_strFilter.Format(_T("姓名 LIKE '*%s*'"), keyword); }
m_pSet->Requery();
Show();
}
專案採用 DSN-less 連接字串,避免部署時需於客戶機器建立 ODBC 系統 DSN:
// 在 CRecordset 派生類的 GetDefaultConnect()
return _T("Driver={Microsoft Access Driver (*.mdb)};Dbq=student.mdb;");
若改用 SQL Server,僅需修改連接字串:
return _T("Driver={SQL Server};Server=.;Database=School;UID=sa;PWD=123;");

圖:完整的學生成績管理介面,支援增刪改查排序
CRecordView 需在 OnInitialUpdate 中呼叫 m_pSet->Open(),否則視圖無法綁定資料集。Requery() 後記錄指標重置,務必檢查 IsBOF()/IsEOF() 再操作。Show() 逐筆 InsertItem 會閃爍,可改用 SetRedraw(FALSE) 批次更新。透過 CRecordView + CRecordset + CListCtrl 的經典組合,配合 Show() 封裝與 m_strFilter/m_strSort 動態查詢排序,即可快速建構功能完整、介面專業的 ODBC 資料庫應用。此架構易於維護、擴充至其他資料庫,是 MFC 資料庫開發的標準範本。