如何基于MFC的CListCtrl实现虚拟列表控件

    当我们开发的应用程序中经常用到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;
	}
}

我实现了一个完整的例子,界面截图如下:

如何基于MFC的CListCtrl实现虚拟列表控件_第1张图片

这个例子显示一个目录里所有文件内容,如果文件是图片,则每一个记录的左边会有图标。

下载地址:

http://download.csdn.net/download/toshiba689/10261719



你可能感兴趣的:(windows开发)