Contents

Introduction

Let's say you have a large database in your program and you want to show the database to the user. You use a CListCtrl with a couple of columns and fill it with a few thousand, maybe million elements. When you run it, you notice it's a bit (or very) slow. Wouldn't it be great if you didn't need to add all elements to the list, and let the list show them anyway? Does it sound stupid and ridiculous? That's how a virtual list works.

Virtual list

A virtual list is a list that has no data, it only knows how many data items it is supposed to have. But how does it now what data to show? The secret is that the list asks the parent for the information it needs. Assume you have a list with 100 elements, and elements 10-20 are visible. When the list is redrawing, it first asks the parent about element 10. When the parent has answered, the list redraws element 10, and then goes on to the next element.

A virtual list is sending three different messages to the parent. LVN_GETDISPINFO is sent when the list needs information. This is the most important message. The message LVN_ODFINDITEM is sent when the user tries to find an item by writing in the list. LVN_ODCACHEHINT is sent to give you a chance to cache data. You will probably don't care about this message at all.

OK, that's enough fuzzy theory, let's look on some code instead.

Creating a virtual list

Creating a virtual list isn't much harder than creating an ordinary CListCtrl. Add a list control in the resource editor as you usually do. Then check the style "Owner data", and then add a CListCtrl variable for this control. The only difference from an ordinary CListCtrl is the "Owner data" (LVS_OWNERDATA) style.

When you work with a virtual list, you use it mostly in the same way you do with a non-virtual list. Adding columns, selecting items, adding image list and much more works exactly the same way.

Add items to the list

Let's say m_list is the control variable for the list. Normally, you add data to the list like this:

Collapse | Copy Code
m_list.InsertItem(0, _T("Hello world"));

But in a virtual list, this will not work. Instead, it is up to you to handle the data. Instead of adding, you change the number of elements the list is showing:

Collapse | Copy Code
//"Add" 100 elements
m_list.SetItemCount(100);

If you set the item count to 100 or 1,000,000 doesn't matter, the time to run this command will still be practically zero. In a non-virtual list, adding a million elements could take hours.

Handling the LVN_GETDISPINFO message

As I said before, the list is asking the parent for information when it needs it. The list does this by sending a LVN_GETDISPINFO message. This is the most important message to handle when you are dealing with a virtual list. A typical function looks like this:

Collapse | Copy Code
void CVirtualListDlg::OnGetdispinfoList(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR; //Create a pointer to the item
LV_ITEM* pItem= &(pDispInfo)->item; //Which item number?
int itemid = pItem->iItem; //Do the list need text information?
if (pItem->mask & LVIF_TEXT)
{
CString text; //Which column?
if(pItem->iSubItem == 0)
{
//Text is name
text = m_database[itemid].m_name;
}
else if (pItem->iSubItem == 1)
{
//Text is slogan
text = m_database[itemid].m_slogan;
} //Copy the text to the LV_ITEM structure
//Maximum number of characters is in pItem->cchTextMax
lstrcpyn(pItem->pszText, text, pItem->cchTextMax);
} //Do the list need image information?
if( pItem->mask & LVIF_IMAGE)
{
//Set which image to use
pItem->iImage=m_database[itemid].m_image; //Show check box?
if(IsCheckBoxesVisible())
{
//To enable check box, we have to enable state mask...
pItem->mask |= LVIF_STATE;
pItem->stateMask = LVIS_STATEIMAGEMASK; if(m_database[itemid].m_checked)
{
//Turn check box on
pItem->state = INDEXTOSTATEIMAGEMASK(2);
}
else
{
//Turn check box off
pItem->state = INDEXTOSTATEIMAGEMASK(1);
}
}
} *pResult = 0;
}

First, we create a LV_DISPINFO pointer, and then a pointer to the item. In itemid, we save which item we should handle. Then we check the mask in pItem. The mask is telling us what kind of information the list needs. First, we check if text information is needed. If it is, we first figure out which column the text is. The first column is for names, the second for slogans (several columns will be shown in report view, in other views only first column will be used).

We also check if image information is needed. If it is, we save which image to use in pItem->iImage (this is the number to an image in the image list connected to the list).

If check boxes are visible, we should send information about that when image information is requested. First, we change pItem->mask to tell that we are sending state information. We also change pItem->stateMask to tell what kind of information we are sending. Then we write if check box is on or off.

The mask could also have the flags LVIF_INDENT, LVIF_NORECOMPUTE, LVIF_PARAM, and LVIF_DI_SETITEM. But I have never used any of them, so I guess they aren't important .

It isn't harder to handle the LVN_GETDISPINFO message than this. Check boxes are probably the hardest, but you will probably never use any more complicated code than I have done in this example. When you have written this function, your list will almost act like a normal list. However, it might be a good idea to implement the LVN_ODFINDITEM as well.

Handling the LVN_ODFINDITEM message

First, some basic education: start Explorer and go to a folder where you have a lot of files. Press A. What happens? If you have a file or folder that begins with an 'A', that file should now be selected. Press A again. If you have more than one file that begins with an 'A', the second file should be selected. Write "AB". If there is any file that begins with 'AB', that is now selected. This is how every normal list control behaves. Aren't list controls cool? .

Let's look on how a list control normally searches:

Name
Anders
Anna
Annica
Bob
Emma
Emmanuel

Anna is selected. When we are writing anything, the list will search down to find the best match. If it reaches the end, it restarts at the top and searches until it is back on the start item (Anna). If "A" is written, Annika should be selected. If "AND" is written, Anders should be selected. If "ANNK" is written, the selection should stay on Anna. If "E" is written, Emma should be selected.

Unfortunately, this doesn't work with virtual lists. A virtual list doesn't try to find any item at all, unless you handle the LVN_ODFINDITEM message. I usually implement the message like this:

Collapse | Copy Code
void CVirtualListDlg::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; /* pFindInfo->iStart is from which item we should search.
We search to bottom, and then restart at top and will stop
at pFindInfo->iStart, unless we find an item that match
*/ // Set the default return value to -1
// That means we didn't find any match.
*pResult = -1; //Is search NOT based on string?
if( (pFindInfo->lvfi.flags & LVFI_STRING) == 0 )
{
//This will probably never happend...
return;
} //This is the string we search for
CString searchstr = 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; //Let's search...
do
{
//Do this word begins with all characters in searchstr?
if( _tcsnicmp(m_database[currentPos].m_name,
searchstr, searchstr.GetLength()) == 0)
{
//Select this item and stop search.
*pResult = currentPos;
break;
} //Go to next item
currentPos++; //Need to restart at top?
if(currentPos >= m_list.GetItemCount())
currentPos = 0; //Stop if back to start
}while(currentPos != startPos);
}

It may not be obvious how this works at a first look, but if you read it carefully, you will understand. Or, you simply skip this, and copy the code and just do the necessary changes - it's up to you . If the list is really large, maybe you need to make a faster version of this function, or don't implement it at all.

pFindInfo->lvfi has information on how you should search (like "Restart at top if bottom is reached" or in which direction). I have never cared about that, if you do, you should read in MSDN to get more information.

Handling the LVN_ODCACHEHINT message

LVN_ODCACHEHINT is sent to give you a chance to cache data. If you are working with a database that is in another computer in some network, maybe this is useful, but I haven't used this message in any of my programs. A function that handles this message will probably look something like this:

Collapse | Copy Code
void CVirtualListDlg::OnOdcachehintList(NMHDR* pNMHDR, LRESULT* pResult)
{
NMLVCACHEHINT* pCacheHint = (NMLVCACHEHINT*)pNMHDR; // ... Cache the data pCacheHint->iFrom to pCacheHint->iTo ... *pResult = 0;
}

This is quite simple as you see. But as usual, simple things don't work . According to MSDN, you should override OnChildNotify and add the handler in this function. But I will not dig deeper in this, if you need more information about this, read in MSDN.

Changing an item

What should you do to change the data? This is really simple. You don't change the data in the list, but in the database instead. To redraw the list items, call CListCtrl::RedrawItems.

Check boxes

Check boxes are useful, but they are quite tricky to implement when you are working with a virtual list. In a normal non-virtual list, check boxes are toggled when you click on them or when you press space. But in a virtual list, nothing will happen. So you have to implement these events yourself. We start with a simple toggle-check-box-function:

Collapse | Copy Code
void CVirtualListDlg::ToggleCheckBox(int item)
{
//Change check box
m_database[item].m_checked = !m_database[item].m_checked; //And redraw
m_list.RedrawItems(item, item);
}

We will call this function when we want to change an item. The function toggles the check box value (in the database!) and forces the list to redraw the item. Quite simple. Toggling a check box when space is pressed is also quite simple. Add a message handler for the LVN_KEYDOWN message. The function should be something like this:

Collapse | Copy Code
void CVirtualListDlg::OnKeydownList(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_KEYDOWN* pLVKeyDown = (LV_KEYDOWN*)pNMHDR; //If user press space, toggle flag on selected item
if( pLVKeyDown->wVKey == VK_SPACE )
{
//Toggle if some item is selected
if(m_list.GetSelectionMark() != -1)
ToggleCheckBox( m_list.GetSelectionMark() );
} *pResult = 0;
}

We check if space is pressed and if any item is selected before we toggle the check box. To toggle check box when we click on it, we have to do a more complicated function. Add a message handler for the NM_CLICK message. My function looks like this:

Collapse | Copy Code
void CVirtualListDlg::OnClickList(NMHDR* pNMHDR, LRESULT* pResult)
{
NMLISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; LVHITTESTINFO hitinfo;
//Copy click point
hitinfo.pt = pNMListView->ptAction; //Make the hit test...
int item = m_list.HitTest(&hitinfo); if(item != -1)
{
//We hit one item... did we hit state image (check box)?
//This test only works if we are in list or report mode.
if( (hitinfo.flags & LVHT_ONITEMSTATEICON) != 0)
{
ToggleCheckBox(item);
}
} *pResult = 0;
}

We are using CListCtrl::HitTest to see if we clicked on an item. If we did, the function returns which item we clicked on. We then use hitinfo.flags to see where we clicked. If the flag LVHT_ONITEMSTATEICON is on, then we know that the user clicked on the check box (state image).

Unfortunately, CListCtrl::HitTest doesn't work as it should if the list view is in "Icon" or "Small icon" mode. In these views, hitinfo.flags & LVHT_ONITEMSTATEICON is always 0. I haven't found a solution to this, if you do, let me now. However, using check boxes in these modes is probably quite unusual, so this is not a big problem.

Notes

Unless you are making something very unusual, you will not need to know more information than there is in this article, to use virtual lists. However, there are some minor compatibility issues between a virtual and a non-virtual list. For example, a virtual list can't sort data. But that is quite obvious, isn't it ? You find more information about these issues in MSDN.

When should you use a virtual list, and when should you not? For large lists, I personally prefer a virtual list. But a non-virtual list is usually easier to program (message handling is never fun), so for smaller lists, I use the ordinary list.

A very neat thing about virtual lists is that they are very easy to keep synchronized with a database. You just change the database and redraw the list if necessary. So, if you work with a list where the user should change data in the database, a virtual list could be useful even if number of items are low.

Another nice thing is that you sometimes could generate data when necessary. If you want to show row number in one column, that is very easy to do, and it takes practically no memory at all. In a non-virtual list, you would have to add this data on all items.

Using virtual lists的更多相关文章

  1. Bug管理工具的使用介绍(Bugger 2016)

    1. Bugger 2016 介绍 Bugger 2016 is the version of Bugger adding support fot Team Foundation Server bug ...

  2. Understanding Virtual Memory

    Understanding Virtual Memory by Norm Murray and Neil Horman Introduction Definitions The Life of a P ...

  3. [Windows Azure] Load Balancing Virtual Machines

    Load Balancing Virtual Machines All virtual machines that you create in Windows Azure can automatica ...

  4. The New Virtual List Box in Delphi 6 - lbVirtual lbVirtualOwnerDraw

    http://users.atw.hu/delphicikk/listaz.php?id=2471&oldal=52 Problem/Question/Abstract: What are t ...

  5. C++11中initializer lists的使用

    Before C++11,there was no easy way to do things like initialize a std::vector or std::map(or a custo ...

  6. virtual dynamic shared object

    vdso(7) - Linux manual page http://man7.org/linux/man-pages/man7/vdso.7.html NAME | SYNOPSIS | DESCR ...

  7. Attempt to invoke virtual method 'void android.app.ActionBar.setTitle的解决方法

    在安卓4.4.2的关于蓝牙开发的一个sample BluetoothChat中,调试时,老是出错:Attempt to invoke virtual method 'void android.app. ...

  8. PatentTips – EMC Virtual File System

    BACKGROUND OF THE INVENTION 1. Field of the Invention The present invention generally relates to net ...

  9. PatentTips - Supporting address translation in a virtual machine environment

    BACKGROUND A conventional virtual-machine monitor (VMM) typically runs on a computer and presents to ...

随机推荐

  1. 浅析StackTrace

    我们在学习函数调用时,都知道每个函数都拥有自己的栈空间.一个函数被调用时,就创建一个新的栈空间.那么通过函数的嵌套调用最后就形成了一个函数调用堆栈.在c#中,使用StackTrace记录这个堆栈.你可 ...

  2. ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务

    ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供一致性服务的软件,提供的功 ...

  3. HttpServlet中,用来处理POST请求的方法是(选择1项)

    HttpServlet中,用来处理POST请求的方法是(选择1项) A. doHead B. doGet C. doPost D. doPut 解答:C

  4. 下列哪个为JSP的小脚本的标签?(选择1项)

    下列哪个为JSP的小脚本的标签?(选择1项) A.<% %> B.<@ %> C.<%! %> D.<%– %> 解答:A

  5. mysql -- 存储过程中 declare 和 set 定义变量的区别

    mysql存储过程中,定义变量有两种方式:1.使用set或select直接赋值,变量名以 @ 开头.例如:set @var=1;可以在一个会话的任何地方声明,作用域是整个会话,称为会话变量. 2.以 ...

  6. 【noip模拟题】天神下凡(贪心)

    vijos某次模拟赛原题... 处理出每个圆的一级祖先就行了... 其实没有那么麻烦,贪心即可出解. 我们将每个圆转换成线段后按左端点小右端点大的方法排序 然后维护一个栈: 对于每一个圆i 如果栈顶右 ...

  7. MySQL之explain 的type列 & Extra列

    explain 可以分析 select 语句的执行,即 MySQL 的“执行计划. 一.type 列   MySQL 在表里找到所需行的方式.包括(由左至右,由最差到最好): | All | inde ...

  8. 如何用几何画板画V型尖波函数图像

    虽然几何画板没有编程功能,无法直接进行逻辑判断,但通过恰当地运用sgn.trunc等函数可以间接地画出一些特殊函数图像,比如V型尖波函数图像,下面将详细介绍如何用几何画板画V型尖波函数图像. 具体的操 ...

  9. 简单配置Nginx 指向本地端口,并开启SSL

    简单配置Nginx 指向本地端口,并开启SSL,如果要开启SSL,必须使用域名去申请SSL key,一般是两个文件,一般是要收费的. # 在/etc/nginx/nginx.conf 的文件中有下面一 ...

  10. Amazon(vpc)对等网络

    对等连接测试 1.1 东京账户A创建对等连接 注意,双方的vpc网段不能重复,如果重复会造成无法配置路由表 输入双方信息 账户ID和VPC 1.2 东京账户A查看 查看连接信息 显示正在处理接受,需要 ...