一、什么是虚拟列表控件

虚拟列表控件是指带有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;
    }
}

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;

*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、虚表的另一个优点是可以根据需要产生数据。比如在某一列加上行号。

http://blog.vckbase.com/iwaswzq/archive/2006/07/07/21113.html
http://www.codeproject.com/KB/list/virtuallist.aspx

clistctrl 虚拟列表的更多相关文章

  1. CListView虚拟列表

    首先说下虚拟列表出现的原因: 数据量比较小的时候,对于CListView控件可以直接使用InsertItem并配合SetItemText函数来插入并修改数据.这样操作很直接. 但是,如果数据量比较大了 ...

  2. 基于duilib的虚拟列表实现

    本文由作者邹启文授权网易云社区发布. 在邮箱大师选择duilib作为UI开发库后,我们面临这样一个问题.随着时间的积累,用户数据会越来越多,如何保证我们的软件在展示这些数据时依然保持非常好的体验? 原 ...

  3. MFC CListCtrl 将一个列表的选中项添加到另一个列表

    MFC CListCtrl 将一个列表的选中项添加到另一个列表, 用VC6.0实现: 简单记录一下自己的学习历程, 和大家分享,如果对你有用,我很高兴. 1.新建一个基于对话框的工程(Dialog-B ...

  4. 性能优化:虚拟列表,如何渲染10万条数据的dom,页面同时不卡顿

    列表大概有2万条数据,又不让做成分页,如果页面直接渲染2万条数据,在一些低配电脑上可能会照成页面卡死,基于这个需求,我们来手写一个虚拟列表 思路 列表中固定只显示少量的数据,比如60条 在列表滚动的时 ...

  5. vc mfc listctrl使用虚拟列表 虚拟列表用法

    note 虚拟列表可有效加载大量数据 需要处理listctrl的 LVN_GETDISPINFO 消息 使用虚拟列表 不能 使用 insert的方法插入数据,而是告诉listctrl 当前要显示多少行 ...

  6. 【react表格组件】react-virtualized虚拟列表

    https://css-tricks.com/rendering-lists-using-react-virtualized/

  7. react antd上拉加载与下拉刷新与虚拟列表使用

    创建项目 create-react-app antdReact 安装:antd-mobile.react-virtualized npm i antd-mobile -S npm i react-vi ...

  8. ClistCtrl用法及总结(由怎样隐藏ListCtrl列表头的排序小三角形这个bug学习到的知识)

    1 怎样隐藏ListCtrl列表头的排序小三角形 在创建控件是加入|LVS_NOSORTHEADER风格即可. 一下是用法总结: 本文根据本人在项目中的应用,来谈谈CListCtrl的部分用法及技巧. ...

  9. 【VS开发】MFC CListCtrl列表控件的消息响应

    MFC里的CListCtrl选中一行,消息是哪个.实在想不起来了.找了一篇文章,比较有用: http://www.cnblogs.com/hongfei/archive/2012/12/25/2832 ...

随机推荐

  1. Android checkBox

    checkBox      状态:选中(true),未选中(false)      属性:           checked="true/false"; private Chec ...

  2. Docker容器的跨主机连接

    Docker容器的跨主机连接 Docker容器的跨主机连接 使用网桥实现跨主机容器连接 网络拓扑 网络连接使用的是Bridge 操作 修改/etc/network/interfaces文件,分配静态I ...

  3. intent传对象

    intent还有一个很好用的地方,就是传输对象,但要注意的是这里的传输只是将对象复制了一份通过intent进行传递,并不能达到实时更新的效果,也就是这个对象等偏重于“读”.intent对这个对象有着严 ...

  4. Doctype 严格模式与混杂模式-如何触发这两种模式,区分它们有何意义?

    Doctype:(Document Type)文档类型,它位于文档中最前面的位置,处于标签之前.如果你想制作符合标准的页面,一个必不可少的关键组成部分就是DOCTYPE的声明.确定了正确的Doctyp ...

  5. 柯南君:看大数据时代下的IT架构(2)消息队列之RabbitMQ-基础概念详细介绍

    一.基础概念详细介绍 1.引言 你是否遇到过两个(多个)系统间需要通过定时任务来同步某些数据?你是否在为异构系统的不同进程间相互调用.通讯的问题而苦恼.挣扎?如果是,那么恭喜你,消息服务让你可以很轻松 ...

  6. 使用libcurl提交POST请求

    最近在学习libcurl,并利用它提交POST请求,可是返回的响应总是无从验证该次POST请求是否成功提交了. 1. 先看下根据firebug提交的一次成功的请求,这里以login我喜欢上的xiami ...

  7. poj2140---herd sums

    #include<stdio.h> #include<stdlib.h> int main() { ,i,j; scanf("%d",&n); ;i ...

  8. C++中实现链表的删除和颠倒

    MFC工程中关于链表的操作 1.对于给定的整数n,编写算法删除链表中第n个节点,该链表的第一个节点由first指向. 由于C++中没有关于node的标准头文件,要先手动定义node类,此处只定义了简单 ...

  9. 新浪微博布局学习——妙用TabHost

    前言 为了更好的开发Android应用程序,除了熟练掌握基本的UI组件和API外,还需要掌握一些技巧,而这些技巧可以通过阅读一些代码来提高,本系列将与大家分享一些新浪微博布局方面的收获,欢迎交流! 声 ...

  10. android-supporting-multiple-devices

    There are a few common questions asked whenever development begins on a new Android app. What assets ...