傳統 CListView 需預先插入所有項目,資料量大時極其緩慢。IE4 引入的 虛擬清單模式 (LVS_OWNERDATA) 僅在需要顯示時透過 LVN_GETDISPINFO 回呼請求資料,極大提升效能。本文示範搭配 DAO 動態集記錄集實現。
BOOL CVirtualListDoc::OnOpenDocument(LPCTSTR lpszPathname) {
m_strFile = lpszPathname;
m_pDB = new CDaoDatabase; m_pDB->Open(m_strFile);
m_pRS = new CDaoRecordset(m_pDB);
m_pRS->Open(dbOpenDynaset, _T("SELECT * FROM TableName ORDER BY Col"));
m_pRS->MoveLast(); // 強制填充記錄集以取得正確 RecordCount
return TRUE;
}
關鍵:必須使用 dbOpenDynaset 才支援 SetAbsolutePosition 隨機存取。
BOOL CVirtualListView::PreCreateWindow(CREATESTRUCT& cs) {
cs.lpszClass = WC_LISTVIEW;
cs.style &= ~LVS_TYPEMASK;
cs.style |= LVS_REPORT | LVS_EDITLABELS | LVS_OWNERDATA; // 關鍵風格
return CListView::PreCreateWindow(cs);
}
void CVirtualListView::OnInitialUpdate() {
CListView::OnInitialUpdate();
CListCtrl& list = GetListCtrl();
// 擴充風格:全列選取、格線、標題拖曳追蹤
DWORD ex = LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_HEADERDRAGDROP | LVS_EX_TRACKSELECT;
list.SendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, 0, ex);
// 動態建立欄位
LV_COLUMN lvc = { LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT, 120 };
for (int i = 0; i < m_pRS->GetFieldCount(); ++i) {
CDaoFieldInfo fi; m_pRS->GetFieldInfo(i, fi);
lvc.pszText = (LPTSTR)(LPCTSTR)fi.m_strName;
list.InsertColumn(i, &lvc);
}
// 設定虛擬項目總數
m_nCount = m_pRS->GetRecordCount();
list.SendMessage(LVM_SETITEMCOUNT, m_nCount, LVSICF_NOINVALIDATEALL);
}
void CVirtualListView::OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult) {
LV_DISPINFO* pDisp = (LV_DISPINFO*)pNMHDR;
if (!(pDisp->item.mask & LVIF_TEXT)) return;
long idx = pDisp->item.iItem; // 列索引
long sub = pDisp->item.iSubItem; // 欄索引
try {
m_pRS->SetAbsolutePosition(idx); // 移動至指定記錄
COleVariant var;
m_pRS->GetFieldValue(sub == 0 ? 0 : sub, var); // 取得欄位值
CString str = FormatVariant(var); // 依型別格式化
lstrcpyn(pDisp->item.pszText, str, pDisp->item.cchTextMax);
}
catch (CDaoException*) { /* 忽略錯誤 */ }
if (pDisp->item.mask & LVIF_IMAGE) pDisp->item.iImage = 0;
*pResult = 0;
}
CString FormatVariant(const COleVariant& var) {
switch (var.vt) {
case VT_I2: return CString().Format(_T("%d"), var.iVal);
case VT_I4: return CString().Format(_T("%d"), var.lVal);
case VT_R4: return CString().Format(_T("%.2f"), var.fltVal);
case VT_R8: return CString().Format(_T("%.2f"), var.dblVal);
case VT_CY: { COleCurrency c(var); return c.Format(); }
case VT_DATE: { COleDateTime t(var.date); return t.Format(_T("%Y-%m-%d")); }
case VT_BSTR: return CString(var.bstrVal);
case VT_BOOL: return var.boolVal ? _T("TRUE") : _T("FALSE");
default: return _T("");
}
}
LVM_SETITEMCOUNT 延遲載入。LVSICF_NOINVALIDATEALL 避免設定項目數時全域失效重繪。虛擬清單模式將資料存儲與顯示解耦,配合動態集隨機存取特性,能以極低記憶體佔用流暢顯示百萬級記錄,是 MFC 處理大量表格資料的標準解法。