在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处理程序中处理。
需要使用以下方式处理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函数的实现如下:
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;
}