虚拟列表控件---加载大数据行
平常所用到的列ListView/ListCtrl控件,都是只有行至几百行数据,直至今日,在项目中遇到了上10W量级数据条,终于感觉到普通加载的艰辛,遂到网上乱找一通,发现大同小异,转载了这篇比较详细的,后面代码所用到的m_Items,为存放的列表的数据结构列表,
这篇文章虽详尽,改日做一个DEMO,
Demo
一、什么是虚拟列表控件
虚拟列表控件是指带有LVS_OWNERDATA风格的列表控件。。
二、为什么使用虚拟列表控件
我们知道,通常使用列表控件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提供虚拟列表的功能, MFC的CListView类也有同样的功能。
三、虚拟列表控件的消息
虚拟列表总共发送三个消息给父窗口:当它需要数据的时候,它发送LVN_GETDISPINFO消息。这是最重要的消息。当用户试图查找某个元素的时候,它发送LVN_ODFINDITEM消息;还有一个消息是LVN_ODCACHEHINT,用来缓冲数据,基本上很少用到这个消息。
虚拟列表控件使用起来非常简单。它总共只有三个相关的消息,如果你直接使用CListCtrl,应该在对话框中响应这三个消息。如果你使用CListCtrl派生类,可以在派生类中响应这三个消息的反射消息。这三个消息分别是:
(1)LVN_GETDISPINFO 控件请求某个数据
(2)LVN_ODFINDITEM 查找某个数据
(3)LVN_ODCACHEHINT 缓冲某一部分数据
我们必须响应的消息是(1),多数情况下要响应(2),极少数的情况下需要响应(3)
四、如何使用虚拟列表控件
1、首先要创建控件,创建一个虚拟列表和创建一个正常的 CListCtrl差不多。先在资源编辑器里面添加一个list control资源。然后选中"Owner data"属性,然后给它捆绑一个CListCtrl变量。添加列,添加imagelist等都和使用正常的listctrl一样。
2、给虚拟列表添加元素。假设 m_list 是这个列表的控制变量。通常的情况下这样添加数据:
m_list.InsertItem(0, _T("Hello world"));
但是对于虚拟列表,不能这么做。只需告诉列表有多少个数据:
//总共显示100行
m_list.SetItemCount(100);
3、处理它的通知消息。
五、如何响应虚拟列表的消息
1、处理 LVN_GETDISPINFO 通知消息
当虚拟列表控件需要某个数据的时候,它给父窗口发送一个 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
根据它的请求,填充所需的数据即可。
//================= 例子代码=====================================
下面的给出一个例子,填充的是列表所需的某个数据项的文字以及图像信息:
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: //填充数据项的名字
lstrcpy(pItem->pszText,m_Items[iItemIndx].m_strItemText);
break;
case 1: //填充子项1
lstrcpy(pItem->pszText,m_Items[iItemIndx].m_strSubItem1Text);
break;
case 2: //填充子项2
lstrcpy(pItem->pszText,m_Items[iItemIndx].m_strSubItem2Text);
break;
}
}
/*注意,多数情况下要使用lstrcpyn ,因为最多复制字符的个数由pItem->cchTextMax给出:
lstrcpyn(pItem->pszText, text, pItem->cchTextMax);
*/
if (pItem->mask & LVIF_IMAGE) //是否请求图像
pItem->iImage= m_Items[iItemIndx].m_iImageIndex;
甚至连某行数据是否选中(当有checkbox的情况下)的信息也需要由用户自己来维护,例如:
//是否显示该行的选择信息?
if(IsCheckBoxesVisible()) //自定义函数
{
pItem->mask |= LVIF_STATE;
pItem->stateMask = LVIS_STATEIMAGEMASK;
if(m_database[itemid].m_checked)
{
pItem->state = INDEXTOSTATEIMAGEMASK(2);
}
else
{
pItem->state = INDEXTOSTATEIMAGEMASK(1);
}
}
2、处理 LVN_ODFINDITEM 消息
在资源管理器里面,定位到某个文件夹,会显示很多文件,如果按下键盘的‘A’,则资源管理器会自动找到名字以 'A'打头的文件夹或者文件, 并选择该文件。继续按 A,如果还有其它名字以'A'打头的文件,则下一个文件被选中。如果输入 "AB",则 'AB'打头的文件被选中。这就是列表控件的自动查找功能。
当虚拟列表收到一个LVM_FINDITEM消息,它也会发送这个消息通知父窗口查找目标元素。要搜索的信息通过 LVFINDINFO 结构给出。它是 NMLVFINDITEM 结构的一个成员。当找到要搜索的数据后,应该把该数据的索引(行号)返回,如果没有找到,则返回-1。
以对话框为例,响应函数大致如下:
//================= 例子代码=====================================
void CVirtualListDlg::OnOdfinditemList(NMHDR* pNMHDR, LRESULT* pResult)
{
// pNMHDR 里面是要查找的元素的信息
// 要选中的目标元素的行号最后要保存在 pResult 中, 这是关键!
NMLVFINDITEM* pFindInfo = (NMLVFINDITEM*)pNMHDR;
/* pFindInfo->iStart 是查找的起始位置,一直到最后,然后从头开始,如果没有找到合适的,最终停留在iStart*/
*pResult = -1;
//是否按照文字查找?
if( (pFindInfo->lvfi.flags & LVFI_STRING) == 0 )
{
return;
}
//这是我们要找的字符串
CString searchstr = pFindInfo->lvfi.psz;
int startPos = pFindInfo->iStart;//保存起始位置
//判断是否最后一行
if(startPos >= m_list.GetItemCount())
startPos = 0;
int currentPos=startPos;
//开始查找
do
{
if( _tcsnicmp(m_database[currentPos].m_name,
searchstr, searchstr.GetLength()) == 0)
{
//选中这个元素,停止查找
*pResult = currentPos;
break;
}
currentPos++;
//从头开始
if(currentPos >= m_list.GetItemCount())
currentPos = 0;
}while(currentPos != startPos);
}
显然,如果数据很多,必须实现一个快速查找的方法。
关于pFindInfo->lvfi里面的信息的详细说明可以参考 MSDN。
3、处理 LVN_ODCACHEHINT 消息。
假如我们从数据库或者其它地方读取数据的速度比较慢,则可以利用这个消息,批量读取一些数据,然后根据请求,逐个提供给虚拟列表。LVN_ODCACHEHINT消息的用途就是给程序一个缓冲数据的机会。以提高程序的性能。
//================= 例子代码=====================================
使用 ClassWizard 重载 OnChildNotify 函数,检查是否 LVN_ODCACHEHINT 消息,然后准备缓冲数据:
NMLVCACHEHINT* pcachehint=NULL;
NMHDR* phdr = (NMHDR*)lParam;
if(phdr->code == LVN_ODCACHEHINT)
{
pcachehint= (NMLVCACHEHINT*) phdr;
//自定义函数,准备指定范围的数据到缓冲区
PrepCache(pcachehint->iFrom, pcachehint->iTo);
}
else ...
注意,如果消息不是 LVN_ODCACHEHINT,则要传递给基类进行处理。
五、如何修改ListCtrl显示的数据。
由于是程序自己维护数据,所以只需修改数据库中的数据,然后调用CListCtrl::RedrawItems函数进行重画即可。
六、数据的选择状态和选择框
CListCtrl可以显示checkbox选择框。有些情况下是很有用的。对于正常的listctrl,用户可以用鼠标来修改某个元素的选择状态,但是对于虚拟列表就不行了。必须自己处理一些消息,然后自己保存数据的选中状态:
void CVirtualListDlg::ToggleCheckBox(int item)
{
m_database[item].m_checked = !m_database[item].m_checked;
m_list.RedrawItems(item, item);
}
处理 LVN_KEYDOWN消息,添加对空格键 的响应,用于切换选择状态:
void CVirtualListDlg::OnKeydownList(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_KEYDOWN* pLVKeyDown = (LV_KEYDOWN*)pNMHDR;
if( pLVKeyDown->wVKey == VK_SPACE )
{
int item = m_list.GetSelectionMark();
if(item != -1)
ToggleCheckBox(item);
}
*pResult = 0;
}
然后处理 NM_CLICK 消息:
void CVirtualListDlg::OnClickList(NMHDR* pNMHDR, LRESULT* pResult)
{
NMLISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
LVHITTESTINFO hitinfo;
hitinfo.pt = pNMListView->ptAction;
int item = m_list.HitTest(&hitinfo);
if(item != -1)
{
//看看鼠标是否单击在 check box上面了?
if( (hitinfo.flags & LVHT_ONITEMSTATEICON) != 0)
{
ToggleCheckBox(item);
}
}
*pResult = 0;
}
七、备注:
1、虚拟列表无法进行排序。
2、虚表的一个优点是容易保持和数据库的同步。修改数据库中的数据,然后重画list十分容易而且高效。
3、虚表的另一个优点是可以根据需要产生数据。比如在某一列加上行号。
转载于:https://www.cnblogs.com/lzx_ok/archive/2010/09/16/1828335.html
虚拟列表控件---加载大数据行相关推荐
- paip.gui控件tabs控件加载内容的原理以及easyui最佳实现
paip.gui控件tabs控件加载内容的原理以及easyui最佳实现 //tabs控件的加载 同form窗体一样,俩个方式 两个方式:一个是url,简单的文本可以使用这个,不适合事件的情形.. 一个 ...
- 使用ECharts加载大数据量数据
使用ECharts加载大数据量数据 言归正传,本次研究目的是通过echarts加载大数据量数据,测试数据点为24w左右,最终调试结果,加载一条曲线在2.5s左右,同时加载两条曲线为5s以为,8条曲线为 ...
- Cesium加载大数据量地下管线
Cesium加载大数据量地下管线 Cesium优化加载 管线加载 管井.摄像头.交通信号灯等模型加载 与倾斜摄影模型结合加载 Cesium优化加载 管线.管井.路灯.交通信号灯.接电箱等模型使用Pri ...
- Android使用webview控件加载本地html,通过Js与后台Java实现数据的传递
1.在布局文件中加WebView控件,在java中获取WebView对象. 2.加载本地html文件. webView.loadUrl("file:///android_asset/andr ...
- webbrowser控件 加载为空白_OA系统公文控件升级操作说明
OA系统已于5月16日晚进行升级,其中包含公文预览.编辑.套红功能控件(金格控件)的大版本升级,升级后将提高系统对office版本的兼容性,并加快公文打开速度.升级的详细步骤,浏览器及office兼容 ...
- webbrowser控件 加载为空白_深入学习SAP UI5框架代码系列之一:UI5 Module的懒加载机制...
本文是深入学习SAP UI5框架代码系列的第二篇文章. 系列目录 SAP UI5应用开发人员了解UI5框架代码的意义 UI5 module懒加载机制 UI5 控件渲染机制 HTML原生事件 VS SA ...
- 使用DevExpress的PdfViewer控件加载http传输文件
使用DevExpress自带的PDFViewer控件直接打开本地文件很方便,但是现在想要加载远程服务器上的某个pdf文件,比如说http://192.168.1.1/test.pdf 还是使用PdfV ...
- Android Native APP开发笔记:使用WebView控件加载网页
文章目录 目的 基础使用 处理网页导航 加载本地网页 Web和Native之间交互 调试Web应用 处理页面重绘 总结 目的 WebView是一个比较常用的控件,功能上也比较单一,就是用来加载网页的, ...
- C#Winform中picturebox控件加载图片后无法释放
问题描述 最近测试程序功能时发现存在图片资源一直被占用无法释放的问题. 程序的功能大致为打开窗口时从服务器端临时下载图片到本地,然后在窗口中显示,窗口关闭时清除下载的文件.在最后关闭窗口时会报 ...
最新文章
- 技术人生:如何成为一位优秀的程序员
- ASP.Net TextBox控件只允许输入数字
- Win7 配置Android开发环境
- 对比云备份:企业做出最佳选择
- storage.googleapis.com 访问与下载慢
- CENTOS6.4安装vnc-server
- 2、安装和连接mysql
- java自定义迭代器作用_Java实现的自定义迭代器功能示例
- C++ 一篇文章让你知道智能指针的魅力
- 不会 SQL 的程序员有多难?可能工作都找不到!
- Linux 下使用 screen 与 conda / anaconda 环境的一点坑 | screen 中 conda 环境失效/缺 module
- ChinaITLab Linux实战工程师网校课程-8CD
- cdr添加节点快捷键_CorelDRAW常用热键|cdr快捷键大全
- 为什么不使能中断,中断标志位也会被置一
- db_create_file_dest
- python123货币转换器_Python之美元大写转换器
- 插本计算机教程,广东专插本复习方法
- Unable to import maven project: See logs for details
- 简单的注册表代码记录
- H3C忘记密码,优盘重置
热门文章
- python重命名异常_python异常处理
- notes邮件正文显示不全_Outlook邮件中的可操作消息(Actionable Message)
- 第一行代码学习笔记第四章——探究碎片
- Linux下源码编译安装新版libxcb
- 【已解决】Linux下安装JDK
- 强化学习3——有模型(Model-base)与无模型(Model-free)RL的区别
- c++读二进制文件结束的时候总会异常_python专题文件操作
- 设计模式的理解: 适配器模式 Adapter
- .net get set 初始化_RAM初始化的下板验证(Xilinx)
- Http / Get 和 Post 区别