优化MFC列表控件的数据存储和显示

MFC应用程序开发中,列表控件(CListCtrl)是展示数据的一种常见方式。然而,如果列表中的数据量很大,直接存储数据字符串会增加应用程序的内存占用,并可能影响列表的填充和删除操作的速度。为了解决这个问题,可以采用文本回调机制,让列表控件在需要显示数据时动态请求字符串,而不是将字符串显式存储在列表中。这种方法虽然会略微增加显示时间,但可以显著减少内存占用,并加快列表内容的填充和删除速度。

数据结构定义

假设数据存储在应用程序中的结构体“ItemStruct”中,如下所示:

typedef struct { int nItemNo; CString strName; } ItemStruct;

每个结构体存储在数组、列表或映射中。要让列表控件知道每个项目,最简单的方法是将每个项目的指针存储在传递给CListCtrl::InsertItem函数的LV_ITEM结构的lParam字段中。

添加新项目到列表

当向列表添加新项目时,应设置LV_ITEM结构的掩码字段中的LVIF_PARAM位,这表明lParam字段包含数据。可以创建一个辅助函数来添加新项目到列表:

BOOL CMyListCtrl::AddItem(ItemStruct* pItem, int nPos = -1) { int nNumItems = GetItemCount(); int nInsertPos = (nPos >= 0 && nPos <= nNumItems) ? nPos : nNumItems; LV_ITEM Item; Item.lParam = (LPARAM) pItem; // 存储数据指针 Item.pszText = LPSTR_TEXTCALLBACK; // 使用回调获取文本 Item.mask = LVIF_TEXT | LVIF_PARAM; // lParam和pszText字段激活 Item.iItem = nInsertPos; // 新项目插入位置 Item.iSubItem = 0; if (InsertItem(&Item) < 0) return FALSE; else return TRUE; }

LPSTR_TEXTCALLBACK值告诉列表控件使用回调获取要显示的文本,而不是存储文本本身。每次列表需要显示文本时,它都会发送LVN_GETDISPINFO通知。不为子项添加文本,因为它们也将在LVN_GETDISPINFO处理程序中处理。

处理LVN_GETDISPINFO通知

需要使用以下方式处理Windows通知LVN_GETDISPINFO:

BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl) ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetDispInfo) END_MESSAGE_MAP()

并在类定义中声明相应的函数:

class CMyListCtrl { // ... 其他声明 ... protected: afx_msg void OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult); };

处理函数可能如下所示:

void CMyListCtrl::OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult) { LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR; if (pDispInfo->item.mask & LVIF_TEXT) { ItemStruct* pAppItem = (ItemStruct*) pDispInfo->item.lParam; CString strField = QueryItemText(pDispInfo->item.iItem, pDispInfo->item.iSubItem); LPTSTR pstrBuffer = AddPool(&strField); pDispInfo->item.pszText = pstrBuffer; } *pResult = 0; }

NMHDR变量包含列表视图显示信息,其中包含LV_ITEM成员,指定它正在查找的项目和子项目,以及它正在查找的信息类型。在案例中,只指定文本,所以只处理pDispInfo->item.mask等于LVIF_TEXT的通知。通过LV_ITEM的lParam字段获取应用程序数据的指针,并从中获取数据的文本表示形式(这里使用了另一个辅助函数"QueryItemText")。

QueryItemText函数实现

QueryItemText函数的实现如下:

CString CMyListCtrl::QueryItemText(int nRow, int nCol) { CString strField; ItemStruct* pAppItem = (ItemStruct*) GetItemData(nRow); if (!pAppItem) return strField; switch (nCol) { case 0: strField.Format("%d", pAppItem->nItemNo); break; case 1: strField = pAppItem->strName; break; default: ASSERT(FALSE); } return strField; }

使用QueryItemText函数的主要原因是,由于现在使用文本回调,像CListCtrl::GetItemText这样的函数不再工作。如果在TitleTips或InPlaceEdit中使用GetItemText,则必须用QueryItemText替换它们才能正常工作。

缓存机制实现

OnGetDispInfo函数中的以下两行代码:

LPTSTR pstrBuffer = AddPool(&strField); pDispInfo->item.pszText = pstrBuffer;

来自Mike Blaszczak的程序"ApiBrow"(可以在线获取或从他的书"ProfessionalMFCwith VisualC++5"中获取)。它处理了列表控件要求从OnGetDispInfo返回的缓冲区在接下来的两个LVN_GETDISPINFO通知中仍然有效的奇怪情况。他的解决方案是一个简单的缓存,存储足够的副本,以便列表控件不会出错。实现如下:

LPTSTR CMyListCtrl::AddPool(CString* pstr) { LPTSTR pstrRetVal; int nOldest = m_nNextFree; m_strCPool[m_nNextFree] = *pstr; pstrRetVal = m_strCPool[m_nNextFree].LockBuffer(); m_pstrPool[m_nNextFree++] = pstrRetVal; m_strCPool[nOldest].ReleaseBuffer(); if (m_nNextFree == 3) m_nNextFree = 0; return pstrRetVal; }

需要在类定义中声明受保护的属性:

CString m_strCPool[3]; LPTSTR m_pstrPool[3]; int m_nNextFree;

这就是全部内容!

typedef struct { int nColumn; BOOL bAscending; } SORTINFO; BOOL CMasterListCtrl::SortTextItems(int nCol, BOOL bAscending) { CWaitCursor waiter; SORTINFO si; si.nColumn = m_nSortColumn; si.bAscending = m_bSortAscending; return SortItems(CompareFunc, (LPARAM) &si); } int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { ItemStruct *pItem1 = (ItemStruct*) lParam1; ItemStruct *pItem2 = (ItemStruct*) lParam2; SORTINFO* pSortInfo = (SORTINFO*) lParamSort; ASSERT(pItem1 && pItem2 && pSortInfo); int result; switch (pSortInfo->nColumn) { case 0: if (pItem1->nItemNo < pItem2->nItemNo) result = -1; else if (pItem1->nItemNo == pItem2->nItemNo) result = 0; else result = 1; break; case 1: result = pItem1->strName.Compare(pItem2->strName); break; default: ASSERT(FALSE); } if (!pSortInfo->bAscending) result = -result; return result; }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485