Virtual List(虚拟列表)是LVS_OWNERDATA 样式的List Ctrl.默认的List Ctrl在插入大量的数据时会变得很慢.在我的破机器上插入不到一万行的数据要几十秒,非常令人不爽.而用Virtual List可以大大加快速度。Virtual List不拥有数据,当需要显示一行时才发消息向父窗口查询显示内容。Virtual List的使用方法与普通List Ctrl稍微有点不同。它有三个重要的消息LVN_GETDISPINFO,LVN_ODCACHEHINT和 LVN_ODFINDITEM。响应这三个消息是关键。
1.创建Virtual List
只需添加LVS_OWNERDATA风格到List Ctrl就行了。
m_list.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_OWNERDATA);
2.添加,删除和更改一行
添加:因为Virtual List不拥有数据,数据存在外部(如:数据库),所以你只需将数据添加到外部,然后m_list.SetItemCount(list_size+1) 告诉Virtual List行数加一。这样Virtual List会更改滚动条的样子,以便看上去添加了一行.
删除:先在外部数据中删除行,然后m_list.SetItemCount(list_size-1)更改滚动条.
更改:直接修改在外部数据中的行.
如果这些行是正在显示的行,则需要调用m_list.RedrawItems(nFirst,nLast)
3.响应LVN_GETDISPINFO消息
当Virtual List需要显示一行数据时,它发送这个消息给父窗口询问数据内容.父窗口收到消息后从传过来参数中知道Virtual List现在显示的是第几项,第几列,内容是什么类型.然后父窗口在外部数据中找到数据内容,将它返回给Virtual List.
先添加消息映射,这里用的是反射消息,可以减少控件于父窗口之间的耦合度
BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetdispinfo)
END_MESSAGE_MAP()
相应的响应函数
void CMyListCtrl::OnGetdispinfo(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
LV_ITEM* pItem= &(pDispInfo)->item;
int iItemIndx= pItem->iItem;
if (pItem->mask & LVIF_TEXT)
{
switch(pItem->iSubItem)
{
case 0:
{
memcpy(pItem->pszText,(find_index + iItemIndx)->code,8);
*(pItem->pszText+8) = 0;
}
break;
case 1:
{
memcpy(pItem->pszText,(find_index + iItemIndx)-
>description,INDEX_DESLEN);
*(pItem->pszText+INDEX_DESLEN) = 0;
}
break;
}
}
*pResult = 0;
}
4.响应LVN_ODCACHEHINT消息
这个消息用来缓存请求项的数据.我没用到这个消息,因为我已一次将所有数据读入内存.
5.响应LVN_ODFINDITEM消息
在资源管理器中浏览文件时,让焦点list上,然后在键盘上输入文件名.资源管理器会自动找到并选中与之最相近的文件.LVN_ODFINDITEM就是 实现这个功能的.当焦点在list上时,按键操作会使Virtual List发送LVN_ODFINDITEM给父窗口.父窗口找到相应项并将它选中.
BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
ON_NOTIFY_REFLECT(LVN_ODFINDITEM, OnOdfinditemList)
END_MESSAGE_MAP()
void CMyListCtrl::OnOdfinditemList(NMHDR* pNMHDR, LRESULT* pResult)
{
// pNMHDR has information about the item we should find
// In pResult we should save which item that should be selected
NMLVFINDITEM* pFindInfo = (NMLVFINDITEM*)pNMHDR;
*pResult = -1; // *pResult = -1 表明没有找到
if( (pFindInfo->lvfi.flags & LVFI_STRING) == 0 )
return;
int nlen = _tcslen(pFindInfo->lvfi.psz);
int startPos = pFindInfo->iStart;
//Is startPos outside the list (happens if last item is selected)
if(startPos >= m_list.GetItemCount())
startPos = 0;
int currentPos=startPos;
do
{
if(memcmp((find_index + startPos ),pFindInfo->lvfi.psz,nlen) == 0)
{
*pResult = currentPos;
break;
}
currentPos++;
if(currentPos >= m_list.GetItemCount())
currentPos = 0;
}while(currentPos != startPos);
}
使用Virtual List后的效果令我很满意,但我还是不明白普通list ctrl为什么插入速度这么慢?普通list ctrl的插入操作做了些什么呢?
你通过使用列表视图控件去维护和显示一系列关联图像的条目。已包含在windows XP 中的ComCtl32.dll第6版负责实现列表视图的各个特性。这篇文章谈论了这些特性,并且提供了些编程的总则。
列表视图的创建
为了创建列表视图控件,必须调用CreateWidow和CreateWindowEx函数并且在函数中指定WC_LISTVIEW窗口类。当通用控件的 DLL被载入的时候,这个窗口类就被注册了。可以通过调用InitCommonControl或者InitCommonControlEx函数来确保这个 DLL已经加载。因为列表视图控件可以显示图形和文字,作为创建过程的一部分,你必须加载图片并且插入文字条目。
列表视图控件的风格和视图
列表视图控件可以在四种不同的风格的视图下显示条目。控件窗口的风格指定了当前视图。附加的窗口风格指定了条目的对齐方式和具体的控制特性。接下来的表描述了这四种视图。
视图名称 | 描述 |
图像视图 | 用LVS_ICON窗口风格来指定。每个条目显示了一张实际大小的图像,和一个在图像下面的标签。用户可以拖拽条目到列表视图控件窗口的任意位置。 |
小图像视图 | 用LVS_SMALLICON窗口风格来指定。每个条目显示了一个小图像和在图像右边的标签。用户可以拖拽条目到列表视图控件窗口的任意位置。 |
列表视图 | 用LVS_LIST窗口风格来指定。每个条目显示了一个小图像和在图像右边的标签。所有条目在同一列。用户不可以通过拖拽来改变位置。 |
报表视图 | 用LVS_REPORT窗口风格来指定。每个条目显示在自己的一行中,显示的信息按列排列。最左边的列通常留下合适的长度来容纳小图标和标签。随后的列容纳应用程序指定的子条目。每个列都有头部,除非你指定了LVS_NOCOLUMNHEADER窗口风格。 |
在创建列表视图控件后,你依旧可以改变视图的风格。可以通过调用GetWindowLong获取窗口风格,SetWindowLong改变窗口风格。使用LVS_TYPEMASK这个参数可以确定当前视图的窗口风格。
通过指定LVS_ALIGNTOP(默认)或者LVS_ALIGNLEFT窗口风格,你可以控制条目的图标或者小图标的对齐方式。
在创建列表视图控件后,你依旧可以改变对齐方式。使用LVS_ALIGNMASK这个参数可以确定当前的对齐方式。
扩展的列表视图风格
扩展的列表视图控件风格提供了像检查框,平滑的滚动条,网格线,和热追踪。你不可以用像标准窗口风格那样的方法来获取扩展的列表视图风格。你不可以使用GetWindowLong和SetWindowLong函数来改变扩展窗口风格。
这 里有2个消息用来获取和设置扩展风格信息:LVM_SETEXTENDLISTVIEWSTYPE和 LVM_GETEXTENDEDLISTVIEWSTYLE。你还可以使用功能相似的宏来替代:ListView SetExtendedListViewStyle和ListView GetExtendedListViewStyle.
虚拟列表视图风格
虚拟列表视图是指定了LVS_OWNERDATA风格的列表视图控件。这种风格使控件能处理数百万的条目,因为控件拥有者承担了管理条目数据的重任。当指定了适当的数据存取方法,这种风格允许你使用虚拟的列表视图控制大量的信息数据库。
一个虚拟的列表视图控件自己只是维护了很少的条目信息。除了被选中的条目和获得焦点的信息,控件的拥有者必须管理其他信息。其他进程通过使用LVN_GETDISPINFO通知消息,来请求获取条目信息。
因为这种列表是为了大数据集合准备的,所以推荐你缓存被请求的条目数据来提高取还性能。列表视图提供了缓存提示的机制来优化高速缓存。这种提示通过LVN_ODCACHENINT的通知消息来执行。
创建一个虚拟的列表视图控件
你可以通过调用CreateWindow或者CreateWindowEx函数,指定LVS_OWNERDATA为函数参数dwStyle的一部分,来创建虚拟的列表视图控件。
你可以将LVS_OWNERDATA与大多数窗口类型合并,除了LVS_SORTASCENDING风格或者LVS_SORTDESCENDING风格。所有的虚拟列表视图控件默认的风格为LVS_AUTOARRANGE。
兼容性的问题
四种列表视图都支持LVS_OWNERDATA风格。有LVS_OWNERDATA风格的 列表视图控件不存储条目指定的信息。因此,唯一有效的条目状态标志是你可以申请条目是LVIS_SELECTED和LVIS_FOCUSED。其他状态的 信息是不存储的。特别是,列表视图控件不维护各个条目的状态和覆盖的图像。然而,你可以通过向应用程序发送LVM_SETCALLBACKMASK消息, 来查询这些图像。
当你使用LVS_OWNERDATA风格的时候,大多数列表视图控件的消息和关联的宏是支
持的,但是,有些消息有点限制或者不支持。下面的表总结了受影响的消息。
消息 | 限制 |
LVM_ARRANGE | 不支持LVA_SNAPTOGRID风格 |
LVM_DELETEALLITEMS | 条目数清零,清楚所有内置的选址变量,但是它不是真正的删除所有的条目。它发出了一个通知的回调。 |
LVM_DALATEITEM | 只有被完全选中的条目才有效,并且不是真正的删除。 |
LVM_GETITEMSTATE | 仅返回有焦点和被选择状态 |
LVM_GETNEXTITEM | 除了LVNI_CUT,LVNI_HIDDEN,LVNI_DROPHILITED,其他的搜索条件都支持。 |
LVM_GETWORKAREAS | 不支持 |
LVM_INSERTITEM | 仅支持完全选中的条目 |
LVM_SETITEM | 不支持,设置条目状态,使用ListView SetItemState消息 |
LVM_SETITEMCOUNT | 设置当前列表中条目的个数。如果列表视图控件发送了一个请求数据直到所有条目 都达到最大的容量的通知消息,拥有者必须准备好提供的数据。消息参数支持虚拟列表视图控件。 |
LVM_SETITEMPOSITION | 不支持 |
LVM_SETITEMSTATE | 仅支持改为选择和有焦点两种状态 |
LVM_SETITEMTEXT | 不支持, |
LVM_SETWORKAREAS | 不支持 |
LVM_SORTITEMS | 不支持 |
处理虚拟列表视图控件的通知消息
具有LVS_OWNERDATA风格的列表视图控件除了能发送一般的列表视图控件的通知消
息外,还能发生两个附加的通知消息:LVN_ODCACHINT和LVN_ODFINDITEM。下面的是具有LVS_OWNERDATA风格的列表视图控件发送得最多的通知消息。
LVN_GETDISPINFO | 一个虚拟的列表视图控件自己维护很少的条目信息。因此,它经常发送LVN_GETDISPINFO通知消息来请求获得条目信息。这个消息的处理 方式与标准的列表控件处理回调条目很相似。因为控件支持的条目数量可以很大,所以缓存条目数据可以挺高性能。当处理LVN_GETDISPINFO的时 候,控件的拥有者首先尝试从高速缓存中获取条目信息。如果请求的条目没有被缓存,那么拥有者将通过其他的方式来提供信息。 |
LVN_ODCACHEHINT | 虚拟的列表视图通过发送LVN_ODCACHEHINT来帮助优化高速缓存。这个通知消息提供了被推荐缓存条目范围的指定值。当拥有者接受到这 个通知消息的时候,就必须准备将请求范围内的条目信息加载到高速缓存中,确保当虚拟列表视图控件发送LVN_GETDISPINFO消息时,信息是有效 的。 |
LVN_ODFINDITEM | 当虚拟列表视图控件需要拥有者查找一个指定的回调条目时,控件会发送通知消息LVN_ODFINDITEM。当控件接受到快速关键字存取或者 LVN_FINDITEM消息时,就会发送这个通知消息。搜索消息的信息以LVFINDINFO结构体的形式发送,LVFINDITEM是结构体 NMLVFINDITEM的一个成员。拥有者必须准备搜索符合控件提供信息的条目。如果成功返回条目的索引,否则,返回-1。 |
高速缓存管理
具有LVS_OWNERDATA风格的列表视图控件会发送大量的 LVN_GETDISPINFO通知消息,为了优化高速缓存,也会发送大量的LVN_ODCACHEHINT消息。LVN_ODCACHEHINT消息提 供了被推荐加载到高速缓存中的条目的信息。这些消息被当做WM_NOTIFY消息发送,消息的参数lParam保存着结构体NMLVCACHEHINT的 地址。
结构体NMLVCACHEHINT包括2个整型的成员:iFrom和iTo。他们表示了很有可能被用到的条目的范围。拥有者必须准备将这个范围内的条目信息加载到高速缓存中。
列表控件常需要第一个条目(偏移为0)的信息。LVN_ODCACHEHINT通知消息不一定总是包括条目0,但是条目0一定在高速缓存中。
最后一条条目经常被存取。因此,拥有者也许会用第二个高速缓存来保存最后一个条目。通过用LVN_ODCACHEHINT中获取的范围与结束高速缓存(存放最后一条条目)比较来确定是否加载,这样避免了每次重复加载相同的范围。
列表视图图像列表
在默认情况下,列表视图控件不显示条目的图像。为了显示条目的图像,你必须很创建一个图像列表,并且把它与控件关联起来。一个列表视图控件可以有三个图像列表:
实际大小的图像列表和小图像列表可以包容覆盖图像(overlay image),它是设计用来透明的画在条目图像上的。
在空间上使用覆盖图像:
1、调用ImageList SetOverlayImage函数把覆盖图像的索引赋给完全大小图像列表和小图像列表关联起来。一张覆盖图像就是通过索引来辨别的。
2、 当你调用宏ListView InsertItem或者ListView SetItem时,你就可以把一张覆盖图像的索引与条目关联起来。使用宏INDEXTOOVERLAYMASK,可以在条目的结构体LVITEM的状态成 员指定覆盖图像索引。你也必须在成员stateMask中设置LVIS_OVERLAYMASK。
如果状态图像列表指定了,控件必须在每个条目的左边为状态图像预留足够大小的空间。
为了将状态图像与条目关联起来,可以使用宏 INDEXTOSTATEIMAGEMASK在结构体LVITEM的状态成员state中指定状态图像的索引。这个索引等同于图像在控件状态图像列表中的 索引。虽然图像列表索引基于0,控件使用基于1的索引来辨别状态图像。状态索引是0,表明条目没有状态图像。
默认情况下,当列表视图控件销毁的时 候,它同时也销毁与它关联的图像列表。然而,如果列表视图控件具有LVS_SHAREIMAGELISTS的窗口风格,那么,当列表不再被使用的时候,由 应用程序负责销毁。当你将同一个图像列表分配给多个列表视图控件时,你应该对控件指定这种窗口风格。否则,将会有多个控件试图去销毁同一个图像列表。
列表视图条目和子条目
在列表视图控件中的每个条目都有图像、标签、当前状态,和应用程序指定的值。通过控件的消息,你可以增加、修改、删除条目,也可以获得条目的信息。
每 个条目都有一个或多个子条目。在报表视图下,子条目是在列中显示的一个字符串,与条目的图像和标签分开。可以使用LVM_SETITEMTEXT和 LVM_SETITEM消息来指定子条目的文字。在控件中的所有条目都有相同数量的子条目。子条目的数量由控件的列数决定。当你向控件增加列时,你要指定 列关联的子条目的索引。
结构体LVITEM定义了控件的条目或子条目。结构体成员iItem是条目的索引,从0开始。在增加多个条目前,你需要向 控件发送LVM_SETITEMCOUNT消息,来指定控件最后会包含的条目的数量。这个消息一次性重新分配内部的数据结构,而不是增加一条条目分配一 次。通过消息LVM_GETITEMCOUNT,你可以确定控件的条目数量。如果你要增加大量数目的条目,在增加之前,你可以禁止控件的重绘功能来加速进 程。当增加条目完成后,再使能重绘功能。使用消息WM_SETREDRAW来禁用和使能重绘。
为了改变条目的属性,你可以使用消息LVM_SETITEM,消息中指定了结构体LVITEM的地址。结构体的成员mask中指定了你想改变的条目属性。
为了获取控件条目的属性,你可以使用消息LVM_GETITEM,消息中指定了你填充的结构体LVITEM的地址。结构体的成员mask指定了你想获取的条目的属性。如果只是为了获取条目或子条目的文本信息,你可以使用LVM_GETITEMTEXT消息。
使用LVM_DELETEITEM消息来删除控件条目。使用消息LVM_DELETEALLITEMS来删除控件中的所有条目。
列表视图条目状态
条目的状态是一个值,指定了条目的可用性,标示了用户的行为,或者反映了条目的状态。例如当用户选择条目的时候,列表视图控件会改变一些状态位。应用程序可能改变另外的状态位来禁止或隐藏条目,或者指定覆盖图像或状态图像。
条 目的状态由结构体LVITEM的成员变量state指定。当你想改变或指定一个条目的状态时,成员stateMask指定了哪个状态位你想改变。你可以使 用消息LVM_SETITEMSTATE来改变条目状态。你可以使用消息LVM_GETITEMSTATE或者消息LVM_GETITEM来获得条目的当 前状态。
为了设置条目的覆盖图像,结构体LVITEM的成员stateMask必须包括值LVIS_OVERLATMASK,并且成员state 必须包括覆盖图像的索引(从1开始),索引必须通过宏INDEXTOSTATEIMAGETMASK来左移12位。当索引为0时,则没有状态图像。