在windows程序开发中,如果使用了List显示数据,但是目标数据量很大,List性能就会变得很慢,刷新一次需要几秒甚至几十秒的时间,若解决此问题,可使用VirtualList技术 - 即带LVS_OWNERDATA样式的List control。
创建时指定LVS_OWNERDATA样式,即在调用CreateWindow或CreateWindowEx时指定。
注:1)不支持动态切换到LVS_OWNERDATA样式,因此通过SetWindowLong等设置该样式将是失败的;
2)本样式不可与LVS_SORTASCENDING或LVS_SORTDESCENDING风格组合,所有的virtual list使用默认的LVS_AUTOARRANGE样式。
1)四种形式的List均支持LVS_OWNERDATA,使用本样式的list不存储条目信息。因此可以应用的有效的状态信息只有LVIS_SELECTED和LVIS_FOCUSED。Virtual list 也不会为每一个条目保持状态和覆盖的图像,然而virtual list可以向父窗口发送一个LVM_SETCALLBACKMASK消息来查询这些图像。
2)受影响消息:
Message |
限制描述 |
LVM_ARRANGE |
不支持LVA_SNAPTOGRID样式 |
LVM_DELETEALLITEMS |
设置条目个数为0并清除所有内部选集变量,但是并不是实际地删除所有条目,它将向父窗口发送一个回调的通知消息 |
LVM_DELETEITEM |
仅全选时支持且不实际删除该条目 |
LVM_GETITEMSTATE |
返回焦点和选择状态,即virtual list存储的状态 |
LVM_GETNEXTITEM |
不支持LVNI_CUT,LVNI_HIDDEN,LVNI_DROPHILITED。 |
LVM_GETWORKAREAS |
不支持 |
LVM_INSERTITEM |
仅全选时支持 |
LVM_SETITEM |
不支持 |
LVM_SETITEMCOUNT |
设置当前list的item个数。如果list发送了一个请求所有数据的通知,拥有者必须准备好这些数据 |
LVM_SETITEMPOSTION |
不支持 |
LVM_SETITEMSTATE |
仅支持选择和焦点状态 |
LVM_SETITEMTEXT |
不支持,由拥有者保持 |
LVM_SETWORKAREAS |
不支持 |
LVM_SORTITEMS |
不支持。由拥有者负责以期望的顺序显示item |
1)添加:因为Virtual List不拥有数据,数据存在外部(如:数据库),所以你只需将数据添加到外部,然后m_list.SetItemCount(list_size+1)告诉Virtual List行数加一。这样Virtual List会更改滚动条的样子,以便看上去添加了一行。
2)删除:先在外部数据中删除行,然后m_list.SetItemCount(list_size-1)更改滚动条。
3)修改:直接修改在外部数据中的行。
如果这些行是正在显示的行,则需要调用m_list.RedrawItems(nFirst,nLast)。
当Virtual List需要显示一行数据时,它发送这个消息给父窗口询问数据内容。父窗口收到消息后从传过来参数中知道Virtual List现在显示的是第几项、第几列,内容是什么类型。然后父窗口在外部数据中找到数据内容,将它返回给Virtual List。
先添加消息映射,这里用的是反射消息,可以减少控件于父窗口之间的耦合度。
BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetDispInfo)
END_MESSAGE_MAP()
这个消息用来缓存请求项的数据。
Virtual list 会产生大量的LVN_GETDISPINFO消息,LVN_ODCACHEHINT消息用于帮助提高性能。
该消息指定一个区间,表示该区间内(含边界)的item是最可能需要的,拥有者将该区间内的这些条目加载到缓存中
List经常需要第一条item的信息(index等于0),但是LVN_ODCACHEHINT通知消息不会总是包含item0,但是item0一定要总是在缓存中;
最后一条item也总是被处理,因此拥有者可能需要保持第二个缓存用于包含list最后面的一段。这样LVN_ODCACHEHINT可以对最后一段的缓存请求直接处理,而不需要重新加载。
在资源管理器中浏览文件时,让焦点在list上,然后在键盘上输入文件名,资源管理器会自动找到并选中与之最相近的文件。LVN_ODFINDITEM就是实现这个功能的,当焦点在list上时,按键操作会使VirtualList发送LVN_ODFINDITEM给父窗口。父窗口找到相应项并将它选中。
BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
ON_NOTIFY_REFLECT(LVN_ODFINDITEM, OnOdFindItem)
END_MESSAGE_MAP()