当我们开发的应用程序中经常用到MFC的列表控件来显示数据,但当数据记录超过上千条甚至上万条时,用CListCtrl的一般模式显示效率就有问题:内存占用大,还有刷新时会比较卡。其实CListCtrl还有一个强大的功能--虚拟列表模式,这种模式是微软专门为显示大数据量的情况而设计的,比如是数据库的列表控件要显示成千上万条记录,显示效率非常高。工作在这种模式下,数据只在需要显示的时候才被回调,刷新列表只刷新当前页的可见项。默认方式创建的CListCtrl并没有启用虚拟列表控件功能,需要做一些参数设置,虚拟列表控件的使用也很简单,下面就讲一下使用步骤。
1. 在建立的工程的对话框里拖入一个ListCtrl控件,然后将控件的属性View改成“Report”,“所有者数据”改成True。如下图所示:
2. 在我们的程序中需要在ListCtrl的父窗口的类里面响应CListCtrl的跟虚拟控件相关的几个消息事件,在父窗口的消息宏里面加入下面几个宏:
ON_NOTIFY(LVN_ODCACHEHINT,IDC_LIST1, OnOwnerDataHint)
ON_NOTIFY(LVN_GETDISPINFO, IDC_LIST1, OnGetDispInfo)
ON_NOTIFY(NM_CUSTOMDRAW, IDC_LIST1, OnCustomDraw)
关于这几个消息的说明:
LVN_ODCACHEHINT:该消息在拖动ListCtrl滚动条或翻页的时候会触发,消息的参数携带了当前页的记录范围(当前页记录是从第i到第j个,其中i,j是记录的行号),用户可以在这个消息函数里完成一些比较耗时的加载数据操作,对未加载的数据(翻页时新显示的Line项)读出来放到内存进行缓冲,以便到后面要回调的时候就可以快速读内存。
LVN_GETDISPINFO:该消息的响应函数用于对每行记录的内容进行赋值,消息带的参数为一个LV_ITEM结构体:
typedef struct tagLVITEMA
{
UINT mask;
int iItem;
int iSubItem;
UINT state;
UINT stateMask;
LPSTR pszText;
int cchTextMax;
int iImage;
LPARAM lParam;
#if (_WIN32_IE >= 0x0300)
int iIndent;
#endif
#if (_WIN32_WINNT >= 0x0501)
int iGroupId;
UINT cColumns; // tile view columns
PUINT puColumns;
#endif
#if _WIN32_WINNT >= 0x0600 // Will be unused downlevel, but sizeof(LVITEMA) must be equal to sizeof(LVITEMW)
int* piColFmt;
int iGroup; // readonly. only valid for owner data.
#endif
} LVITEMA, *LPLVITEMA;
上面这个结构体里面的一些成员变量意义,iItem:Line项行号;iSubItem:列号;pszTest:该列的字符串内容;iImage:图标索引;maskd:跟项相关的风格属性。
NM_CUSTOMDRAW:该消息处理函数用于定义每行Line项的文字颜色和背景的颜色属性。
3. 实现消息处理函数,下面代码是我的例程中的实现方式:
//函数作用:显示List项的内容前会触发该消息,缓冲还没显示到List控件中的项的数据。如果加载数据的操作比较耗时,那么可以在用户翻页的时候才加载数据到内存。
void CVirtualListThumbnailDlg::OnOwnerDataHint(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMLVCACHEHINT lpCacheHint = (LPNMLVCACHEHINT)pNMHDR;
TRACE("OnOwnerDataHint From:%-4d To:%-4d\n",lpCacheHint->iFrom,lpCacheHint->iTo);
//lpCacheHint->iFrom,lpCacheHint->iTo是当前新插入的List项的起始和末尾行号,表示每次翻页或拖动滚动条时新显示的一组Line项。
//并不是当前页的第一个和当前页的最后一个记录的行号(下面的nStartIndex和nEndIndex才表示当前页的两个起始和结束行号)
TRACE("Cache items from %d to %d \n", lpCacheHint->iFrom, lpCacheHint->iTo);
int nLow, nHigh, nStartIndex, nEndIndex;
int n = m_vThumb.size();
//nStartIndex, nEndIndex表示当前页的可见的Line项的范围
nStartIndex = m_wndList1.GetTopIndex();
nEndIndex = nStartIndex + m_wndList1.GetCountPerPage();
if(lpCacheHint->iFrom < nStartIndex)
nStartIndex = lpCacheHint->iFrom;
if(lpCacheHint->iTo > nEndIndex)
nEndIndex = lpCacheHint->iTo;
if(nEndIndex >= n)
nEndIndex = n - 1;
TRACE("Current Page Start: %d, End: %d \n", nStartIndex, nEndIndex);
nLow = nHigh = -1;
if(m_vCacheItems.size() > 0)
{
nLow = m_vCacheItems[0]->nSeqNo;
nHigh = m_vCacheItems[m_vCacheItems.size() - 1]->nSeqNo;
if(nLow <= nStartIndex && nEndIndex <= nHigh)
{
return;
}
if(nStartIndex >= 0 && nEndIndex < n)
{
m_vCacheItems.clear();
for(int i=nStartIndex; i<=nEndIndex; i++)
{
m_vCacheItems.push_back(m_vThumb[i]);
}
}
else
{
ASSERT(0);
}
}
else
{
for(int i=nStartIndex; i<=nEndIndex; i++)
{
m_vCacheItems.push_back(m_vThumb[i]);
}
}
*pResult = 0;
}
//函数作用: 在显示一行数据时回调,定义一行Line项的各列的字符串内容,以及第一列前面的图标的ImageIndex
void CVirtualListThumbnailDlg::OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
int nItem = pDispInfo->item.iItem;
QListItemData* pNode = GetCachedItem(nItem);
if(pNode == NULL)
{
TRACE("Warning: OnGetDispInfo Item: %d requests to get source item data, it is ok but slow! \n", nItem);
pNode = GetSourceItem(nItem);
if(pNode == NULL)
{
*pResult = 0;
TRACE("OnGetDispInfo Item: %d get item error! \n", nItem);
return;
}
}
//Create a pointer to the item
LV_ITEM* pItem= &(pDispInfo)->item;
//Do the list need text information?
if (pItem->mask & LVIF_TEXT)
{
CString text;
if(pItem->iSubItem == 0)
{ //filename
text = pNode->filename;
}
else if (pItem->iSubItem == 1)
{ // filesize
text = FormatFileSize(pNode->size);
}
else if (pItem->iSubItem == 2)
{ //type
text = pNode->filename.Right(3);
}
else if (pItem->iSubItem == 3)
{ // write time
CTime time(pNode->time);
// Format the date time string.
text.Format( _T("%02d-%02d-%02d %02d:%02d"),
time.GetYear() % 100, time.GetMonth(), time.GetDay(),time.GetHour(), time.GetMinute());
}
//Copy the text to the LV_ITEM structure
//Maximum number of characters is in pItem->cchTextMax
lstrcpyn(pItem->pszText, text, pItem->cchTextMax);
}
//Do the list need image information?
if( pItem->mask & LVIF_IMAGE)
{
//Set which image to use
if(pNode->ImageIndex==-1)
{
// pNode->ImageIndex = GetIconIndex(m_strCurrentDir + pNode->filename,pNode->bIsDir);
pNode->ImageIndex = ExtToIcon(pNode->fileExt);
}
pItem->iImage = pNode->ImageIndex;
}
if(pItem->mask & LVIF_PARAM)
{
TRACE("mask has LVIF_PARAM bit set \n");
}
*pResult = 0;
}
//函数作用:对Line项的背景颜色,文字颜色设置,可以实现自绘的效果
void CVirtualListThumbnailDlg::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
long lStyle = GetWindowLong(m_hWnd, GWL_STYLE) & LVS_TYPEMASK;
NMLVCUSTOMDRAW * lplvcd = (NMLVCUSTOMDRAW*)pNMHDR;
// By default set the return value to do the default behavior.
*pResult = 0;
switch( lplvcd->nmcd.dwDrawStage )
{
case CDDS_PREPAINT:
// First stage (for the whole control)
// Tell the control we want to receive drawing messages for drawing items.
*pResult = CDRF_NOTIFYITEMDRAW;
//*pResult = CDRF_DODEFAULT;
break;
case CDDS_ITEMPREPAINT:
{
DWORD nItem = lplvcd->nmcd.dwItemSpec;
if(nItem < 0)
break;
QListItemData* pNode = GetCachedItem(nItem);
if(pNode == NULL)
break;
//lplvcd->clrTextBk = ::ExtToColor(pNode->fileExt, FileColors, sizeof(FileColors) / sizeof(FileColors[0]));
//指定文字的背景色和前景色
lplvcd->clrTextBk = pNode->crBack;
lplvcd->clrText = pNode->crText;
*pResult = CDRF_NEWFONT| CDRF_NOTIFYPOSTPAINT;
}
break;
case CDDS_ITEMPOSTPAINT:
{
int nItem = lplvcd->nmcd.dwItemSpec;
if(nItem < 0)
break;
QListItemData* pNode = GetCachedItem(nItem);
if(pNode == NULL)
break;
CDC* pDC = CDC::FromHandle(lplvcd->nmcd.hdc);
//For listview style: report/icon/smallicon/list
{
CRect rcItem;
m_wndList1.GetItemRect( nItem, &rcItem, LVIR_ICON);
CRect rcIcon(rcItem);
int nIcon = pNode->ImageIndex;
}
*pResult = CDRF_DODEFAULT;
}
break;
default:
*pResult = CDRF_DODEFAULT;
}
}
我实现了一个完整的例子,界面截图如下:
这个例子显示一个目录里所有文件内容,如果文件是图片,则每一个记录的左边会有图标。
下载地址:
http://download.csdn.net/download/toshiba689/10261719