一、什么是虚拟列表控件
虚拟列表控件是具有LVS_OWNERDATA样式的列表视图控件。此样式使控件能够支持最多可达DWORD(默认项计数仅扩展到INT)个数据的显示,这种样式提供的最大优势是在任何时候都只需在内存中拥有一个数据项子集。
MFC中支持虚拟列表的有CListCtrl与CListView。
二、为什么使用虚拟列表控件
我们知道,通常使用列表控件CListCtrl,需要调用InsertItem把要显示的数据插入列表中,之后我们就不必关心数据在哪里了,这是因为控件自己开辟了内存空间来保存这些数据。现在假设我们要显示一个数据库,里面的信息量很大,有几十万条记录。通常有两种方法解决这个问题:1是仅仅在ListCtrl中插入少量的数据,比如100个,然后通过[上一页][下一页]两个按钮进行控制,某一时刻显示的只是从xxx到xxx+100之间的记录。2是把所有数据全部插入到ListCtrl中,然后让用户通过滚动来查看数据。无疑,很多用户喜欢采用第二种方式,特别是对于已经排序的数据,用户只需用键盘输入某行的开头字符,就可以快速定位到某一行。但是,如果这样做,InsertItem插入数据的过程将是很漫长的,而且用户会看到ListCtrl刷新速度也很慢,而且所有数据都位于内存中消耗了大量的内存,当数据多达上万以后几乎是不能忍受的。
为此,mfc特别提供了虚拟列表的支持。一个虚拟列表看起来和普通的ListCtrl一样,但是不用通过InsertItem来插入数据,它仅仅知道自己应该显示多少数据。但是它如何知道要显示什么数据呢?秘密就在于当列表控件需要显示某个数据的时候,它向父窗口要。假设这个列表控件包含100个元素,第10到20个元素(行)是可见的。当列表控件重画的时候 ,它首先请求父窗口给它第10个元素的数据,父窗口收到请求以后,把数据信息填充到列表提供的一个结构中,列表就可以用来显示了,显示第10个数据后,列表会继续请求下一个数据。
在虚拟的样式下,ListCtrl可以支持多达DWORD个数据项。(缺省的listctrl控件最多支持int个数据项)。但是,虚拟列表的最大优点不在于此,而是它仅仅需要在内存中保持极少量的数据,从而加快了显示的速度。所以,在使用列表控件显示一个很大的数据库的情况下,采用虚拟列表最好不过了。
三、虚拟列表的实现
建立一个CListCtrl控件,修改其属性OwnerData(中文名为:所有者数据)为True;
为其设置要显示的行数:m_list.SetItemCount(100);然后为其响应LVN_GETDISPINFO消息
添加后得到:
之后要在这个消息里把每次要显示的数据交给CListCtrl,然后在消息响应函数中添加代码将数据传给控件
void CDisplayIniTreeDlg::OnLvnGetdispinfoList1(NMHDR *pNMHDR, LRESULT *pResult)
{
NMLVDISPINFO *pDispInfo = reinterpret_cast(pNMHDR);
// TODO: 在此添加控件通知处理程序代码
LV_ITEM* item = &(pDispInfo->item);
int iItemIndex = item->iItem;
if (item->mask & LVIF_TEXT)
{
int nSubItem = item->iSubItem;
_tcscpy(item->pszText, m_strVec.GetString(iItemIndex, nSubItem));
}
}
最后,在数据存储好之后调用:
m_list.SetItemCountEx(m_sigInfoContain.GetItemCount(), LVSICF_NOINVALIDATEALL | LVSICF_NOSCROLL);
m_list.Invalidate();
虚拟列表就实现完成。
其中SetItemCountEx用于设置虚拟列表视图控件的项目计数。
复制
BOOL SetItemCountEx(
int iCount,
DWORD dwFlags = LVSICF_NOINVALIDATEALL
);
参数:
iCount
控件最终包含的项目数。
dwFlags
指定重置项目计数后列表视图控件的行为。此值可以是以下组合:
LVSICF_NOINVALIDATEALL 除非受影响的项目当前在视图中,否则列表视图控件不会重新绘制。这是默认值。
LVSICF_NOSCROLL 列表视图控件在项目计数更改时不会更改滚动位置
四、虚拟列表可能用到的三个消息
当虚拟列表控件需要某个数据的时候,它给父窗口发送一个 LVN_GETDISPINFO通知消息,表示请求某个数据。因此列表的所有者窗口(或者它自己)必须处理这个消息。例如派生类的情况 (CMyListCtrl是一个虚拟列表类对象):
//这里处理的是反射消息
BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
//{{AFX_MSG_MAP(CMyListCtrl)
ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetdispinfo)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
在LVN_GETDISPINFO的处理函数中,必须首先检查列表请求的是什么数据,可能的值包括:
(1)LVIF_TEXT 必须填充 pszText
(2)LVIF_IMAGE 必须填充 iImage
(3)LVIF_INDENT 必须填充 iIndent
(4)LVIF_PARAM 必须填充 lParam
(5)LVIF_STATE 必须填充 state
根据它的请求,填充所需的数据即可。
2、处理 LVN_ODFINDITEM 消息查找某个数据
在资源管理器里面,定位到某个文件夹,会显示很多文件,如果按下键盘的‘A’,则资源管理器会自动找到名字以 'A'打头的文件夹或者文件, 并选择该文件。继续按 A,如果还有其它名字以'A'打头的文件,则下一个文件被选中。如果输入 "AB",则 'AB'打头的文件被选中。这就是列表控件的自动查找功能。
当虚拟列表收到一个LVM_FINDITEM消息,它也会发送这个消息通知父窗口查找目标元素。要搜索的信息通过 LVFINDINFO 结构给出。它是 NMLVFINDITEM 结构的一个成员。当找到要搜索的数据后,应该把该数据的索引(行号)返回,如果没有找到,则返回-1。
3、处理 LVN_ODCACHEHINT 消息缓冲某一部分数据
假如我们从数据库或者其它地方读取数据的速度比较慢,则可以利用这个消息,批量读取一些数据,然后根据请求,逐个提供给虚拟列表。LVN_ODCACHEHINT消息的用途就是给程序一个缓冲数据的机会。以提高程序的性能。
我们必须响应的消息是(1),多数情况下要响应(2),极少数的情况下需要响应(3)
五、注意
因为虚拟列表需要将ownerdata属性设置为true,所以无法再使用SetItemData(),也无法进行排序。